基于openstack二次开发IaaS平台:Solid

来源:互联网 发布:关于云计算的图片 编辑:程序博客网 时间:2024/06/06 16:39

写在前面的话

一直想对2016进行总结一下,可是没有时间,现在刚好有时间就把自己2016年做的工作总结一下吧。

2016年主要做了一个项目,项目代号为Solid的Iaas平台,这个平台主要是把硬件资源暴露给租户进行使用,使租户可以充分的申请资源,然后调用或者查看自己已有的资源。

  • 硬件即服务平台以下简称(solid)有三个系统组成:
    – 采购系统,设备管理系统,IDC系统
  • 以下主要介绍一下设备管理系统和IDC系统,

设备管理系统

设备管理系统的功能:

1.添加设备,记录设备的硬件信息;

2.对于服务器可以远程进行开机与关机,并获取console

3.对于裸机可以安装租户上传镜像,进行远程的装机

服务器的来源有两个地方,一个是走采购系统进行自动的生成,另外就是直接在设备管理进行添加。

对于设备的管理,我们分为两种权限,一种是admin权限,管理员可以查看所有的物理设备,对于普通用户则只能查看属于他名下的设备,或者说用户所在组的设备,这里的分组是用project_id来进行分组的。
这里写图片描述
对于设备我们在设备管理页面可以清晰的看到所有设本,看到他所属的人,所属的IDC 并且可以对设备进行管理。

设备管理我们使用的是openstack的组件ironic,下面我们主要介绍一下我们在solid项目中怎么实现远程的开机,关机,装操作系统。
这里写图片描述
ironic的逻辑主要实在服务器的controller这里进行实现的,安装了ironic的客户端放在了common这个目录中

ironic

#这个是ironic的控制节点class Controller(rest.RestController):    def __init__(self):              #实例化ironic的客户端        super(Controller, self).__init__()        self.ironicclient = client_wrapper.IronicClientWrapper()    def _wait_for_power_state(self, ironic_node_id):     #这里是判断node的状态是不是关机,如果状态为None,则跳出循环        """Wait for the node to complete a power state change."""        node = self.ironicclient.call('node.get', ironic_node_id)        if node.target_power_state is None:            raise loopingcall.LoopingCallDone()    def _wait_for_provision_manageable(self, ironic_node_id):       #这里是检查node节点到没有到可manageable的状态,就是可以操作的状态        node = self.ironicclient.call('node.get', ironic_node_id)        if node.provision_state == 'manageable':            raise loopingcall.LoopingCallDone()    def _wait_for_provison_available_or_error(self, ironic_node_id):       # 这里是个检查,如果不是available专业并且不是错误的状态,这样代表node进入       #可以操作的状态        node = self.ironicclient.call('node.get', ironic_node_id)        if node.provision_state != 'available' and node.provision_state != 'error':            raise loopingcall.LoopingCallDone()    def _get_project(self):    #从keystone获取project的列表,获取用户的id和用户名        project_list = keystone_client.get_client().projects.list()        # print dir(project_list)        project_dict = dict((project.id, project.name)                            for project in project_list)        return project_dict

上面是几个状态的检查,判断服务器到没到可以被操作的状态

