Ironic: Add soft power off support to Ironic driver.

This patch introduces soft power off support to Ironic virt driver.

Change-Id: Ie81d5c6e7f12cc5371ca3475daab09aa7d92d645
Implements: blueprint soft-reboot-poweroff
Co-Authored-By: Tang Chen <chen.tang@easystack.cn>
Co-Authored-By: xiexs <xiexs@cn.fujitsu.com>
This commit is contained in:
Hironori Shiina 2016-12-14 14:32:10 +00:00
parent f3c774a96b
commit cf02e5161d
4 changed files with 105 additions and 29 deletions

View File

@ -1372,24 +1372,11 @@ class IronicDriverTestCase(test.NoDBTestCase):
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')
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
def test_power_off(self, mock_sp, fake_validate, mock_looping):
self._test_power_on_off(mock_sp, fake_validate, mock_looping,
method_name='power_off')
@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_power_on(self, mock_sp, fake_validate, mock_looping):
self._test_power_on_off(mock_sp, fake_validate, mock_looping,
method_name='power_on')
def _test_power_on_off(self, mock_sp, fake_validate, mock_looping,
method_name=None):
node = ironic_utils.get_test_node()
fake_validate.side_effect = [node, node]
@ -1397,14 +1384,65 @@ class IronicDriverTestCase(test.NoDBTestCase):
mock_looping.return_value = fake_looping_call
instance = fake_instance.fake_instance_obj(self.ctx,
node=self.instance_uuid)
# Call the method under test here
if method_name == 'power_on':
self.driver.power_on(self.ctx, instance,
utils.get_test_network_info())
mock_sp.assert_called_once_with(node.uuid, 'on')
elif method_name == 'power_off':
self.driver.power_off(instance)
mock_sp.assert_called_once_with(node.uuid, 'off')
self.driver.power_on(self.ctx, instance,
utils.get_test_network_info())
mock_sp.assert_called_once_with(node.uuid, 'on')
@mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
def _test_power_off(self, mock_looping, timeout=0):
fake_looping_call = FakeLoopingCall()
mock_looping.return_value = fake_looping_call
instance = fake_instance.fake_instance_obj(self.ctx,
node=self.instance_uuid)
self.driver.power_off(instance, timeout)
@mock.patch.object(ironic_driver.IronicDriver,
'_validate_instance_and_node')
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
def test_power_off(self, mock_sp, fake_validate):
node = ironic_utils.get_test_node()
fake_validate.side_effect = [node, node]
self._test_power_off()
mock_sp.assert_called_once_with(node.uuid, 'off')
@mock.patch.object(ironic_driver.IronicDriver,
'_validate_instance_and_node')
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
def test_power_off_soft(self, mock_sp, fake_validate):
node = ironic_utils.get_test_node()
power_off_node = ironic_utils.get_test_node(
power_state=ironic_states.POWER_OFF)
fake_validate.side_effect = [node, power_off_node]
self._test_power_off(timeout=30)
mock_sp.assert_called_once_with(node.uuid, 'off', soft=True,
timeout=30)
@mock.patch.object(ironic_driver.IronicDriver,
'_validate_instance_and_node')
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
def test_power_off_soft_exception(self, mock_sp, fake_validate):
node = ironic_utils.get_test_node()
fake_validate.side_effect = [node, node]
mock_sp.side_effect = [ironic_exception.BadRequest(), None]
self._test_power_off(timeout=30)
mock_sp.assert_has_calls([mock.call(node.uuid, 'off', soft=True,
timeout=30).
mock.call(node.uuid, 'off')])
@mock.patch.object(ironic_driver.IronicDriver,
'_validate_instance_and_node')
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
def test_power_off_soft_not_stopped(self, mock_sp, fake_validate):
node = ironic_utils.get_test_node()
fake_validate.side_effect = [node, node]
self._test_power_off(timeout=30)
mock_sp.assert_has_calls([mock.call(node.uuid, 'off', soft=True,
timeout=30).
mock.call(node.uuid, 'off')])
@mock.patch.object(FAKE_CLIENT.node, 'vif_attach')
def test_plug_vifs_with_port(self, mock_vatt):

View File

@ -147,7 +147,7 @@ class FakeNodeClient(object):
def list_ports(self, node_uuid, detail=False):
pass
def set_power_state(self, node_uuid, target, soft=False):
def set_power_state(self, node_uuid, target, soft=False, timeout=None):
pass
def set_provision_state(self, node_uuid, target):

View File

@ -1038,25 +1038,58 @@ class IronicDriver(virt_driver.ComputeDriver):
def power_off(self, instance, timeout=0, retry_interval=0):
"""Power off the specified instance.
NOTE: Ironic does not support soft-off, so this method ignores
timeout and retry_interval parameters.
NOTE: Unlike the libvirt driver, this method does not delete
and recreate the instance; it preserves local state.
:param instance: The instance object.
:param timeout: time to wait for node to shutdown. Ignored by
this driver.
:param timeout: time to wait for node to shutdown. If it is set,
soft power off is attempted before hard power off.
:param retry_interval: How often to signal node while waiting
for it to shutdown. Ignored by this driver.
for it to shutdown. Ignored by this driver. Retrying depends on
Ironic hardware driver.
"""
LOG.debug('Power off called for instance', instance=instance)
node = self._validate_instance_and_node(instance)
self.ironicclient.call("node.set_power_state", node.uuid, 'off')
if timeout:
try:
self.ironicclient.call("node.set_power_state", node.uuid,
'off', soft=True, timeout=timeout)
timer = loopingcall.FixedIntervalLoopingCall(
self._wait_for_power_state, instance, 'soft power off')
timer.start(interval=CONF.ironic.api_retry_interval).wait()
node = self._validate_instance_and_node(instance)
if node.power_state == ironic_states.POWER_OFF:
LOG.info(_LI('Successfully soft powered off Ironic node '
'%s'),
node.uuid, instance=instance)
return
LOG.info(_LI("Failed to soft power off instance "
"%(instance)s on baremetal node %(node)s "
"within the required timeout %(timeout)d "
"seconds due to error: %(reason)s. "
"Attempting hard power off."),
{'instance': instance.uuid,
'timeout': timeout,
'node': node.uuid,
'reason': node.last_error},
instance=instance)
except ironic.exc.ClientException as e:
LOG.info(_LI("Failed to soft power off instance "
"%(instance)s on baremetal node %(node)s "
"due to error: %(reason)s. "
"Attempting hard power off."),
{'instance': instance.uuid,
'node': node.uuid,
'reason': e},
instance=instance)
self.ironicclient.call("node.set_power_state", node.uuid, 'off')
timer = loopingcall.FixedIntervalLoopingCall(
self._wait_for_power_state, instance, 'power off')
timer.start(interval=CONF.ironic.api_retry_interval).wait()
LOG.info(_LI('Successfully powered off Ironic node %s'),
LOG.info(_LI('Successfully hard powered off Ironic node %s'),
node.uuid, instance=instance)
def power_on(self, context, instance, network_info,

View File

@ -0,0 +1,5 @@
---
features:
- Adds soft power off support to Ironic virt driver. This feature requires
the Ironic service to support API version 1.27 or later. It also requires
python-ironicclient >= 1.10.0.