def test_create_pxe_config_uefi_grub(self, ensure_tree_mock, render_mock, write_mock, link_ip_configs_mock, chmod_mock): grub_tmplte = "ironic/drivers/modules/pxe_grub_config.template" with task_manager.acquire(self.context, self.node.uuid) as task: task.node.properties['capabilities'] = 'boot_mode:uefi' pxe_utils.create_pxe_config(task, self.pxe_options, grub_tmplte) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')), ] ensure_tree_mock.assert_has_calls(ensure_calls) chmod_mock.assert_not_called() render_mock.assert_called_with( grub_tmplte, {'pxe_options': self.pxe_options, 'ROOT': '(( ROOT ))', 'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))'}) link_ip_configs_mock.assert_called_once_with(task, False) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def _prepare_instance_pxe_config(self, task, image_info, iscsi_boot=False, ramdisk_boot=False): """Prepares the config file for PXE boot :param task: a task from TaskManager. :param image_info: a dict of values of instance image metadata to set on the configuration file. :param iscsi_boot: if boot is from an iSCSI volume or not. :param ramdisk_boot: if the boot is to a ramdisk configuration. :returns: None """ node = task.node dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) if not os.path.isfile(pxe_config_path): pxe_options = _build_pxe_config_options(task, image_info, service=ramdisk_boot) pxe_config_template = (deploy_utils.get_pxe_config_template(node)) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) deploy_utils.switch_pxe_config( pxe_config_path, None, boot_mode_utils.get_boot_mode_for_deploy(node), False, iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot)
def test_continue_deploy_good(self, mock_image_cache, mock_switch_config, notify_mock): token_path = self._create_token_file() self.node.power_state = states.POWER_ON self.node.provision_state = states.DEPLOYWAIT self.node.target_provision_state = states.ACTIVE self.node.save() root_uuid = "12345678-1234-1234-1234-1234567890abcxyz" boot_mode = None def fake_deploy(**kwargs): return root_uuid self.useFixture(fixtures.MonkeyPatch( 'ironic.drivers.modules.deploy_utils.deploy', fake_deploy)) with task_manager.acquire(self.context, self.node.uuid) as task: task.driver.vendor._continue_deploy( task, address='123456', iqn='aaa-bbb', key='fake-56789') self.node.refresh() self.assertEqual(states.ACTIVE, self.node.provision_state) self.assertEqual(states.NOSTATE, self.node.target_provision_state) self.assertEqual(states.POWER_ON, self.node.power_state) self.assertIsNone(self.node.last_error) self.assertFalse(os.path.exists(token_path)) mock_image_cache.assert_called_once_with() mock_image_cache.return_value.clean_up.assert_called_once_with() pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) mock_switch_config.assert_called_once_with(pxe_config_path, root_uuid, boot_mode) notify_mock.assert_called_once_with('123456')
def _get_deploy_info(self, node, **kwargs): d_info = _parse_deploy_info(node) deploy_key = kwargs.get('key') if d_info['deploy_key'] != deploy_key: raise exception.InvalidParameterValue(_("Deploy key does not" " match")) 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), 'pxe_config_path': pxe_utils.get_pxe_config_file_path(node.uuid), 'root_mb': 1024 * int(d_info['root_gb']), 'swap_mb': int(d_info['swap_mb']), 'ephemeral_mb': 1024 * int(d_info['ephemeral_gb']), 'preserve_ephemeral': d_info['preserve_ephemeral'], 'node_uuid': node.uuid, } missing = [key for key in params.keys() if params[key] is None] if missing: raise exception.InvalidParameterValue(_( "Parameters %s were not passed to ironic" " for deploy.") % missing) # ephemeral_format is nullable params['ephemeral_format'] = d_info.get('ephemeral_format') return params
def prepare(self, task): """Prepare the deployment environment for this task's node. Generates the TFTP configuration for PXE-booting both the deployment and user images, fetches the TFTP image from Glance and add it to the local cache. :param task: a TaskManager instance containing the node to act on. """ node = task.node # TODO(deva): optimize this if rerun on existing files if CONF.pxe.ipxe_enabled: # Copy the iPXE boot script to HTTP root directory bootfile_path = os.path.join(CONF.pxe.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) pxe_info = _get_image_info(node, task.context) pxe_options = _build_pxe_config_options(node, pxe_info, task.context) if deploy_utils.get_boot_mode_for_deploy(node) == 'uefi': pxe_config_template = CONF.pxe.uefi_pxe_config_template else: pxe_config_template = CONF.pxe.pxe_config_template pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) # FIXME(lucasagomes): If it's local boot we should not cache # the image kernel and ramdisk (Or even require it). _cache_ramdisk_kernel(task.context, node, pxe_info) # NOTE(deva): prepare may be called from conductor._do_takeover # in which case, it may need to regenerate the PXE config file for an # already-active deployment. if node.provision_state == states.ACTIVE: # this should have been stashed when the deploy was done # but let's guard, just in case it's missing iwdi = node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = node.driver_internal_info[ 'root_uuid_or_disk_id'] except KeyError: if not iwdi: LOG.warn(_LW("The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for node " "%(node)s"), {"node": node.uuid}) else: LOG.warn(_LW("The disk id for the whole disk image can't " "be found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s"), {"node": node.uuid}) else: pxe_config_path = pxe_utils.get_pxe_config_file_path( node.uuid) deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi)
def setUp(self): super(CleanUpFullFlowTestCase, self).setUp() self.config(image_cache_size=0, group='pxe') # Configure node mgr_utils.mock_the_extension_manager(driver="fake_pxe") instance_info = INST_INFO_DICT instance_info['deploy_key'] = 'fake-56789' self.node = obj_utils.create_test_node(self.context, driver='fake_pxe', instance_info=instance_info, driver_info=DRV_INFO_DICT) self.dbapi = dbapi.get_instance() self.port = obj_utils.create_test_port(self.context, node_id=self.node.id) # Configure temporary directories pxe_temp_dir = tempfile.mkdtemp() self.config(tftp_root=pxe_temp_dir, group='pxe') tftp_master_dir = os.path.join(CONF.pxe.tftp_root, 'tftp_master') self.config(tftp_master_path=tftp_master_dir, group='pxe') os.makedirs(tftp_master_dir) instance_temp_dir = tempfile.mkdtemp() self.config(images_path=instance_temp_dir, group='pxe') instance_master_dir = os.path.join(CONF.pxe.images_path, 'instance_master') self.config(instance_master_path=instance_master_dir, group='pxe') os.makedirs(instance_master_dir) self.pxe_config_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg') os.makedirs(self.pxe_config_dir) # Populate some file names self.master_kernel_path = os.path.join(CONF.pxe.tftp_master_path, 'kernel') self.master_instance_path = os.path.join(CONF.pxe.instance_master_path, 'image_uuid') self.node_tftp_dir = os.path.join(CONF.pxe.tftp_root, self.node.uuid) os.makedirs(self.node_tftp_dir) self.kernel_path = os.path.join(self.node_tftp_dir, 'kernel') self.node_image_dir = iscsi_deploy._get_image_dir_path(self.node.uuid) os.makedirs(self.node_image_dir) self.image_path = iscsi_deploy._get_image_file_path(self.node.uuid) self.config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) self.mac_path = pxe_utils._get_pxe_mac_path(self.port.address) self.token_path = pxe._get_token_file_path(self.node.uuid) # Create files self.files = [ self.config_path, self.master_kernel_path, self.master_instance_path, self.token_path ] for fname in self.files: # NOTE(dtantsur): files with 0 size won't be cleaned up with open(fname, 'w') as fp: fp.write('test') os.link(self.config_path, self.mac_path) os.link(self.master_kernel_path, self.kernel_path) os.link(self.master_instance_path, self.image_path)
def test_create_pxe_config_uefi_elilo(self, ensure_tree_mock, render_mock, write_mock, link_ip_configs_mock): self.config(uefi_pxe_config_template=('ironic/drivers/modules/' 'elilo_efi_pxe_config.template'), group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: task.node.properties['capabilities'] = 'boot_mode:uefi' pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.uefi_pxe_config_template) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')) ] ensure_tree_mock.assert_has_calls(ensure_calls) render_mock.assert_called_with( CONF.pxe.uefi_pxe_config_template, { 'pxe_options': self.pxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}' }) link_ip_configs_mock.assert_called_once_with(task, True) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_create_pxe_config_uefi_ipxe(self, ensure_tree_mock, render_mock, write_mock, link_mac_pxe_mock, chmod_mock): self.config(ipxe_enabled=True, group='pxe') ipxe_template = "ironic/drivers/modules/ipxe_config.template" with task_manager.acquire(self.context, self.node.uuid) as task: task.node.properties['capabilities'] = 'boot_mode:uefi' pxe_utils.create_pxe_config(task, self.ipxe_options, ipxe_template) ensure_calls = [ mock.call(os.path.join(CONF.deploy.http_root, self.node.uuid)), mock.call(os.path.join(CONF.deploy.http_root, 'pxelinux.cfg')), ] ensure_tree_mock.assert_has_calls(ensure_calls) chmod_mock.assert_not_called() render_mock.assert_called_with( ipxe_template, { 'pxe_options': self.ipxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}' }) link_mac_pxe_mock.assert_called_once_with(task) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_create_pxe_config_set_dir_permission(self, ensure_tree_mock, render_mock, write_mock, chmod_mock): self.config(dir_permission=0o755, group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) render_mock.assert_called_with( CONF.pxe.pxe_config_template, {'pxe_options': self.pxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'} ) node_dir = os.path.join(CONF.pxe.tftp_root, self.node.uuid) pxe_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg') ensure_calls = [ mock.call(node_dir), mock.call(pxe_dir), ] ensure_tree_mock.assert_has_calls(ensure_calls) chmod_calls = [mock.call(node_dir, 0o755), mock.call(pxe_dir, 0o755)] chmod_mock.assert_has_calls(chmod_calls) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_continue_deploy_good(self, mock_image_cache, mock_switch_config, notify_mock): token_path = self._create_token_file() self.node.power_state = states.POWER_ON self.node.provision_state = states.DEPLOYWAIT self.node.save() root_uuid = "12345678-1234-1234-1234-1234567890abcxyz" boot_mode = None def fake_deploy(**kwargs): return root_uuid self.useFixture(fixtures.MonkeyPatch( 'ironic.drivers.modules.deploy_utils.deploy', fake_deploy)) with task_manager.acquire(self.context, self.node.uuid) as task: task.driver.vendor.vendor_passthru( task, method='pass_deploy_info', address='123456', iqn='aaa-bbb', key='fake-56789') self.node.refresh(self.context) self.assertEqual(states.ACTIVE, self.node.provision_state) self.assertEqual(states.POWER_ON, self.node.power_state) self.assertIsNone(self.node.last_error) self.assertFalse(os.path.exists(token_path)) mock_image_cache.assert_called_once_with() mock_image_cache.return_value.clean_up.assert_called_once_with() pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) mock_switch_config.assert_called_once_with(pxe_config_path, root_uuid, boot_mode) notify_mock.assert_called_once_with('123456')
def test_create_pxe_config_set_dir_permission(self, ensure_tree_mock, render_mock, write_mock, chmod_mock): self.config(dir_permission=0o755, group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) render_mock.assert_called_with( CONF.pxe.pxe_config_template, { 'pxe_options': self.pxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}' }) node_dir = os.path.join(CONF.pxe.tftp_root, self.node.uuid) pxe_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg') ensure_calls = [ mock.call(node_dir), mock.call(pxe_dir), ] ensure_tree_mock.assert_has_calls(ensure_calls) chmod_calls = [mock.call(node_dir, 0o755), mock.call(pxe_dir, 0o755)] chmod_mock.assert_has_calls(chmod_calls) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_prepare_instance_netboot( self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, set_boot_device_mock): provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock image_info = {'kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk')} get_image_info_mock.return_value = image_info with task_manager.acquire(self.context, self.node.uuid) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) task.node.properties['capabilities'] = 'boot_mode:bios' task.node.driver_internal_info['root_uuid_or_disk_id'] = ( "30212642-09d3-467f-8e09-21685826ab50") task.node.driver_internal_info['is_whole_disk_image'] = False task.driver.boot.prepare_instance(task) get_image_info_mock.assert_called_once_with( task.node, task.context) cache_mock.assert_called_once_with( task.context, task.node, image_info) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) switch_pxe_config_mock.assert_called_once_with( pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", 'bios', False, False) set_boot_device_mock.assert_called_once_with(task, boot_devices.PXE)
def test_prepare_instance_localboot_with_fallback( self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, clean_up_pxe_config_mock, set_boot_device_mock): self.config(enable_netboot_fallback=True, group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: task.node.instance_info = task.node.instance_info task.node.instance_info['capabilities'] = {'boot_option': 'local'} task.node.driver_internal_info['root_uuid_or_disk_id'] = ( "30212642-09d3-467f-8e09-21685826ab50") task.node.driver_internal_info['is_whole_disk_image'] = False pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid, ipxe_enabled=True) task.driver.boot.prepare_instance(task) set_boot_device_mock.assert_called_once_with(task, boot_devices.DISK, persistent=True) switch_pxe_config_mock.assert_called_once_with( pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", 'bios', True, False, False, False, ipxe_enabled=True) # No clean up self.assertFalse(clean_up_pxe_config_mock.called) # No netboot configuration beyond the PXE files self.assertFalse(get_image_info_mock.called) self.assertFalse(cache_mock.called) self.assertFalse(dhcp_factory_mock.return_value.update_dhcp.called)
def test_create_pxe_config_uefi_grub(self, ensure_tree_mock, render_mock, write_mock, link_ip_configs_mock, chmod_mock): grub_tmplte = "ironic/drivers/modules/pxe_grub_config.template" with task_manager.acquire(self.context, self.node.uuid) as task: task.node.properties['capabilities'] = 'boot_mode:uefi' pxe_utils.create_pxe_config(task, self.pxe_options, grub_tmplte) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')), ] ensure_tree_mock.assert_has_calls(ensure_calls) chmod_mock.assert_not_called() render_mock.assert_called_with( grub_tmplte, { 'pxe_options': self.pxe_options, 'ROOT': '(( ROOT ))', 'DISK_IDENTIFIER': '(( DISK_IDENTIFIER ))' }) link_ip_configs_mock.assert_called_once_with(task, False) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def prepare(self, task): """Prepare the deployment environment for this task's node. Generates the TFTP configuration for PXE-booting both the deployment and user images, fetches the TFTP image from Glance and add it to the local cache. :param task: a TaskManager instance containing the node to act on. """ node = task.node # TODO(deva): optimize this if rerun on existing files if CONF.pxe.ipxe_enabled: # Copy the iPXE boot script to HTTP root directory bootfile_path = os.path.join( CONF.pxe.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) pxe_info = _get_image_info(node, task.context) pxe_options = _build_pxe_config_options(node, pxe_info, task.context) if deploy_utils.get_boot_mode_for_deploy(node) == 'uefi': pxe_config_template = CONF.pxe.uefi_pxe_config_template else: pxe_config_template = CONF.pxe.pxe_config_template pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) # FIXME(lucasagomes): If it's local boot we should not cache # the image kernel and ramdisk (Or even require it). _cache_ramdisk_kernel(task.context, node, pxe_info) # NOTE(deva): prepare may be called from conductor._do_takeover # in which case, it may need to regenerate the PXE config file for an # already-active deployment. if node.provision_state == states.ACTIVE: # this should have been stashed when the deploy was done # but let's guard, just in case it's missing iwdi = node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = ( node.driver_internal_info['root_uuid_or_disk_id']) except KeyError: if not iwdi: LOG.warn( _LW("The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for node " "%(node)s"), {"node": node.uuid}) else: LOG.warn( _LW("The disk id for the whole disk image can't " "be found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s"), {"node": node.uuid}) else: pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node))
def _get_deploy_info(self, node, **kwargs): d_info = _parse_deploy_info(node) deploy_key = kwargs.get('key') if d_info['deploy_key'] != deploy_key: raise exception.InvalidParameterValue( _("Deploy key does not" " match")) 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), 'pxe_config_path': pxe_utils.get_pxe_config_file_path(node.uuid), 'root_mb': 1024 * int(d_info['root_gb']), 'swap_mb': int(d_info['swap_mb']), 'ephemeral_mb': 1024 * int(d_info['ephemeral_gb']), 'preserve_ephemeral': d_info['preserve_ephemeral'], 'node_uuid': node.uuid, } missing = [key for key in params.keys() if params[key] is None] if missing: raise exception.InvalidParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) # ephemeral_format is nullable params['ephemeral_format'] = d_info.get('ephemeral_format') return params
def _continue_deploy(self, task, **kwargs): """Continues the deployment of baremetal node over iSCSI. This method continues the deployment of the baremetal node over iSCSI from where the deployment ramdisk has left off. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs for performing iscsi deployment. :raises: InvalidState """ node = task.node task.process_event('resume') _destroy_token_file(node) root_uuid = iscsi_deploy.continue_deploy(task, **kwargs) if not root_uuid: return 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) task.process_event('done') except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg)
def test_create_pxe_config_uefi_ipxe(self, ensure_tree_mock, render_mock, write_mock, link_mac_pxe_mock, chmod_mock): self.config(ipxe_enabled=True, group='pxe') ipxe_template = "ironic/drivers/modules/ipxe_config.template" with task_manager.acquire(self.context, self.node.uuid) as task: task.node.properties['capabilities'] = 'boot_mode:uefi' pxe_utils.create_pxe_config(task, self.ipxe_options, ipxe_template) ensure_calls = [ mock.call(os.path.join(CONF.deploy.http_root, self.node.uuid)), mock.call(os.path.join(CONF.deploy.http_root, 'pxelinux.cfg')), ] ensure_tree_mock.assert_has_calls(ensure_calls) chmod_mock.assert_not_called() render_mock.assert_called_with( ipxe_template, {'pxe_options': self.ipxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'}) link_mac_pxe_mock.assert_called_once_with(task) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def pass_deploy_info(self, task, **kwargs): """Continues the deployment of baremetal node over iSCSI. This method continues the deployment of the baremetal node over iSCSI from where the deployment ramdisk has left off. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs for performing iscsi deployment. :raises: InvalidState """ node = task.node task.process_event('resume') _destroy_token_file(node) is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] uuid_dict = iscsi_deploy.continue_deploy(task, **kwargs) root_uuid_or_disk_id = uuid_dict.get( 'root uuid', uuid_dict.get('disk identifier')) # save the node's root disk UUID so that another conductor could # rebuild the PXE config file. Due to a shortcoming in Nova objects, # we have to assign to node.driver_internal_info so the node knows it # has changed. driver_internal_info = node.driver_internal_info driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id node.driver_internal_info = driver_internal_info node.save() try: if iscsi_deploy.get_boot_option(node) == "local": deploy_utils.try_set_boot_device(task, boot_devices.DISK) # If it's going to boot from the local disk, get rid of # the PXE configuration files used for the deployment pxe_utils.clean_up_pxe_config(task) # Ask the ramdisk to install bootloader and # wait for the call-back through the vendor passthru # 'pass_bootloader_install_info', if it's not a # whole disk image. if not is_whole_disk_image: deploy_utils.notify_ramdisk_to_proceed(kwargs['address']) task.process_event('wait') return else: pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) boot_mode = deploy_utils.get_boot_mode_for_deploy(node) deploy_utils.switch_pxe_config(pxe_config_path, root_uuid_or_disk_id, boot_mode, is_whole_disk_image) except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg) else: iscsi_deploy.finish_deploy(task, kwargs.get('address'))
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. :param task: a task from TaskManager. :returns: None """ node = task.node boot_option = deploy_utils.get_boot_option(node) if boot_option != "local": # Make sure that the instance kernel/ramdisk is cached. # This is for the takeover scenario for active nodes. instance_image_info = _get_instance_image_info( task.node, task.context) _cache_ramdisk_kernel(task.context, task.node, instance_image_info) # If it's going to PXE boot we need to update the DHCP server dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) iwdi = task.node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = task.node.driver_internal_info[ 'root_uuid_or_disk_id' ] except KeyError: if not iwdi: LOG.warn(_LW("The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s"), {"node": task.node.uuid}) else: LOG.warn(_LW("The disk id for the whole disk image can't " "be found, unable to switch the pxe config " "from deployment mode to service (boot) mode " "for node %(node)s"), {"node": task.node.uuid}) else: pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node)) else: # If it's going to boot from the local disk, we don't need # PXE config files. They still need to be generated as part # of the prepare() because the deployment does PXE boot the # deploy ramdisk pxe_utils.clean_up_pxe_config(task)
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. :param task: a task from TaskManager. :returns: None """ node = task.node boot_option = deploy_utils.get_boot_option(node) if boot_option != "local": # Make sure that the instance kernel/ramdisk is cached. # This is for the takeover scenario for active nodes. instance_image_info = _get_instance_image_info( task.node, task.context) _cache_ramdisk_kernel(task.context, task.node, instance_image_info) # If it's going to PXE boot we need to update the DHCP server dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) iwdi = task.node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = task.node.driver_internal_info[ 'root_uuid_or_disk_id'] except KeyError: if not iwdi: LOG.warn( _LW("The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s"), {"node": task.node.uuid}) else: LOG.warn( _LW("The disk id for the whole disk image can't " "be found, unable to switch the pxe config " "from deployment mode to service (boot) mode " "for node %(node)s"), {"node": task.node.uuid}) else: pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node)) else: # If it's going to boot from the local disk, we don't need # PXE config files. They still need to be generated as part # of the prepare() because the deployment does PXE boot the # deploy ramdisk pxe_utils.clean_up_pxe_config(task)
def test_prepare_instance_netboot_iscsi(self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, set_boot_device_mock, create_pxe_config_mock): http_url = 'http://192.1.2.3:1234' self.config(http_url=http_url, group='deploy') provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock vol_id = uuidutils.generate_uuid() obj_utils.create_test_volume_target(self.context, node_id=self.node.id, volume_type='iscsi', boot_index=0, volume_id='1234', uuid=vol_id, properties={ 'target_lun': 0, 'target_portal': 'fake_host:3260', 'target_iqn': 'fake_iqn', 'auth_username': '******', 'auth_password': '******' }) with task_manager.acquire(self.context, self.node.uuid) as task: task.node.driver_internal_info = {'boot_from_volume': vol_id} dhcp_opts = pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True) dhcp_opts += pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True, ip_version=6) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid, ipxe_enabled=True) task.node.properties['capabilities'] = 'boot_mode:bios' task.driver.boot.prepare_instance(task) self.assertFalse(get_image_info_mock.called) self.assertFalse(cache_mock.called) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) create_pxe_config_mock.assert_called_once_with( task, mock.ANY, CONF.pxe.pxe_config_template, ipxe_enabled=True) switch_pxe_config_mock.assert_called_once_with( pxe_config_path, None, boot_modes.LEGACY_BIOS, False, ipxe_enabled=True, iscsi_boot=True, ramdisk_boot=False) set_boot_device_mock.assert_called_once_with(task, boot_devices.PXE, persistent=True)
def _test_continue_deploy(self, is_localboot, mock_image_cache, mock_switch_config, notify_mock, mock_node_boot_dev, mock_clean_pxe): token_path = self._create_token_file() # set local boot if is_localboot: i_info = self.node.instance_info i_info['capabilities'] = '{"boot_option": "local"}' self.node.instance_info = i_info self.node.power_state = states.POWER_ON self.node.provision_state = states.DEPLOYWAIT self.node.target_provision_state = states.ACTIVE self.node.save() root_uuid = "12345678-1234-1234-1234-1234567890abcxyz" boot_mode = None def fake_deploy(**kwargs): return root_uuid self.useFixture( fixtures.MonkeyPatch('ironic.drivers.modules.deploy_utils.deploy', fake_deploy)) with task_manager.acquire(self.context, self.node.uuid) as task: task.driver.vendor._continue_deploy(task, address='123456', iqn='aaa-bbb', key='fake-56789') self.node.refresh() self.assertEqual(states.ACTIVE, self.node.provision_state) self.assertEqual(states.NOSTATE, self.node.target_provision_state) self.assertEqual(states.POWER_ON, self.node.power_state) self.assertIn('root_uuid', self.node.driver_internal_info) self.assertIsNone(self.node.last_error) self.assertFalse(os.path.exists(token_path)) mock_image_cache.assert_called_once_with() mock_image_cache.return_value.clean_up.assert_called_once_with() pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) notify_mock.assert_called_once_with('123456') if is_localboot: mock_node_boot_dev.assert_called_once_with(mock.ANY, boot_devices.DISK, persistent=True) mock_clean_pxe.assert_called_once_with(mock.ANY) self.assertFalse(mock_switch_config.called) else: mock_switch_config.assert_called_once_with(pxe_config_path, root_uuid, boot_mode) self.assertFalse(mock_node_boot_dev.called) self.assertFalse(mock_clean_pxe.called)
def test_prepare_instance_netboot_active(self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, set_boot_device_mock, create_pxe_config_mock, isfile_mock): provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock image_info = { 'kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk') } instance_info = {"boot_option": "netboot"} get_image_info_mock.return_value = image_info self.node.provision_state = states.ACTIVE self.node.save() with task_manager.acquire(self.context, self.node.uuid) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True) dhcp_opts += pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True, ip_version=6) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid, ipxe_enabled=True) task.node.properties['capabilities'] = 'boot_mode:bios' task.node.instance_info['capabilities'] = instance_info task.node.driver_internal_info['root_uuid_or_disk_id'] = ( "30212642-09d3-467f-8e09-21685826ab50") task.node.driver_internal_info['is_whole_disk_image'] = False task.driver.boot.prepare_instance(task) get_image_info_mock.assert_called_once_with(task, ipxe_enabled=True) cache_mock.assert_called_once_with(task, image_info, ipxe_enabled=True) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) create_pxe_config_mock.assert_called_once_with( task, mock.ANY, CONF.pxe.pxe_config_template, ipxe_enabled=True) switch_pxe_config_mock.assert_called_once_with( pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", 'bios', False, False, False, False, ipxe_enabled=True) self.assertFalse(set_boot_device_mock.called)
def test_create_pxe_config(self, ensure_tree_mock, build_mock, write_mock): build_mock.return_value = self.pxe_options with task_manager.acquire(self.context, self.node.uuid) as task: pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) build_mock.assert_called_with(self.pxe_options, CONF.pxe.pxe_config_template) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, "pxelinux.cfg")), ] ensure_tree_mock.has_calls(ensure_calls) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options)
def _test_continue_deploy(self, is_localboot, mock_image_cache, mock_switch_config, notify_mock, mock_node_boot_dev, mock_clean_pxe): token_path = self._create_token_file() # set local boot if is_localboot: i_info = self.node.instance_info i_info['capabilities'] = '{"boot_option": "local"}' self.node.instance_info = i_info self.node.power_state = states.POWER_ON self.node.provision_state = states.DEPLOYWAIT self.node.target_provision_state = states.ACTIVE self.node.save() root_uuid = "12345678-1234-1234-1234-1234567890abcxyz" boot_mode = None def fake_deploy(**kwargs): return root_uuid self.useFixture(fixtures.MonkeyPatch( 'ironic.drivers.modules.deploy_utils.deploy', fake_deploy)) with task_manager.acquire(self.context, self.node.uuid) as task: task.driver.vendor._continue_deploy( task, address='123456', iqn='aaa-bbb', key='fake-56789') self.node.refresh() self.assertEqual(states.ACTIVE, self.node.provision_state) self.assertEqual(states.NOSTATE, self.node.target_provision_state) self.assertEqual(states.POWER_ON, self.node.power_state) self.assertIsNone(self.node.last_error) self.assertFalse(os.path.exists(token_path)) mock_image_cache.assert_called_once_with() mock_image_cache.return_value.clean_up.assert_called_once_with() pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) notify_mock.assert_called_once_with('123456') if is_localboot: mock_node_boot_dev.assert_called_once_with( mock.ANY, boot_devices.DISK, persistent=True) mock_clean_pxe.assert_called_once_with(mock.ANY) self.assertFalse(mock_switch_config.called) else: mock_switch_config.assert_called_once_with( pxe_config_path, root_uuid, boot_mode) self.assertFalse(mock_node_boot_dev.called) self.assertFalse(mock_clean_pxe.called)
def _continue_deploy(self, task, **kwargs): """Continues the deployment of baremetal node over iSCSI. This method continues the deployment of the baremetal node over iSCSI from where the deployment ramdisk has left off. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs for performing iscsi deployment. :raises: InvalidState """ node = task.node task.process_event('resume') _destroy_token_file(node) root_uuid = iscsi_deploy.continue_deploy(task, **kwargs) if not root_uuid: return # save the node's root disk UUID so that another conductor could # rebuild the PXE config file. Due to a shortcoming in Nova objects, # we have to assign to node.driver_internal_info so the node knows it # has changed. driver_internal_info = node.driver_internal_info driver_internal_info['root_uuid'] = root_uuid node.driver_internal_info = driver_internal_info node.save() try: if iscsi_deploy.get_boot_option(node) == "local": try_set_boot_device(task, boot_devices.DISK) # If it's going to boot from the local disk, get rid of # the PXE configuration files used for the deployment pxe_utils.clean_up_pxe_config(task) else: 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) task.process_event('done') except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg)
def test_create_pxe_config(self, ensure_tree_mock, build_mock, write_mock): build_mock.return_value = self.pxe_options with task_manager.acquire(self.context, self.node.uuid) as task: pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) build_mock.assert_called_with(self.pxe_options, CONF.pxe.pxe_config_template) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')) ] ensure_tree_mock.assert_has_calls(ensure_calls) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options)
def continue_deploy(self, task, **kwargs): """Method invoked when deployed with the IPA ramdisk. This method is invoked during a heartbeat from an agent when the node is in wait-call-back state. This deploys the image on the node and then configures the node to boot according to the desired boot option (netboot or localboot). :param task: a TaskManager object containing the node. :param kwargs: the kwargs passed from the heartbeat method. :raises: InstanceDeployFailure, if it encounters some error during the deploy. """ task.process_event('resume') node = task.node LOG.debug('Continuing the deployment on node %s', node.uuid) # NOTE(lucasagomes): We don't use the token file with the agent, # but as it's created as part of deploy() we are going to remove # it here. _destroy_token_file(node) uuid_dict = iscsi_deploy.do_agent_iscsi_deploy(task, self._client) is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if iscsi_deploy.get_boot_option(node) == "local": # Install the boot loader root_uuid = uuid_dict.get('root uuid') efi_sys_uuid = uuid_dict.get('efi system partition uuid') self.configure_local_boot(task, root_uuid=root_uuid, efi_system_part_uuid=efi_sys_uuid) # If it's going to boot from the local disk, get rid of # the PXE configuration files used for the deployment pxe_utils.clean_up_pxe_config(task) else: root_uuid_or_disk_id = uuid_dict.get( 'root uuid', uuid_dict.get('disk identifier')) pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) boot_mode = deploy_utils.get_boot_mode_for_deploy(node) deploy_utils.switch_pxe_config(pxe_config_path, root_uuid_or_disk_id, boot_mode, is_whole_disk_image) self.reboot_and_finish_deploy(task)
def continue_deploy(self, task, **kwargs): """Method invoked when deployed with the IPA ramdisk. This method is invoked during a heartbeat from an agent when the node is in wait-call-back state. This deploys the image on the node and then configures the node to boot according to the desired boot option (netboot or localboot). :param task: a TaskManager object containing the node. :param kwargs: the kwargs passed from the heartbeat method. :raises: InstanceDeployFailure, if it encounters some error during the deploy. """ task.process_event('resume') node = task.node LOG.debug('Continuing the deployment on node %s', node.uuid) # NOTE(lucasagomes): We don't use the token file with the agent, # but as it's created as part of deploy() we are going to remove # it here. _destroy_token_file(node) uuid_dict = iscsi_deploy.do_agent_iscsi_deploy(task, self._client) is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if iscsi_deploy.get_boot_option(node) == "local": # Install the boot loader root_uuid = uuid_dict.get('root uuid') efi_sys_uuid = uuid_dict.get('efi system partition uuid') self.configure_local_boot( task, root_uuid=root_uuid, efi_system_part_uuid=efi_sys_uuid) # If it's going to boot from the local disk, get rid of # the PXE configuration files used for the deployment pxe_utils.clean_up_pxe_config(task) else: root_uuid_or_disk_id = uuid_dict.get( 'root uuid', uuid_dict.get('disk identifier')) pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) boot_mode = deploy_utils.get_boot_mode_for_deploy(node) deploy_utils.switch_pxe_config(pxe_config_path, root_uuid_or_disk_id, boot_mode, is_whole_disk_image) self.reboot_and_finish_deploy(task)
def _link_mac_pxe_configs(self, task): def create_link(mac_path): ironic_utils.unlink_without_raise(mac_path) relative_source_path = os.path.relpath(pxe_config_file_path, os.path.dirname(mac_path)) utils.create_link_without_raise(relative_source_path, mac_path) pxe_config_file_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) pxe_boot_interface_mac = self._get_boot_interface_mac(task) LOG.info("pxe_boot_interface_mac: %s", pxe_boot_interface_mac) for port in task.ports: LOG.info("port.address: %s", port.address) if port.address == pxe_boot_interface_mac: client_id = port.extra.get('client-id') create_link( self._get_pxe_mac_path(port.address, client_id=client_id))
def _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id): node = task.node pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) # NOTE(pas-ha) if it is takeover of ACTIVE node, # first ensure that basic PXE configs and links # are in place before switching pxe config if (node.provision_state == states.ACTIVE and not os.path.isfile(pxe_config_path)): pxe_options = _build_pxe_config_options(task, instance_image_info, service=True) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) iwdi = node.driver_internal_info.get('is_whole_disk_image') deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node))
def _continue_deploy(self, task, **kwargs): """Continues the deployment of baremetal node over iSCSI. This method continues the deployment of the baremetal node over iSCSI from where the deployment ramdisk has left off. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs for performing iscsi deployment. """ node = task.node 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) if not root_uuid: return 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 node.target_provision_state = states.NOSTATE node.save() except Exception as e: LOG.error( _LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), { 'instance': node.instance_uuid, 'error': e }) msg = _('Failed to continue iSCSI deployment.') iscsi_deploy.set_failed_state(task, msg)
def _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id): node = task.node pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) # NOTE(pas-ha) if it is takeover of ACTIVE node, # first ensure that basic PXE configs and links # are in place before switching pxe config if (node.provision_state == states.ACTIVE and not os.path.isfile(pxe_config_path)): pxe_options = _build_pxe_config_options(task, instance_image_info, service=True) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) iwdi = node.driver_internal_info.get('is_whole_disk_image') deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node), deploy_utils.is_iscsi_boot(task))
def continue_deploy(self, task, **kwargs): """Method invoked when deployed with the IPA ramdisk. This method is invoked during a heartbeat from an agent when the node is in wait-call-back state. This deploys the image on the node and then configures the node to boot according to the desired boot option (netboot or localboot). :param task: a TaskManager object containing the node. :param kwargs: the kwargs passed from the heartbeat method. :raises: InstanceDeployFailure, if it encounters some error during the deploy. """ task.process_event("resume") node = task.node LOG.debug("Continuing the deployment on node %s", node.uuid) uuid_dict = iscsi_deploy.do_agent_iscsi_deploy(task, self._client) is_whole_disk_image = node.driver_internal_info["is_whole_disk_image"] if iscsi_deploy.get_boot_option(node) == "local": # Install the boot loader root_uuid = uuid_dict.get("root uuid") efi_sys_uuid = uuid_dict.get("efi system partition uuid") self.configure_local_boot(task, root_uuid=root_uuid, efi_system_part_uuid=efi_sys_uuid) # If it's going to boot from the local disk, get rid of # the PXE configuration files used for the deployment pxe_utils.clean_up_pxe_config(task) else: root_uuid_or_disk_id = uuid_dict.get("root uuid", uuid_dict.get("disk identifier")) pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) boot_mode = deploy_utils.get_boot_mode_for_deploy(node) deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, boot_mode, is_whole_disk_image, deploy_utils.is_trusted_boot_requested(node), ) self.reboot_and_finish_deploy(task)
def _create_pxe_config(self, task): pxe_options = self._build_pxe_options(task.node) pxe_config_template = CONF.pxe.pxe_config_template node_uuid = task.node.uuid root_dir = CONF.pxe.tftp_root fileutils.ensure_tree(os.path.join(root_dir, node_uuid)) fileutils.ensure_tree(os.path.join(root_dir, PXE_CFG_DIR_NAME)) pxe_config_file_path = pxe_utils.get_pxe_config_file_path(node_uuid) tmpl_path, tmpl_file = os.path.split(pxe_config_template) env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path)) template = env.get_template(tmpl_file) pxe_config = template.render({ 'pxe_options': pxe_options, 'server_ip': CONF.my_ip, 'UUID': node_uuid, }) utils.write_to_file(pxe_config_file_path, pxe_config) self._link_mac_pxe_configs(task)
def clean_up(self, task): extra_info = task.node.extra pxe_boot_interface_mac = extra_info.get('boot_detailed').get( 'pxe_interface') pxe_boot_interface_mac.replace('-', ':') for port in task.ports: if port.address == pxe_boot_interface_mac: client_id = port.extra.get('client-id') ironic_utils.unlink_without_raise( self._get_pxe_mac_path(port.address, client_id=client_id)) pxe_config_file_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) fileutils.delete_if_exists(pxe_config_file_path) if os.path.exists(os.path.join(CONF.pxe.tftp_root, task.node.uuid)): rmtree(os.path.join(CONF.pxe.tftp_root, task.node.uuid)) auto_file_name = task.node.uuid + '_auto.cfg' fileutils.delete_if_exists(AUTO_FILE_DIR + auto_file_name)
def test_create_pxe_config(self, ensure_tree_mock, render_mock, write_mock): with task_manager.acquire(self.context, self.node.uuid) as task: pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) render_mock.assert_called_with( CONF.pxe.pxe_config_template, { 'pxe_options': self.pxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}' }) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')) ] ensure_tree_mock.assert_has_calls(ensure_calls) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_create_pxe_config_existing_dirs(self, ensure_tree_mock, render_mock, write_mock, chmod_mock, isdir_mock): self.config(dir_permission=0o755, group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: isdir_mock.return_value = True pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) render_mock.assert_called_with( CONF.pxe.pxe_config_template, { 'pxe_options': self.pxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}' }) ensure_tree_mock.assert_has_calls([]) chmod_mock.assert_not_called() isdir_mock.assert_has_calls([]) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_create_pxe_config_existing_dirs(self, ensure_tree_mock, render_mock, write_mock, chmod_mock, isdir_mock): self.config(dir_permission=0o755, group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: isdir_mock.return_value = True pxe_utils.create_pxe_config(task, self.pxe_options, CONF.pxe.pxe_config_template) render_mock.assert_called_with( CONF.pxe.pxe_config_template, {'pxe_options': self.pxe_options, 'ROOT': '{{ ROOT }}', 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}'} ) ensure_tree_mock.assert_has_calls([]) chmod_mock.assert_not_called() isdir_mock.assert_has_calls([]) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, render_mock.return_value)
def test_create_pxe_config_uefi_elilo(self, ensure_tree_mock, build_mock, write_mock, link_ip_configs_mock): build_mock.return_value = self.pxe_options_uefi with task_manager.acquire(self.context, self.node.uuid) as task: task.node.properties['capabilities'] = 'boot_mode:uefi' pxe_utils.create_pxe_config(task, self.pxe_options_uefi, CONF.pxe.uefi_pxe_config_template) ensure_calls = [ mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')) ] ensure_tree_mock.assert_has_calls(ensure_calls) build_mock.assert_called_with(self.pxe_options_uefi, CONF.pxe.uefi_pxe_config_template, '{{ ROOT }}', '{{ DISK_IDENTIFIER }}') link_ip_configs_mock.assert_called_once_with(task, True) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options_uefi)
def _continue_deploy(self, task, **kwargs): """Continues the deployment of baremetal node over iSCSI. This method continues the deployment of the baremetal node over iSCSI from where the deployment ramdisk has left off. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs for performing iscsi deployment. """ node = task.node 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) if not root_uuid: return 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 node.target_provision_state = states.NOSTATE node.save() except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') iscsi_deploy.set_failed_state(task, msg)
def _continue_deploy(self, task, **kwargs): """Continues the deployment of baremetal node over iSCSI. This method continues the deployment of the baremetal node over iSCSI from where the deployment ramdisk has left off. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs for performing iscsi deployment. :raises: InvalidState """ node = task.node task.process_event('resume') _destroy_token_file(node) root_uuid = iscsi_deploy.continue_deploy(task, **kwargs) if not root_uuid: return try: if iscsi_deploy.get_boot_option(node) == "local": try_set_boot_device(task, boot_devices.DISK) # If it's going to boot from the local disk, get rid of # the PXE configuration files used for the deployment pxe_utils.clean_up_pxe_config(task) else: 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) task.process_event('done') except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg)
def test_prepare_instance_netboot_iscsi( self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, set_boot_device_mock, create_pxe_config_mock): http_url = 'http://192.1.2.3:1234' self.config(ipxe_enabled=False, group='pxe') self.config(http_url=http_url, group='deploy') provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock vol_id = uuidutils.generate_uuid() obj_utils.create_test_volume_target( self.context, node_id=self.node.id, volume_type='iscsi', boot_index=0, volume_id='1234', uuid=vol_id, properties={'target_lun': 0, 'target_portal': 'fake_host:3260', 'target_iqn': 'fake_iqn', 'auth_username': '******', 'auth_password': '******'}) with task_manager.acquire(self.context, self.node.uuid) as task: task.node.driver_internal_info = { 'boot_from_volume': vol_id} dhcp_opts = pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) task.node.properties['capabilities'] = 'boot_mode:bios' task.driver.boot.prepare_instance(task) self.assertFalse(get_image_info_mock.called) self.assertFalse(cache_mock.called) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) create_pxe_config_mock.assert_called_once_with( task, mock.ANY, CONF.pxe.pxe_config_template, ipxe_enabled=True) switch_pxe_config_mock.assert_called_once_with( pxe_config_path, None, boot_modes.LEGACY_BIOS, False, ipxe_enabled=True, iscsi_boot=True, ramdisk_boot=False) set_boot_device_mock.assert_called_once_with(task, boot_devices.PXE, persistent=True)
def test_prepare_instance_netboot_ramdisk( self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, set_boot_device_mock, create_pxe_config_mock): http_url = 'http://192.1.2.3:1234' self.config(http_url=http_url, group='deploy') provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock self.node.instance_info = {'boot_iso': 'http://1.2.3.4:1234/boot.iso', 'capabilities': {'boot_option': 'ramdisk'}} image_info = {'kernel': ('', '/path/to/kernel'), 'deploy_kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk'), 'deploy_ramdisk': ('', '/path/to/ramdisk')} get_image_info_mock.return_value = image_info self.node.provision_state = states.DEPLOYING self.node.save() with task_manager.acquire(self.context, self.node.uuid) as task: print(task.node) dhcp_opts = pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True) dhcp_opts += pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=True, ip_version=6) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid, ipxe_enabled=True) task.driver.boot.prepare_instance(task) self.assertTrue(get_image_info_mock.called) self.assertTrue(cache_mock.called) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) create_pxe_config_mock.assert_called_once_with( task, mock.ANY, CONF.pxe.ipxe_config_template, ipxe_enabled=True) switch_pxe_config_mock.assert_called_once_with( pxe_config_path, None, boot_modes.LEGACY_BIOS, False, ipxe_enabled=True, iscsi_boot=False, ramdisk_boot=True) set_boot_device_mock.assert_called_once_with(task, boot_devices.PXE, persistent=True)
def test_get_pxe_config_file_path(self): self.assertEqual( os.path.join(CONF.pxe.tftp_root, self.node.uuid, 'config'), pxe_utils.get_pxe_config_file_path(self.node.uuid))
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. :param task: a task from TaskManager. :returns: None """ node = task.node boot_option = deploy_utils.get_boot_option(node) boot_device = None if deploy_utils.is_iscsi_boot(task): dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) # configure iPXE for iscsi boot pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) if not os.path.isfile(pxe_config_path): pxe_options = _build_pxe_config_options(task, {}) pxe_config_template = ( deploy_utils.get_pxe_config_template(node)) pxe_utils.create_pxe_config( task, pxe_options, pxe_config_template) deploy_utils.switch_pxe_config( pxe_config_path, None, deploy_utils.get_boot_mode_for_deploy(node), False, iscsi_boot=True) boot_device = boot_devices.PXE elif boot_option != "local": if task.driver.storage.should_write_image(task): # Make sure that the instance kernel/ramdisk is cached. # This is for the takeover scenario for active nodes. instance_image_info = _get_instance_image_info( task.node, task.context) _cache_ramdisk_kernel(task.context, task.node, instance_image_info) # If it's going to PXE boot we need to update the DHCP server dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) iwdi = task.node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = task.node.driver_internal_info[ 'root_uuid_or_disk_id' ] except KeyError: if not task.driver.storage.should_write_image(task): pass elif not iwdi: LOG.warning("The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s", {"node": task.node.uuid}) else: LOG.warning("The disk id for the whole disk image can't " "be found, unable to switch the pxe config " "from deployment mode to service (boot) mode " "for node %(node)s", {"node": task.node.uuid}) else: _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id) boot_device = boot_devices.PXE else: # If it's going to boot from the local disk, we don't need # PXE config files. They still need to be generated as part # of the prepare() because the deployment does PXE boot the # deploy ramdisk pxe_utils.clean_up_pxe_config(task) boot_device = boot_devices.DISK # NOTE(pas-ha) do not re-set boot device on ACTIVE nodes # during takeover if boot_device and task.node.provision_state != states.ACTIVE: manager_utils.node_set_boot_device(task, boot_device, persistent=True)
def setUp(self): super(CleanUpFullFlowTestCase, self).setUp() self.config(image_cache_size=0, group='pxe') # Configure node mgr_utils.mock_the_extension_manager(driver="fake_pxe") instance_info = INST_INFO_DICT instance_info['deploy_key'] = 'fake-56789' self.node = obj_utils.create_test_node(self.context, driver='fake_pxe', instance_info=instance_info, driver_info=DRV_INFO_DICT) self.port = obj_utils.create_test_port(self.context, node_id=self.node.id) # Configure temporary directories pxe_temp_dir = tempfile.mkdtemp() self.config(tftp_root=pxe_temp_dir, group='pxe') tftp_master_dir = os.path.join(CONF.pxe.tftp_root, 'tftp_master') self.config(tftp_master_path=tftp_master_dir, group='pxe') os.makedirs(tftp_master_dir) instance_temp_dir = tempfile.mkdtemp() self.config(images_path=instance_temp_dir, group='pxe') instance_master_dir = os.path.join(CONF.pxe.images_path, 'instance_master') self.config(instance_master_path=instance_master_dir, group='pxe') os.makedirs(instance_master_dir) self.pxe_config_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg') os.makedirs(self.pxe_config_dir) # Populate some file names self.master_kernel_path = os.path.join(CONF.pxe.tftp_master_path, 'kernel') self.master_instance_path = os.path.join(CONF.pxe.instance_master_path, 'image_uuid') self.node_tftp_dir = os.path.join(CONF.pxe.tftp_root, self.node.uuid) os.makedirs(self.node_tftp_dir) self.kernel_path = os.path.join(self.node_tftp_dir, 'kernel') self.node_image_dir = iscsi_deploy._get_image_dir_path(self.node.uuid) os.makedirs(self.node_image_dir) self.image_path = iscsi_deploy._get_image_file_path(self.node.uuid) self.config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) self.mac_path = pxe_utils._get_pxe_mac_path(self.port.address) self.token_path = pxe._get_token_file_path(self.node.uuid) # Create files self.files = [self.config_path, self.master_kernel_path, self.master_instance_path, self.token_path] for fname in self.files: # NOTE(dtantsur): files with 0 size won't be cleaned up with open(fname, 'w') as fp: fp.write('test') os.link(self.config_path, self.mac_path) os.link(self.master_kernel_path, self.kernel_path) os.link(self.master_instance_path, self.image_path)
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. :param task: a task from TaskManager. :returns: None """ node = task.node boot_option = deploy_utils.get_boot_option(node) boot_device = None if deploy_utils.is_iscsi_boot(task): dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) # configure iPXE for iscsi boot pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) if not os.path.isfile(pxe_config_path): pxe_options = _build_pxe_config_options(task, {}) pxe_config_template = ( deploy_utils.get_pxe_config_template(node)) pxe_utils.create_pxe_config( task, pxe_options, pxe_config_template) deploy_utils.switch_pxe_config( pxe_config_path, None, deploy_utils.get_boot_mode_for_deploy(node), False, iscsi_boot=True) boot_device = boot_devices.PXE elif boot_option != "local": if task.driver.storage.should_write_image(task): # Make sure that the instance kernel/ramdisk is cached. # This is for the takeover scenario for active nodes. instance_image_info = _get_instance_image_info( task.node, task.context) _cache_ramdisk_kernel(task.context, task.node, instance_image_info) # If it's going to PXE boot we need to update the DHCP server dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) iwdi = task.node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = task.node.driver_internal_info[ 'root_uuid_or_disk_id' ] except KeyError: if not task.driver.storage.should_write_image(task): pass elif not iwdi: LOG.warning("The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s", {"node": task.node.uuid}) else: LOG.warning("The disk id for the whole disk image can't " "be found, unable to switch the pxe config " "from deployment mode to service (boot) mode " "for node %(node)s. Booting the instance " "from disk.", {"node": task.node.uuid}) pxe_utils.clean_up_pxe_config(task) boot_device = boot_devices.DISK else: _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id) boot_device = boot_devices.PXE else: # If it's going to boot from the local disk, we don't need # PXE config files. They still need to be generated as part # of the prepare() because the deployment does PXE boot the # deploy ramdisk pxe_utils.clean_up_pxe_config(task) boot_device = boot_devices.DISK # NOTE(pas-ha) do not re-set boot device on ACTIVE nodes # during takeover if boot_device and task.node.provision_state != states.ACTIVE: manager_utils.node_set_boot_device(task, boot_device, persistent=True)
def test_get_pxe_config_file_path(self): self.assertEqual(os.path.join(CONF.pxe.tftp_root, self.node.uuid, 'config'), pxe_utils.get_pxe_config_file_path(self.node.uuid))