#这里是删除ironic的node节点信息def _delete_ironic_node(self, ironic_node_id):        try:            ironic_node = self.ironicclient.call('node.get', ironic_node_id)            #传入ironic节点的id,获取一个实例        except Exception, msg:            LOG.info(msg)            return None         #这里是先判断的node的状态,如果node节点是clean wait状态则必须把node的状态         #转变为可操作的状态才能删除ironic节点         if ironic_node.provision_state == 'clean wait':            self.ironicclient.call(                'node.set_provision_state', ironic_node.uuid, 'abort')                #先置为abort(中止)状态            self.ironicclient.call(                'node.wait_for_provision_state', ironic_node.uuid, 'clean failed')  #然后置为clean failed状态            self.ironicclient.call(                'node.set_provision_state', ironic_node.uuid, 'manage')                #最后置为可以操作的manage状态            self.ironicclient.call(                'node.wait_for_provision_state', ironic_node.uuid, 'manageable')                #但是机器不能立马改变自己状态,所以用需要监控node的状态,这里是检查状态        elif ironic_node.provision_state == 'active' or ironic_node.provision_state == 'deploy failed':        #如果node节点的状态是激活或者部署失败时,下面是我们的操作步骤            self.ironicclient.call(                'node.set_provision_state', ironic_node.uuid, 'deleted')                #直接执行删除ironic节点的状态            timer = loopingcall.FixedIntervalLoopingCall(                self._wait_for_provison_available_or_error, ironic_node_id)            timer.start(interval=0).wait()            #这里是判断节点是不是出现错误或者是可使用        elif ironic_node.provision_state == 'clean failed':        #clean failed状态时            self.ironicclient.call(                'node.set_provision_state', ironic_node.uuid, 'manage')                #node节点置为manage的状态            self.ironicclient.call(                'node.wait_for_provision_state', ironic_node.uuid, 'manageable')             #这里是监控ironic的状态        try:            ports_list = self.ironicclient.call(                'node.list_ports', ironic_node_id, detail=True)                #获取ironic客户端的node端口            for port in ports_list:                if port.extra.get('vif_port_id', None):                #剔除不需要的port                    try:                        neutronclient(pecan.request.context).delete_port(                            port.extra.get('vif_port_id'))                        #neutron的端口删除                        except Exception, msg:                        LOG.info(msg)                try:                    self.ironicclient.call('port.delete', port.uuid)                    #删除ironic客户端的端口                except Exception, msg:                    LOG.info(msg)        except Exception, msg:            LOG.info(msg)        try:            neutronclient(pecan.request.context).delete_port(                ironic_node.driver_info['ipmi_port_id'])                #删除impi的端口        except Exception, msg:            LOG.info(msg)        try:            from pyghmi.ipmi.command import Command            cm = Command(ironic_node.driver_info[                         'ipmi_address'], userid='用户', password='密码')           #创建一个command实例,impi的用户一般用root还有密码,这里写死了            cm.set_net_configuration(ipv4_configuration='DHCP')        except Exception, msg:            LOG.info(msg)        self.ironicclient.call('node.delete', ironic_node_id)        #最后删除ironic客户端,传入它的id

下面是远程开机和关机的介绍

 def _change_power_state_server(self, id, **kwargs):        node = pecan.request.db_api.node_get(pecan.request.context, id)        #从前端传来的context获取需要操作的node的ID        if node.ironic_node_id:            if kwargs.get('power_state') == 'power on':                #前端需要开机的信息放在power_state中 power on代表开机                try:                    self.ironicclient.call(                        "node.set_power_state",                        node.ironic_node_id,                        'on')                    #调用ironic的客户端,传入ironic的节点id与on表示开机                    timer = loopingcall.FixedIntervalLoopingCall(                        self._wait_for_power_state, node.ironic_node_id)                    timer.start(interval=0).wait()                    #这里在一直检测是否已经开机                    node = pecan.request.db_api.node_update(                        pecan.request.context, id, kwargs)                    #开机后更新数据库中node节点的信息                    node = self.get(node.id)                    return node                except Exception, msg:                    LOG.info(msg)                    return None            else:                try:                    self.ironicclient.call(                        "node.set_power_state",                        node.ironic_node_id,                        'off')                     #关机操作                    timer = loopingcall.FixedIntervalLoopingCall(                        self._wait_for_power_state, node.ironic_node_id)                    timer.start(interval=0).wait()                    node = pecan.request.db_api.node_update(                        pecan.request.context, id, kwargs)                    node = self.get(node.id)                    return node                except Exception, msg:                    LOG.info(msg)                    return None        else:            return None

远程给裸机安装系统

