声明:
本博客欢迎转载,但请保留原作者信息!
作者:姜飞
团队:华为杭州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的源代码流程解析结束。