Tripleo之nova-compute 和Ironic的代码深入分析(五)

来源:互联网 发布:惠普m1216nfh扫描软件 编辑:程序博客网 时间:2024/05/20 00:13

声明:

本博客欢迎转载,但请保留原作者信息!

作者:姜飞

团队:华为杭州OpenStack团队


物理单板在PXE的init启动时候,deploy-ironic发送了一个POST v1/nodes/{node-id}/vendor_passthru/pass_deploy_info请求到ironic-api,数据data是:

DATA='{"address":"$BOOT_IP_ADDRESS","key":"$DEPLOYMENT_KEY","iqn":"$ISCSI_TARGET_IQN","error":"$FIRST_ERR_MSG"}'

 

Ironic-api的处理是在ironic.api.controllers.v1.node.NodeVendorPassthruController的post方法处理的,这个方法很简单,检查到node是否存在,发送vendor_passthru的rpc请求到ironic-conductor,这里的method就是pass_deploy_info

    def post(self, node_uuid, method, data):        # Raise an exception if node is not found        rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid)        topic = pecan.request.rpcapi.get_topic_for(rpc_node)        # Raise an exception if method is not specified        if not method:            raise wsme.exc.ClientSideError(_("Method not specified"))        return pecan.request.rpcapi.vendor_passthru(                pecan.request.context, node_uuid, method, data, topic)

ironic-conductor的manager类vendor_passthru进行处理

    def vendor_passthru(self, context, node_id, driver_method, info):        LOG.debug("RPC vendor_passthru called for node %s." % node_id)                with task_manager.acquire(context, node_id, shared=False) as task:            if not getattr(task.driver, 'vendor', None):                raise exception.UnsupportedDriverExtension(                    driver=task.node.driver,                    extension='vendor passthru')            task.driver.vendor.validate(task, method=driver_method,                                        **info)            task.spawn_after(self._spawn_worker,                             task.driver.vendor.vendor_passthru, task,                             method=driver_method, **info)

task.driver.vendor是ironic.drivers.modules.pxe.VendorPassthru类,validate方法和vendor_passthru方法都是ironic.drivers.modules.pxe.VendorPassthru类的方法。

Validate就是检测instance_info的必须参数是否ok,其实就是参数检测。

    def validate(self, task, **kwargs):        method = kwargs['method']        if method == 'pass_deploy_info':            iscsi_deploy.get_deploy_info(task.node, **kwargs)        else:            raise exception.InvalidParameterValue(_(                "Unsupported method (%s) passed to PXE driver.")                % method)

vendor_passthru就只支持pass_deploy_info的method方法,会调用self._continue_deploy的方法,这个就是前面ironic-conductor进行deploy结束后设置state为DEPLOYWAIT,然后这个方法就是后续的动作。

    def vendor_passthru(self, task, **kwargs):                method = kwargs['method']        if method == 'pass_deploy_info':            self._continue_deploy(task, **kwargs)

_continue_deploy其实就是通过iSCSI来继续部署物理节点,而且要进行继续部署,单板的状态只能是DEPLOYWAIT,其他的状态都会报错。该函数主要有以下步骤:

1、  删除使用的token鉴权文件,

2、  发现iSCSI设备,这个设备是物理单板在PXE的时候开启的iSCSI设备,在这里就是要image_source的镜像通过dd命令以direct方式写到发现的iSCSI设备磁盘上,还涉及到创建swap分区和ephemeral第二分区,同时删除image_source的镜像文件。

3、  获取tftp下的DHCP的启动的config文件,然后修改config的default的默认启动项为boot,config文件里面有2个启动项,一个启动项是PXE的deploy,另外就是我们这设置的这个。

4、通过端口10000通知iSCSI关闭iSCSI设备,通知PXE结束,设置单板部署状态为ACTIVE。

    @task_manager.require_exclusive_lock    def _continue_deploy(self, task, **kwargs):        if node.provision_state != states.DEPLOYWAIT:            LOG.error(_LE('Node %s is not waiting to be deployed.'), node.uuid)            return        _destroy_token_file(node)       root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)        try:            pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)            deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,                          driver_utils.get_node_capability(node, 'boot_mode'))            deploy_utils.notify_deploy_complete(kwargs['address'])            LOG.info(_LI('Deployment to node %s done'), node.uuid)            node.provision_state = states.ACTIVE            …..        except Exception as e:…….