def _change_os_server(self, id, **kwargs):        node = pecan.request.db_api.node_get(pecan.request.context, id)        # 获取node        if node.ironic_node_id:            ironic_node = self.ironicclient.call(                'node.get', node.ironic_node_id)            #获取ironic的节点                patch = []            #patch用来储存配置信息的列表            kernel_id = kwargs.get('image_dict').get('kernel_id', None)            #安装前要的kernel前台传回            ramdisk_id = kwargs.get('image_dict').get('ramdisk_id', None)            #磁盘            image_id = kwargs.get('image_dict').get('image_id')            #镜像            root_partition = kwargs.get('root_partition')            #根分区            if kernel_id:                patch.append(                    {'op': 'add', 'path': '/instance_info/kernel', 'value': kernel_id})            if ramdisk_id:                patch.append(                    {'op': 'add', 'path': '/instance_info/ramdisk', 'value': ramdisk_id})            patch.append(                {'op': 'add', 'path': '/instance_info/image_source', 'value': image_id})            patch.append(                {'op': 'add', 'path': '/instance_info/root_gb', 'value': root_partition})            #以上都是把配置信息存到了列表中            self.ironicclient.call('node.update', node.ironic_node_id, patch)            #把配置信息patch传给了ironic            if ironic_node.provision_state == 'active':             #节点的状态如果已经是激活的状态,则可以直接进行创建                self.ironicclient.call(                    'node.set_provision_state', node.ironic_node_id, 'rebuild')               #这里传入ironic的id还有rebuild,就可以装系统            if ironic_node.provision_state == 'available' or ironic_node.provision_state == 'deploy failed':         #如果是avaiable或者部署失败                install_mac_address = node.nic.get('install_mac_address')                #获取node的mac地址,数据库中的                install_network_id = CONF.ironic.install_network_id                #分配网络                body = {'port': {'network_id': install_network_id}}                body['port']['binding:vnic_type'] = 'baremetal'                body['port']['tenant_id'] = node.project_id                body['port']['mac_address'] = install_mac_address                #组一个配置的字典body                ports_list = self.ironicclient.call(                    'node.list_ports', node.ironic_node_id, detail=True)                    #获取端口列表                for port in ports_list:                    if not port.extra:                        try:                            install_port = neutronclient(                                pecan.request.context).create_port(body=body).get('port')                      #如果端口中没有在neutron中则创建端口在neutron                                      self.ironicclient.call('port.update', port.uuid, patch=[{'op': 'add', 'path': '/extra/vif_port_id', 'value': install_port['id']}] #更新ironic中的信息                                                    )                        except Exception:                            node.message = 'Unable to create intall port!'                            return node                self.ironicclient.call(                    'node.set_provision_state', node.ironic_node_id, 'active')            node = self.get(node.id)            return node        else:            return None

激活baremetal

服务器不是立马就可以使用,需要创建链接,这个就是激活

