def test_dhcp_options_for_instance_ipxe(self): self.config(tftp_server='192.0.2.1', group='pxe') self.config(pxe_bootfile_name='fake-bootfile', group='pxe') self.config(ipxe_enabled=True, group='pxe') self.config(http_url='http://192.0.3.2:1234', group='pxe') self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe') self.config(dhcp_provider='isc', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{'opt_name': '!175,bootfile-name', 'opt_value': 'fake-bootfile'}, {'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1'}, {'opt_name': 'tftp-server', 'opt_value': '192.0.2.1'}, {'opt_name': 'bootfile-name', 'opt_value': expected_boot_script_url}] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertItemsEqual(expected_info, pxe_utils.dhcp_options_for_instance(task)) self.config(dhcp_provider='neutron', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{'opt_name': 'tag:!ipxe,bootfile-name', 'opt_value': 'fake-bootfile'}, {'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1'}, {'opt_name': 'tftp-server', 'opt_value': '192.0.2.1'}, {'opt_name': 'tag:ipxe,bootfile-name', 'opt_value': expected_boot_script_url}] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertItemsEqual(expected_info, pxe_utils.dhcp_options_for_instance(task))
def test_prepare_instance_netboot_missing_root_uuid( 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 instance_info = {"boot_option": "netboot"} with task_manager.acquire(self.context, self.node.uuid) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True, ip_version=4) dhcp_opts += pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True, ip_version=6) task.node.properties['capabilities'] = 'boot_mode:bios' task.node.instance_info['capabilities'] = instance_info 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) self.assertFalse(switch_pxe_config_mock.called) self.assertFalse(set_boot_device_mock.called)
def test_prepare_instance_whole_disk_image_missing_root_uuid( self, get_image_info_mock, cache_mock, dhcp_factory_mock, set_boot_device_mock, clean_up_pxe_mock, log_mock): provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock get_image_info_mock.return_value = {} instance_info = {"boot_option": "netboot"} 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) task.node.properties['capabilities'] = 'boot_mode:bios' task.node.instance_info['capabilities'] = instance_info task.node.driver_internal_info['is_whole_disk_image'] = True 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, {}, ipxe_enabled=True) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) self.assertTrue(log_mock.called) clean_up_pxe_mock.assert_called_once_with(task, ipxe_enabled=True) set_boot_device_mock.assert_called_once_with(task, boot_devices.DISK, persistent=True)
def _dhcp_options_for_instance_ipxe(self, task, boot_file): self.config(tftp_server='192.0.2.1', group='pxe') self.config(ipxe_enabled=True, group='pxe') self.config(http_url='http://192.0.3.2:1234', group='deploy') self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe') self.config(dhcp_provider='isc', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{ 'opt_name': '!175,67', 'opt_value': boot_file, 'ip_version': 4 }, { 'opt_name': '66', 'opt_value': '192.0.2.1', 'ip_version': 4 }, { 'opt_name': '150', 'opt_value': '192.0.2.1', 'ip_version': 4 }, { 'opt_name': '67', 'opt_value': expected_boot_script_url, 'ip_version': 4 }, { 'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1', 'ip_version': 4 }] self.assertItemsEqual(expected_info, pxe_utils.dhcp_options_for_instance(task)) self.config(dhcp_provider='neutron', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{ 'opt_name': 'tag:!ipxe,67', 'opt_value': boot_file, 'ip_version': 4 }, { 'opt_name': '66', 'opt_value': '192.0.2.1', 'ip_version': 4 }, { 'opt_name': '150', 'opt_value': '192.0.2.1', 'ip_version': 4 }, { 'opt_name': 'tag:ipxe,67', 'opt_value': expected_boot_script_url, 'ip_version': 4 }, { 'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1', 'ip_version': 4 }] self.assertItemsEqual(expected_info, pxe_utils.dhcp_options_for_instance(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_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_update_neutron(self, mock_gnvi, mock_updo, mock_wait_neutron): opts = pxe_utils.dhcp_options_for_instance() mock_gnvi.return_value = {'port-uuid': 'vif-uuid'} with task_manager.acquire(self.context, self.node.uuid) as task: neutron.update_neutron(task, self.node) mock_updo.assertCalleOnceWith('vif-uuid', opts) mock_wait_neutron.assert_called_once_with(task)
def test_update_dhcp(self, mock_gnvi, mock_updo): mock_gnvi.return_value = {"port-uuid": "vif-uuid"} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_updo.assert_called_once_with("vif-uuid", opts, token=self.context.auth_token)
def test_deploy( self, mock_node_set_boot, mock_node_power_action, mock_update_neutron, mock_cache_instance_image, mock_get_image_file_path, mock_get_image_mb, ): fake_img_path = "/test/path/test.img" mock_get_image_file_path.return_value = fake_img_path mock_get_image_mb.return_value = 1 dhcp_opts = pxe_utils.dhcp_options_for_instance() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: state = task.driver.deploy.deploy(task) self.assertEqual(state, states.DEPLOYWAIT) mock_cache_instance_image.assert_called_once_with(self.context, task.node) mock_get_image_file_path.assert_called_once_with(task.node.uuid) mock_get_image_mb.assert_called_once_with(fake_img_path) mock_update_neutron.assert_called_once_with(task, dhcp_opts) mock_node_set_boot.assert_called_once_with(task, "pxe", persistent=True) mock_node_power_action.assert_called_once_with(task, states.REBOOT) # ensure token file created t_path = pxe._get_token_file_path(self.node.uuid) token = open(t_path, "r").read() self.assertEqual(self.context.auth_token, token)
def test_update_dhcp(self, mock_gnvi, mock_updo): mock_gnvi.return_value = {'port-uuid': 'vif-uuid'} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory(token=self.context.auth_token) api.update_dhcp(task, self.node) mock_updo.assertCalleOnceWith('vif-uuid', opts)
def _test_prepare_ramdisk(self, mock_pxe_config, mock_build_pxe, mock_cache_r_k, mock_deploy_img_info, mock_instance_img_info, dhcp_factory_mock, uefi=False, cleaning=False): mock_build_pxe.return_value = {} mock_deploy_img_info.return_value = {'deploy_kernel': 'a'} mock_instance_img_info.return_value = {'kernel': 'b'} mock_pxe_config.return_value = None mock_cache_r_k.return_value = None provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock with task_manager.acquire(self.context, self.node.uuid) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'}) mock_deploy_img_info.assert_called_once_with(task.node) provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) if cleaning is False: mock_cache_r_k.assert_called_once_with( self.context, task.node, {'deploy_kernel': 'a', 'kernel': 'b'}) mock_instance_img_info.assert_called_once_with(task.node, self.context) else: mock_cache_r_k.assert_called_once_with( self.context, task.node, {'deploy_kernel': 'a'}) if uefi: mock_pxe_config.assert_called_once_with( task, {'foo': 'bar'}, CONF.pxe.uefi_pxe_config_template) else: mock_pxe_config.assert_called_once_with( task, {'foo': 'bar'}, CONF.pxe.pxe_config_template)
def _dhcp_options_for_instance(self, ip_version=4): self.config(ip_version=ip_version, group='pxe') self.config(tftp_server='192.0.2.1', group='pxe') self.config(pxe_bootfile_name='fake-bootfile', group='pxe') self.config(tftp_root='/tftp-path/', group='pxe') expected_info = [{ 'opt_name': '67', 'opt_value': 'fake-bootfile', 'ip_version': ip_version }, { 'opt_name': '210', 'opt_value': '/tftp-path/', 'ip_version': ip_version }, { 'opt_name': '66', 'opt_value': '192.0.2.1', 'ip_version': ip_version }, { 'opt_name': '150', 'opt_value': '192.0.2.1', 'ip_version': ip_version }, { 'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1', 'ip_version': ip_version }] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance(task))
def test_deploy(self, mock_node_set_boot, mock_node_power_action, mock_update_dhcp, mock_cache_instance_image, mock_get_image_file_path, mock_get_image_mb, mock_expire): fake_img_path = '/test/path/test.img' mock_get_image_file_path.return_value = fake_img_path mock_get_image_mb.return_value = 1 mock_expire.return_value = False self.config(deploy_callback_timeout=600, group='conductor') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) state = task.driver.deploy.deploy(task) self.assertEqual(state, states.DEPLOYWAIT) mock_cache_instance_image.assert_called_once_with( self.context, task.node) mock_get_image_file_path.assert_called_once_with(task.node.uuid) mock_get_image_mb.assert_called_once_with(fake_img_path) mock_update_dhcp.assert_called_once_with(task, dhcp_opts) mock_expire.assert_called_once_with(self.context.auth_token, 600) mock_node_set_boot.assert_called_once_with(task, 'pxe', persistent=True) mock_node_power_action.assert_called_once_with(task, states.REBOOT) # ensure token file created t_path = pxe._get_token_file_path(self.node.uuid) token = open(t_path, 'r').read() self.assertEqual(self.context.auth_token, token)
def deploy(self, task): """Start deployment of the task's node'. Fetches instance image, creates a temporary keystone token file, updates the Neutron DHCP port options for next boot, and issues a reboot request to the power driver. This causes the node to boot into the deployment ramdisk and triggers the next phase of PXE-based deployment via VendorPassthru._continue_deploy(). :param task: a TaskManager instance containing the node to act on. :returns: deploy state DEPLOYING. """ _cache_instance_image(task.context, task.node) _check_image_size(task) # TODO(yuriyz): more secure way needed for pass auth token # to deploy ramdisk _create_token_file(task) dhcp_opts = pxe_utils.dhcp_options_for_instance() neutron.update_neutron(task, dhcp_opts) manager_utils.node_set_boot_device(task, 'pxe', persistent=True) manager_utils.node_power_action(task, states.REBOOT) return states.DEPLOYWAIT
def _plug_provisioning(self, task, **kwargs): LOG.debug("Plugging the provisioning!") if task.node.power_state != states.POWER_ON: manager_utils.node_power_action(task, states.REBOOT) client = neutron._build_client(task.context.auth_token) port = client.create_port({ 'port': { "network_id": CONF.neutron.cleaning_network_uuid, "extra_dhcp_opts": pxe_utils.dhcp_options_for_instance(task), } }) name = port['port']['id'] network = client.show_network(port['port']['network_id']) seg_id = network['network']['provider:segmentation_id'] try: common.add_vnic( task, name, port['port']['mac_address'], seg_id, True) except imcsdk.ImcException: client.delete_port(name) raise new_port = objects.Port( task.context, node_id=task.node.id, address=port['port']['mac_address'], extra={"vif_port_id": port['port']['id'], "type": "deploy", "state": "ACTIVE"}) new_port.create() return port['port']['fixed_ips'][0]['ip_address']
def deploy(self, task): """Start deployment of the task's node'. Fetches instance image, creates a temporary keystone token file, updates the DHCP port options for next boot, and issues a reboot request to the power driver. This causes the node to boot into the deployment ramdisk and triggers the next phase of PXE-based deployment via VendorPassthru.pass_deploy_info(). :param task: a TaskManager instance containing the node to act on. :returns: deploy state DEPLOYWAIT. """ iscsi_deploy.cache_instance_image(task.context, task.node) iscsi_deploy.check_image_size(task) # TODO(yuriyz): more secure way needed for pass auth token # to deploy ramdisk _create_token_file(task) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) deploy_utils.try_set_boot_device(task, boot_devices.PXE) manager_utils.node_power_action(task, states.REBOOT) return states.DEPLOYWAIT
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_take_over(self, update_dhcp_mock): with task_manager.acquire( self.context, self.node.uuid, shared=True) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) task.driver.deploy.take_over(task) update_dhcp_mock.assert_called_once_with( task, dhcp_opts)
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 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.warning( _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.warning( _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: _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: deploy_utils.try_set_boot_device(task, boot_device)
def test_update_neutron(self, mock_gnvi, mock_updo, mock_wait_neutron): opts = pxe_utils.dhcp_options_for_instance() mock_gnvi.return_value = {"port-uuid": "vif-uuid"} with task_manager.acquire(self.context, self.node.uuid) as task: neutron.update_neutron(task, self.node) mock_updo.assertCalleOnceWith("vif-uuid", opts) mock_wait_neutron.assert_called_once_with(task)
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 prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node if CONF.pxe.ipxe_enabled: # NOTE(mjturek): At this point, the ipxe boot script should # already exist as it is created at startup time. However, we # call the boot script create method here to assert its # existence and handle the unlikely case that it wasn't created # or was deleted. pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) persistent = strutils.bool_from_string( node.driver_info.get('force_persistent_boot_device', False)) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: pxe_info.pop('deploy_kernel', None) pxe_info.pop('deploy_ramdisk', None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
def prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node if CONF.pxe.ipxe_enabled: # Copy the iPXE boot script to HTTP root directory bootfile_path = os.path.join( CONF.deploy.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) if (not os.path.isfile(bootfile_path) or not filecmp.cmp(CONF.pxe.ipxe_boot_script, bootfile_path)): shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) 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) deploy_utils.try_set_boot_device(task, boot_devices.PXE) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: pxe_info.pop('deploy_kernel', None) pxe_info.pop('deploy_ramdisk', None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
def test_take_over_localboot(self, update_dhcp_mock, clean_pxe_mock): with task_manager.acquire( self.context, self.node.uuid, shared=True) as task: task.node.instance_info['capabilities'] = {"boot_option": "local"} dhcp_opts = pxe_utils.dhcp_options_for_instance(task) task.driver.deploy.take_over(task) update_dhcp_mock.assert_called_once_with( task, dhcp_opts) clean_pxe_mock.assert_called_once_with(task)
def test_update_dhcp(self, mock_gnvi, mock_updo): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_updo.assert_called_once_with('vif-uuid', opts)
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_dhcp_options_for_instance_ipxe(self): self.config(tftp_server='192.0.2.1', group='pxe') self.config(pxe_bootfile_name='fake-bootfile', group='pxe') self.config(ipxe_enabled=True, group='pxe') self.config(http_url='http://192.0.3.2:1234', group='pxe') self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe') self.config(dhcp_provider='isc', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{ 'opt_name': '!175,bootfile-name', 'opt_value': 'fake-bootfile' }, { 'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1' }, { 'opt_name': 'tftp-server', 'opt_value': '192.0.2.1' }, { 'opt_name': 'bootfile-name', 'opt_value': expected_boot_script_url }] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertEqual(sorted(expected_info), sorted(pxe_utils.dhcp_options_for_instance(task))) self.config(dhcp_provider='neutron', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{ 'opt_name': 'tag:!ipxe,bootfile-name', 'opt_value': 'fake-bootfile' }, { 'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1' }, { 'opt_name': 'tftp-server', 'opt_value': '192.0.2.1' }, { 'opt_name': 'bootfile-name', 'opt_value': expected_boot_script_url }] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertEqual(sorted(expected_info), sorted(pxe_utils.dhcp_options_for_instance(task)))
def prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ 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.deploy.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) if deploy_utils.get_boot_mode_for_deploy(node) == 'uefi': pxe_config_template = CONF.pxe.uefi_pxe_config_template else: pxe_config_template = _get_pxe_conf_option(task, 'pxe_config_template') pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) deploy_utils.try_set_boot_device(task, boot_devices.PXE) # 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)
def test_update_dhcp(self, mock_gnvi, mock_updo): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts, context=task.context)
def test_deploy(self, power_mock, bootdev_mock, dhcp_mock): with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) driver_return = self.driver.deploy(task) self.assertEqual(driver_return, states.DEPLOYWAIT) dhcp_mock.assert_called_once_with(task, dhcp_opts, None) bootdev_mock.assert_called_once_with(task, 'pxe', persistent=True) power_mock.assert_called_once_with(task, states.REBOOT)
def test_dhcp_options_for_instance(self): self.config(tftp_server="192.0.2.1", group="pxe") self.config(pxe_bootfile_name="fake-bootfile", group="pxe") expected_info = [ {"opt_name": "bootfile-name", "opt_value": "fake-bootfile"}, {"opt_name": "server-ip-address", "opt_value": "192.0.2.1"}, {"opt_name": "tftp-server", "opt_value": "192.0.2.1"}, ] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance(task))
def test_deploy(self, power_mock, bootdev_mock, dhcp_mock): with task_manager.acquire( self.context, self.node['uuid'], shared=False) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) driver_return = self.driver.deploy(task) self.assertEqual(driver_return, states.DEPLOYWAIT) dhcp_mock.assert_called_once_with(mock.ANY, task, dhcp_opts, None) bootdev_mock.assert_called_once_with(task, 'pxe', persistent=True) power_mock.assert_called_once_with(task, states.REBOOT)
def take_over(self, task): dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) if iscsi_deploy.get_boot_option(task.node) == "local": # 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_dhcp_options_for_instance(self): self.config(tftp_server='192.0.2.1', group='pxe') self.config(pxe_bootfile_name='fake-bootfile', group='pxe') expected_info = [{'opt_name': 'bootfile-name', 'opt_value': 'fake-bootfile'}, {'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1'}, {'opt_name': 'tftp-server', 'opt_value': '192.0.2.1'} ] self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance())
def _dhcp_options_for_instance_ipxe(self, task, boot_file): self.config(tftp_server='192.0.2.1', group='pxe') self.config(ipxe_enabled=True, group='pxe') self.config(http_url='http://192.0.3.2:1234', group='deploy') self.config(ipxe_boot_script='/test/boot.ipxe', group='pxe') self.config(dhcp_provider='isc', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{'opt_name': '!175,bootfile-name', 'opt_value': boot_file, 'ip_version': 4}, {'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1', 'ip_version': 4}, {'opt_name': 'tftp-server', 'opt_value': '192.0.2.1', 'ip_version': 4}, {'opt_name': 'bootfile-name', 'opt_value': expected_boot_script_url, 'ip_version': 4}] self.assertItemsEqual(expected_info, pxe_utils.dhcp_options_for_instance(task)) self.config(dhcp_provider='neutron', group='dhcp') expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe' expected_info = [{'opt_name': 'tag:!ipxe,bootfile-name', 'opt_value': boot_file, 'ip_version': 4}, {'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1', 'ip_version': 4}, {'opt_name': 'tftp-server', 'opt_value': '192.0.2.1', 'ip_version': 4}, {'opt_name': 'tag:ipxe,bootfile-name', 'opt_value': expected_boot_script_url, 'ip_version': 4}] self.assertItemsEqual(expected_info, pxe_utils.dhcp_options_for_instance(task))
def _do_pxe_boot(task, ports=None): """Reboot the node into the PXE ramdisk. :param task: a TaskManager instance :param ports: a list of Neutron port dicts to update DHCP options on. If None, will get the list of ports from the Ironic port objects. """ dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts, ports) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=True) manager_utils.node_power_action(task, states.REBOOT)
def test_take_over(self, update_dhcp_mock, clean_pxe_mock): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: dhcp_opts = pxe_utils.dhcp_options_for_instance(task) task.driver.deploy.take_over(task) # Assert we update the DHCP server update_dhcp_mock.assert_called_once_with(task, dhcp_opts) # Assert we don't clean the PXE config files in # case it's not local boot self.assertFalse(clean_pxe_mock.called)
def take_over(self, task): if not iscsi_deploy.get_boot_option(task.node) == "local": # 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) 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_update_dhcp_unset_sleep_and_fake(self, mock_gnvi, mock_updo, mock_log): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_log.debug.assert_not_called() mock_log.warning.assert_not_called() mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts, token=self.context.auth_token)
def test_update_dhcp_unset_sleep_and_fake(self, mock_gnvi, mock_log): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() with mock.patch.object(api.provider, 'update_port_dhcp_opts', autospec=True) as mock_updo: api.update_dhcp(task, opts) mock_log.debug.assert_not_called() mock_updo.assert_called_once_with('vif-uuid', opts, context=task.context)
def test_dhcp_options_for_instance(self): self.config(tftp_server='192.0.2.1', group='pxe') self.config(pxe_bootfile_name='fake-bootfile', group='pxe') expected_info = [{'opt_name': 'bootfile-name', 'opt_value': 'fake-bootfile'}, {'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1'}, {'opt_name': 'tftp-server', 'opt_value': '192.0.2.1'} ] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance(task))
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_update_dhcp_set_sleep_and_ssh(self, mock_gnvi, mock_updo, mock_ts): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} self.config(port_setup_delay=30, group='neutron') with task_manager.acquire(self.context, self.node.uuid) as task: task.driver.power = ssh.SSHPower() opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_ts.assert_called_with(30) mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts, token=self.context.auth_token)
def test_dhcp_options_for_instance(self): self.config(tftp_server='192.0.2.1', group='pxe') self.config(pxe_bootfile_name='fake-bootfile', group='pxe') expected_info = [{ 'opt_name': 'bootfile-name', 'opt_value': 'fake-bootfile' }, { 'opt_name': 'server-ip-address', 'opt_value': '192.0.2.1' }, { 'opt_name': 'tftp-server', 'opt_value': '192.0.2.1' }] self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance())
def test_update_dhcp_set_sleep_and_fake(self, mock_gnvi, mock_updo, mock_ts, mock_log): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} self.config(port_setup_delay=30, group='neutron') with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_log.debug.assert_called_once_with( "Waiting %d seconds for Neutron.", 30) mock_log.warning.assert_not_called() mock_ts.assert_called_with(30) mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts, token=self.context.auth_token)
def test_dhcp_options_for_instance_ipxe(self): self.config(tftp_server="192.0.2.1", group="pxe") self.config(pxe_bootfile_name="fake-bootfile", group="pxe") self.config(ipxe_enabled=True, group="pxe") self.config(http_url="http://192.0.3.2:1234", group="pxe") self.config(ipxe_boot_script="/test/boot.ipxe", group="pxe") expected_boot_script_url = "http://192.0.3.2:1234/boot.ipxe" expected_info = [ {"opt_name": "!175,bootfile-name", "opt_value": "fake-bootfile"}, {"opt_name": "server-ip-address", "opt_value": "192.0.2.1"}, {"opt_name": "tftp-server", "opt_value": "192.0.2.1"}, {"opt_name": "bootfile-name", "opt_value": expected_boot_script_url}, ] with task_manager.acquire(self.context, self.node.uuid) as task: self.assertEqual(sorted(expected_info), sorted(pxe_utils.dhcp_options_for_instance(task)))
def test_update_dhcp_unset_sleep_and_ssh(self, mock_gnvi, mock_updo, mock_ts, mock_log): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) task.driver.power = ssh.SSHPower() api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) self.assertTrue(mock_log.warning.called) self.assertIn('Setting the port delay to 15 for SSH', mock_log.warning.call_args[0][0]) mock_ts.assert_called_with(15) mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts, token=self.context.auth_token)