一、用戶調用Nova的rescue函數node
nova/virt/ironic/driver.py class IronicDriver(virt_driver.ComputeDriver): ...... ...... #導入ironicclient模塊 def __init__(self, virtapi, read_only=False): super(IronicDriver, self).__init__(virtapi) global ironic if ironic is None: ironic = importutils.import_module('ironicclient') ...... self.ironicclient = client_wrapper.IronicClientWrapper() def spawn(self, context, instance, image_meta, injected_files, admin_password, allocations, network_info=None, block_device_info=None): ...... #調用ironicclient.call方法,觸發節點部署 try: self.ironicclient.call("node.set_provision_state", node_uuid, ironic_states.ACTIVE, configdrive=configdrive_value) ...... try: ##Virt驅動程序在等待provision_state更改時循環,並根據須要更新Nova狀態 timer.start(interval=CONF.ironic.api_retry_interval).wait() LOG.info('Successfully provisioned Ironic node %s', node.uuid, instance=instance)
ironic/api/controllers/v1/node.py #ronic API接收set_provision_state調用,並執行do_node_rescue RPC調用 class NodeStatesController(rest.RestController): def provision(self, node_ident, target, configdrive=None, clean_steps=None, rescue_password=None): ..... elif (target == ir_states.VERBS['rescue']): if not (rescue_password and rescue_password.strip()): msg = (_('A non-empty "rescue_password" is required when ' 'setting target provision state to %s') % ir_states.VERBS['rescue']) raise wsme.exc.ClientSideError( msg, status_code=http_client.BAD_REQUEST) pecan.request.rpcapi.do_node_rescue( pecan.request.context, rpc_node.uuid, rescue_password, topic)
ironic/conductor/manager.py class ConductorManager(base_manager.BaseConductorManager): ...... def do_node_rescue(self, context, node_id, rescue_password): ...... #保存節點的救援密碼 instance_info = node.instance_info instance_info['rescue_password'] = rescue_password node.instance_info = instance_info node.save()#Ironic conductor在instance_info中設置了救援密碼並將通知給相應的驅動 try: task.driver.power.validate(task) task.driver.rescue.validate(task) task.driver.network.validate(task) try: task.process_event( 'rescue', callback=self._spawn_worker, call_args=(self._do_node_rescue, task),#內部RPC方法來救援現有的節點部署 err_handler=utils.spawn_rescue_error_handler) def _do_node_rescue(self, task): ...... try: next_state = task.driver.rescue.rescue(task) if next_state == states.RESCUEWAIT: task.process_event('wait') elif next_state == states.RESCUE: task.process_event('done')
ironic/drivers/modules/agent.py class AgentRescue(base.RescueInterface): ..... #在節點上啓動一個救援ramdisk def rescue(self, task): #重置電源狀態 manager_utils.node_power_action(task, states.POWER_OFF) #清理實例 task.driver.boot.clean_up_instance(task) #取消節點的租戶網絡 task.driver.network.unconfigure_tenant_networks(task) #爲每一個端口建立neutron端口以啓動救援虛擬磁盤 task.driver.network.add_rescuing_network(task) if CONF.agent.manage_agent_boot: ramdisk_opts = deploy_utils.build_agent_options(task.node) #使用PXE準備Ironic ramdisk的引導 task.driver.boot.prepare_ramdisk(task, ramdisk_opts) #重置電源狀態爲POWER_ON manager_utils.node_power_action(task, states.POWER_ON) return states.RESCUEWAIT
ironic/drivers/modules/pxe.py class PXEBoot(base.BootInterface): ...... def prepare_ramdisk(self, task, ramdisk_params): node = task.node mode = deploy_utils.rescue_or_deploy_mode(node) if CONF.pxe.ipxe_enabled: #將iPXE引導腳本呈現到HTTP根目錄 pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance(task)#檢索DHCP PXE啓動選項 provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts)#發送或更新此節點的DHCP BOOT選項 pxe_info = _get_image_info(node, mode=mode)#爲救援鏡像生成TFTP文件的路徑 manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: kernel_label = '%s_kernel' % mode ramdisk_label = '%s_ramdisk' % mode pxe_info.pop(kernel_label, None) pxe_info.pop(ramdisk_label, None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
ipa和ironic-conductor交互,Agent ramdisk啓動後,回調/v1/lookup獲取節點信息, 發送心跳python
ironic/drivers/modules/agent_base_vendor.py class HeartbeatMixin(object): ...... def heartbeat(self, task, callback_url, agent_version): ...... try: ..... elif (node.provision_state == states.RESCUEWAIT): msg = _('Node failed to perform rescue operation.') self._finalize_rescue(task) def _finalize_rescue(self, task): node = task.node try: result = self._client.finalize_rescue(node)
ironic/drivers/modules/agent_client.py class AgentClient(object): #指示虛擬磁盤完成救援模式的進入 def finalize_rescue(self, node): #根據config drive和rescue password調用finalize_rescue(RESCUEWAIT -> RESCUING),向ipa傳入rescue_password rescue_pass = node.instance_info.get('rescue_password') params = {'rescue_password': rescue_pass} return self._command(node=node, method='rescue.finalize_rescue', params=params) def _command(self, node, method, params, wait=False): #向ipa發送命令 url = self._get_command_url(node) body = self._get_command_body(method, params) request_params = { 'wait': str(wait).lower() try: response = self.session.post(url, params=request_params, data=body)
ironic_python_agent/extensions/rescue.py PASSWORD_FILE = '/etc/ipa-rescue-config/ipa-rescue-password' class RescueExtension(base.BaseAgentExtension): def finalize_rescue(self, rescue_password=""): self.write_rescue_password(rescue_password) self.agent.serve_api = False #關閉api接口 return def write_rescue_password(self, rescue_password=""): LOG.debug('Writing hashed rescue password to %s', PASSWORD_FILE) salt = self.make_salt() hashed_password = crypt.crypt(rescue_password, salt) try: with open(PASSWORD_FILE, 'w') as f: f.write(hashed_password)#把救援密碼寫入到/etc/ipa-rescue-config/ipa-rescue-password
ironic/drivers/modules/agent_base_vendor.py class HeartbeatMixin(object): #調用ramdisk來準備救援模式並驗證結果 def _finalize_rescue(self, task): node = task.node try: result = self._client.finalize_rescue(node) task.process_event('resume')#恢復node的狀態 task.driver.rescue.clean_up(task)#清理此節點的部署環境 task.driver.network.configure_tenant_networks(task)#將網絡調整到以前的租戶網絡 task.process_event('done')#返回task狀態爲done