From f3c774a96b963d66c5397069d12cf9eb2dcac4da Mon Sep 17 00:00:00 2001 From: Hironori Shiina Date: Wed, 14 Dec 2016 12:51:27 +0000 Subject: [PATCH] Ironic: Add soft reboot support to ironic driver This patch gets Ironic virt driver to support soft reboot. Ironic API supports soft reboot since API version 1.27. The API version has already been bumped to 1.28. Change-Id: I4e61ebf852f61e3c0a511b49f0304d3138ef022e Implements: blueprint soft-reboot-poweroff Co-Authored-By: Tang Chen Co-Authored-By: xiexs --- nova/tests/unit/virt/ironic/test_driver.py | 35 ++++++++++++++++++- nova/tests/unit/virt/ironic/utils.py | 2 +- nova/virt/ironic/driver.py | 31 +++++++++++----- ...soft-reboot-poweroff-203e0f33e3b8042e.yaml | 6 ++++ 4 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/bp-soft-reboot-poweroff-203e0f33e3b8042e.yaml diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py index 71039f1e491a..4472f1ee9d7e 100644 --- a/nova/tests/unit/virt/ironic/test_driver.py +++ b/nova/tests/unit/virt/ironic/test_driver.py @@ -1336,9 +1336,42 @@ class IronicDriverTestCase(test.NoDBTestCase): mock_looping.return_value = fake_looping_call instance = fake_instance.fake_instance_obj(self.ctx, node=node.uuid) - self.driver.reboot(self.ctx, instance, None, None) + self.driver.reboot(self.ctx, instance, None, 'HARD') mock_sp.assert_called_once_with(node.uuid, 'reboot') + @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') + @mock.patch.object(ironic_driver.IronicDriver, + '_validate_instance_and_node') + @mock.patch.object(FAKE_CLIENT.node, 'set_power_state') + def test_reboot_soft(self, mock_sp, fake_validate, mock_looping): + node = ironic_utils.get_test_node() + fake_validate.side_effect = [node, node] + + fake_looping_call = FakeLoopingCall() + mock_looping.return_value = fake_looping_call + instance = fake_instance.fake_instance_obj(self.ctx, + node=node.uuid) + self.driver.reboot(self.ctx, instance, None, 'SOFT') + mock_sp.assert_called_once_with(node.uuid, 'reboot', soft=True) + + @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') + @mock.patch.object(ironic_driver.IronicDriver, + '_validate_instance_and_node') + @mock.patch.object(FAKE_CLIENT.node, 'set_power_state') + def test_reboot_soft_not_supported(self, mock_sp, fake_validate, + mock_looping): + node = ironic_utils.get_test_node() + fake_validate.side_effect = [node, node] + mock_sp.side_effect = [ironic_exception.BadRequest(), None] + + fake_looping_call = FakeLoopingCall() + mock_looping.return_value = fake_looping_call + instance = fake_instance.fake_instance_obj(self.ctx, + node=node.uuid) + self.driver.reboot(self.ctx, instance, None, 'SOFT') + mock_sp.assert_has_calls([mock.call(node.uuid, 'reboot', soft=True), + mock.call(node.uuid, 'reboot')]) + @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') @mock.patch.object(ironic_driver.IronicDriver, '_validate_instance_and_node') diff --git a/nova/tests/unit/virt/ironic/utils.py b/nova/tests/unit/virt/ironic/utils.py index 83206005540a..e184d2edc420 100644 --- a/nova/tests/unit/virt/ironic/utils.py +++ b/nova/tests/unit/virt/ironic/utils.py @@ -147,7 +147,7 @@ class FakeNodeClient(object): def list_ports(self, node_uuid, detail=False): pass - def set_power_state(self, node_uuid, target): + def set_power_state(self, node_uuid, target, soft=False): pass def set_provision_state(self, node_uuid, target): diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py index c3261bb0a1f5..280e4e82f9f4 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py @@ -993,8 +993,6 @@ class IronicDriver(virt_driver.ComputeDriver): block_device_info=None, bad_volumes_callback=None): """Reboot the specified instance. - NOTE: Ironic does not support soft-off, so this method - always performs a hard-reboot. NOTE: Unlike the libvirt driver, this method does not delete and recreate the instance; it preserves local state. @@ -1002,23 +1000,40 @@ class IronicDriver(virt_driver.ComputeDriver): :param instance: The instance object. :param network_info: Instance network information. Ignored by this driver. - :param reboot_type: Either a HARD or SOFT reboot. Ignored by - this driver. + :param reboot_type: Either a HARD or SOFT reboot. :param block_device_info: Info pertaining to attached volumes. Ignored by this driver. :param bad_volumes_callback: Function to handle any bad volumes encountered. Ignored by this driver. """ - LOG.debug('Reboot called for instance', instance=instance) + LOG.debug('Reboot(type %s) called for instance', + reboot_type, instance=instance) node = self._validate_instance_and_node(instance) - self.ironicclient.call("node.set_power_state", node.uuid, 'reboot') + + hard = True + if reboot_type == 'SOFT': + try: + self.ironicclient.call("node.set_power_state", node.uuid, + 'reboot', soft=True) + hard = False + except ironic.exc.BadRequest as exc: + LOG.info(_LI('Soft reboot is not supported by ironic hardware ' + 'driver. Falling back to hard reboot: %s'), + exc, + instance=instance) + + if hard: + self.ironicclient.call("node.set_power_state", node.uuid, 'reboot') timer = loopingcall.FixedIntervalLoopingCall( self._wait_for_power_state, instance, 'reboot') timer.start(interval=CONF.ironic.api_retry_interval).wait() - LOG.info(_LI('Successfully rebooted Ironic node %s'), - node.uuid, instance=instance) + LOG.info(_LI('Successfully rebooted(type %(type)s) Ironic node ' + '%(node)s'), + {'type': ('HARD' if hard else 'SOFT'), + 'node': node.uuid}, + instance=instance) def power_off(self, instance, timeout=0, retry_interval=0): """Power off the specified instance. diff --git a/releasenotes/notes/bp-soft-reboot-poweroff-203e0f33e3b8042e.yaml b/releasenotes/notes/bp-soft-reboot-poweroff-203e0f33e3b8042e.yaml new file mode 100644 index 000000000000..c5c644072224 --- /dev/null +++ b/releasenotes/notes/bp-soft-reboot-poweroff-203e0f33e3b8042e.yaml @@ -0,0 +1,6 @@ +--- +features: + - Adds soft reboot support to Ironic virt driver. If hardware driver in + Ironic doesn't support soft reboot, hard reboot is tried. This feature + requires the Ironic service to support API version 1.27 or later. It also + requires python-ironicclient >= 1.10.0.