From f66d9d37c0faee3e2dd690deeb92a3d5c85b5d5b Mon Sep 17 00:00:00 2001 From: Ramakrishnan G Date: Thu, 4 Jun 2015 02:24:35 -0700 Subject: [PATCH] Add vendor-passthru to attach and boot an ISO This commit adds a new vendor-passthru 'boot_into_iso' in iscsi_ilo driver to attach and boot from an ISO located in Glance or an HTTP(s) URL. This vendor passthru can be invoked only when the node is in manage state or in maintenance mode. This vendor-passthru can help in debugging issues with bare metal node. Partial-Bug: 1464168 Change-Id: I75c5363b1df13b12c8f025a4b45de02367229f8f --- ironic/drivers/modules/ilo/deploy.py | 38 +++++++++++++++ ironic/tests/drivers/ilo/test_deploy.py | 62 +++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py index b97a029493..99162c38bb 100644 --- a/ironic/drivers/modules/ilo/deploy.py +++ b/ironic/drivers/modules/ilo/deploy.py @@ -730,6 +730,44 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor): iscsi_deploy.get_deploy_info(task.node, **kwargs) elif method == 'pass_bootloader_install_info': iscsi_deploy.validate_pass_bootloader_info_input(task, kwargs) + elif method == 'boot_into_iso': + self._validate_boot_into_iso(task, kwargs) + + def _validate_boot_into_iso(self, task, kwargs): + """Validates if attach_iso can be called and if inputs are proper.""" + if not (task.node.provision_state == states.MANAGEABLE or + task.node.maintenance is True): + msg = (_("The requested action 'boot_into_iso' can be performed " + "only when node %(node_uuid)s is in %(state)s state or " + "in 'maintenance' mode") % + {'node_uuid': task.node.uuid, + 'state': states.MANAGEABLE}) + raise exception.InvalidStateRequested(msg) + d_info = {'boot_iso_href': kwargs.get('boot_iso_href')} + error_msg = _("Error validating input for boot_into_iso vendor " + "passthru. Some parameters were not provided: ") + deploy_utils.check_for_missing_params(d_info, error_msg) + iscsi_deploy.validate_image_properties( + task.context, {'image_source': kwargs.get('boot_iso_href')}, []) + + @base.passthru(['POST']) + @task_manager.require_exclusive_lock + def boot_into_iso(self, task, **kwargs): + """Attaches an ISO image in glance and reboots bare metal. + + This method accepts an ISO image href (a Glance UUID or an HTTP(S) URL) + attaches it as virtual media and then reboots the node. This is + useful for debugging purposes. This can be invoked only when the node + is in manage state. + + :param task: A TaskManager object. + :param kwargs: The arguments sent with vendor passthru. The expected + kwargs are:: + + 'boot_iso_href': href of the image to be booted. This can be + a Glance UUID or an HTTP(S) URL. + """ + _reboot_into(task, kwargs['boot_iso_href'], ramdisk_options=None) def _configure_vmedia_boot(self, task, root_uuid): """Configure vmedia boot for the node.""" diff --git a/ironic/tests/drivers/ilo/test_deploy.py b/ironic/tests/drivers/ilo/test_deploy.py index c5a01af434..8d5924ef8c 100644 --- a/ironic/tests/drivers/ilo/test_deploy.py +++ b/ironic/tests/drivers/ilo/test_deploy.py @@ -1238,6 +1238,68 @@ class VendorPassthruTestCase(db_base.DbTestCase): reboot_and_finish_deploy_mock.assert_called_once_with( mock.ANY, task) + @mock.patch.object(ilo_deploy, '_reboot_into', spec_set=True, + autospec=True) + def test_boot_into_iso(self, reboot_into_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.vendor.boot_into_iso(task, boot_iso_href='foo') + reboot_into_mock.assert_called_once_with(task, 'foo', + ramdisk_options=None) + + @mock.patch.object(ilo_deploy.VendorPassthru, '_validate_boot_into_iso', + spec_set=True, autospec=True) + def test_validate_boot_into_iso(self, validate_boot_into_iso_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + vendor = ilo_deploy.VendorPassthru() + vendor.validate(task, method='boot_into_iso', foo='bar') + validate_boot_into_iso_mock.assert_called_once_with( + vendor, task, {'foo': 'bar'}) + + def test__validate_boot_into_iso_invalid_state(self): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.provision_state = states.AVAILABLE + self.assertRaises( + exception.InvalidStateRequested, + task.driver.vendor._validate_boot_into_iso, + task, {}) + + def test__validate_boot_into_iso_missing_boot_iso_href(self): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.provision_state = states.MANAGEABLE + self.assertRaises( + exception.MissingParameterValue, + task.driver.vendor._validate_boot_into_iso, + task, {}) + + @mock.patch.object(iscsi_deploy, 'validate_image_properties', + spec_set=True, autospec=True) + def test__validate_boot_into_iso_manage(self, validate_image_prop_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + info = {'boot_iso_href': 'foo'} + task.node.provision_state = states.MANAGEABLE + task.driver.vendor._validate_boot_into_iso( + task, info) + validate_image_prop_mock.assert_called_once_with( + task.context, {'image_source': 'foo'}, []) + + @mock.patch.object(iscsi_deploy, 'validate_image_properties', + spec_set=True, autospec=True) + def test__validate_boot_into_iso_maintenance( + self, validate_image_prop_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + info = {'boot_iso_href': 'foo'} + task.node.maintenance = True + task.driver.vendor._validate_boot_into_iso( + task, info) + validate_image_prop_mock.assert_called_once_with( + task.context, {'image_source': 'foo'}, []) + class IloPXEDeployTestCase(db_base.DbTestCase):