def _change_activate(self, id, **kwargs):        node = pecan.request.db_api.node_get(pecan.request.context, id)        if kwargs['activate'] == True:        #先判断前台传来是不是激活命令            if node.nic:                if node.nic.get('ipmi_mac_address', None) and node.nic.get('install_mac_address', None):                #取得物理机的ipmi的mac地址,与物理机的mac地址                #这个是idc管理员在录入的数据                    ipmi_mac_address = node.nic.get('ipmi_mac_address')                    install_mac_address = node.nic.get('install_mac_address')                    ipmi_network_id = CONF.ironic.ipmi_network_id                    body = {'port': {'network_id': ipmi_network_id}}                    body['port']['binding:vnic_type'] = 'baremetal'                    body['port']['tenant_id'] = node.project_id                    body['port']['mac_address'] = ipmi_mac_address                    #组一个body字典来进行数据传输                    try:                        ipmi_port = neutronclient(                    pecan.request.context).create_port(body=body).get('port')                    #创建ipmi的端口                    except Exception, msg:                        LOG.info(msg)                        node.message = msg                        return node                    driver_info = {}                    driver_info['ipmi_terminal_port'] = 9003                    driver_info['ipmi_username'] = 'root'                    driver_info['ipmi_port_id'] = ipmi_port['id']                    driver_info['ipmi_address'] = ipmi_port[                        'fixed_ips'][0]['ip_address']                    #使用driber_info来创建ipmi                        response = os.system(                        "ping -c 20 " + driver_info['ipmi_address'])                        #ping 看是否可以ping通impi的地址                    if response != 0:                        node.message = 'Need DHCP init.'                        ipmi_port = neutronclient(                            pecan.request.context).delete_port(ipmi_port['id'])                        return node                    driver_info['ipmi_password'] = '密码'                    driver_info['deploy_kernel'] = CONF.ironic.deploy_kernel                    driver_info['deploy_ramdisk'] = CONF.ironic.deploy_ramdisk                    try:                        ironic_node = self.ironicclient.call(                            'node.create', driver_info=driver_info, driver='pxe_ipmitool')                         #创建ironic的节点                    except Exception, msg:                        LOG.info(msg)                        ipmi_port = neutronclient(                            pecan.request.context).delete_port(ipmi_port['id'])                           #没成功则删除端口                        node.message = msg                        return node                    try:                        install_ironic_port = self.ironicclient.call(                            'port.create', address=install_mac_address, node_uuid=ironic_node.uuid)                   #ironic的端口创建                    except Exception, msg:                        LOG.info(msg)                        ipmi_port = neutronclient(                            pecan.request.context).delete_port(ipmi_port['id'])                        ironic_node = self.ironicclient.call(                            'node.delete', ironic_node.uuid)                       #如果创建没有成功,则把相对应的ipmi的端口和ironic的端口都删除                        # install_port = neutronclient(pecan.request.context).delete_port(install_port['id'])                        node.message = msg                        return node                    self.ironicclient.call(                        'node.set_console_mode', ironic_node.uuid, True)               #openstack的物理机的console的获取一个实例                    self.ironicclient.call(                        'node.set_provision_state', ironic_node.uuid, 'manage')                   #将ironic节点置成可操作的状态manage                    timer = loopingcall.FixedIntervalLoopingCall(                        self._wait_for_provision_manageable, ironic_node.uuid)                    timer.start(interval=0).wait()                    self.ironicclient.call(                        'node.set_provision_state', ironic_node.uuid, 'provide')                    kwargs['ironic_node_id'] = ironic_node.uuid                    node = pecan.request.db_api.node_update(                        pecan.request.context, id, kwargs)                    node = self.get(node.id)                    return node                else:                    node.message = 'Please input mac address first!'                    return node            else:                node.message = 'Please input mac address first!'                return node        else:            self._delete_ironic_node(node.ironic_node_id)            kwargs['ironic_node_id'] = None            node = pecan.request.db_api.node_update(                pecan.request.context, id, kwargs)           #更新数据库的信息            node = self.get(node.id)            return node

IDC管理

这里写图片描述

数据中心可以查看机房的信息
这里写图片描述

使用了多少机柜的数目

点击进行时每一列的机柜名称
这里写图片描述

每一列点进行是其中机柜的信息
这里写图片描述

查看机柜上的设备
这里写图片描述

点击设备的名称可以查看设备的具体信息。

机架申请是可以预定机架来供自己进行预定的机器用来上架时进行使用,管理员对于租户的申请的机架,可以同意或者退回,已经预定的机架,其他租户不能进行预定以及进行上架行为。当机器进行移动的时候,空出的机架,管理员可以手动收回。

这部分的图先不贴了,主要是个走一个审批流程(抽时间我会更新一下idc的管理代码)。

结尾的话

致辞solid硬件即服务系统介绍的差不多了,从中确实学了不少东西,使我对openstack的开发有了一些浅薄的理解,感谢xyp,gs,zrl,一起看着solid从无到有。

0 0
原创粉丝点击