scsi_deploy.continue_deploy()这个方法就是做第二步的事情,先解析得到deploy_info

params = {'address': kwargs.get('address'),              'port': kwargs.get('port', '3260'),              'iqn': kwargs.get('iqn'),              'lun': kwargs.get('lun', '1'),              'image_path': _get_image_file_path(node.uuid),              'root_mb': 1024 * int(i_info['root_gb']),              'swap_mb': int(i_info['swap_mb']),              'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']),              'preserve_ephemeral': i_info['preserve_ephemeral'],              'node_uuid': node.uuid,             }

然后调用deploy_utils.deploy,先获取到单板上的/dev/disk/by-path/ip-{address}:{port}-iscsi-{irq}-lun-{lun}的设备,然后调用命令iscsiadm的命令发现网络上的设备(iscsiadm–m discovery –t st –p {address}:{port} ),然后发现设备上login这个iSCSI设备,然后调用work_on_disk这个方法将镜像写到iSCSI设备上,写完后,退出登录iSCSI,然后删除iSCSI设备。

def deploy(address, port, iqn, lun, image_path,           root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,           preserve_ephemeral=False):        dev = get_dev(address, port, iqn, lun)    image_mb = get_image_mb(image_path)    if image_mb > root_mb:        root_mb = image_mb    discovery(address, port)    login_iscsi(address, port, iqn)    try:        root_uuid = work_on_disk(dev, root_mb, swap_mb, ephemeral_mb,                                 ephemeral_format, image_path, node_uuid,                                 preserve_ephemeral)    except processutils.ProcessExecutionError as err:        with excutils.save_and_reraise_exception():            LOG.error(_LE("Deploy to address %s failed."), address)            LOG.error(_LE("Command: %s"), err.cmd)            LOG.error(_LE("StdOut: %r"), err.stdout)            LOG.error(_LE("StdErr: %r"), err.stderr)    except exception.InstanceDeployFailure as e:        with excutils.save_and_reraise_exception():            LOG.error(_LE("Deploy to address %s failed."), address)            LOG.error(e)    finally:        logout_iscsi(address, port, iqn)        delete_iscsi(address, port, iqn)    return root_uuid

work_on_disk这个方法其实就是使用dd命令写镜像文件到磁盘上、用parted进行分区(root分区,swap分区,ephemeral第二磁盘分区)、用mkfs的命令对分区进行格式化(swap分区的话用mkswap)。

这里有个疑问:iSCSI登录的话,如果用CHAP验证的话不是需要用户名和密码,如果没有验证,这块安全应该是有问题的,不知道社区是怎么考虑的。

当上面的方法执行完成后,就是设置DHCP的config,设置为boot,我们知道在instance_info上设置了ramdisk、kernel、image_source 3个镜像,其实就是内核、根文件系统、磁盘。这里就是设置了ramdisk和kernel,磁盘镜像上面已经写到磁盘中去了。switch_pxe_config这个方法就是将当前的操作系统的启动项设置为ramdisk和kernel作为引导程序。

def switch_pxe_config(path, root_uuid, boot_mode):    """Switch a pxe config from deployment mode to service mode."""    with open(path) as f:        lines = f.readlines()    root = 'UUID=%s' % root_uuid    rre = re.compile(r'\{\{ ROOT \}\}')    if boot_mode == 'uefi':        dre = re.compile('^default=.*$')        boot_line = 'default=boot'    else:        pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'        dre = re.compile('^%s .*$' % pxe_cmd)        boot_line = '%s boot' % pxe_cmd    with open(path, 'w') as f:        for line in lines:            line = rre.sub(root, line)            line = dre.sub(boot_line, line)            f.write(line)

deploy_utils.notify_deploy_complete就是往10000端口发送一个socket信息,内容是Done,然后物理单板收到这个socket信息后,就执行reboot了。

def notify(address, port):    """Notify a node that it becomes ready to reboot."""    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    try:        s.connect((address, port))        s.send('done')    finally:        s.close()

reboot之后内核和根文件系统还是需要PXE上引导,进入os。OS启动后动作取决于image-source的镜像,目前os启动后,会运行os-refresh-config将openstack的服务拉起来。至此,tripleO部署openstack的源代码流程解析结束。

0 0
原创粉丝点击