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:
@@ -1372,24 +1372,11 @@ class IronicDriverTestCase(test.NoDBTestCase):
|
|||||||
mock_sp.assert_has_calls([mock.call(node.uuid, 'reboot', soft=True),
|
mock_sp.assert_has_calls([mock.call(node.uuid, 'reboot', soft=True),
|
||||||
mock.call(node.uuid, 'reboot')])
|
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(loopingcall, 'FixedIntervalLoopingCall')
|
||||||
@mock.patch.object(ironic_driver.IronicDriver,
|
@mock.patch.object(ironic_driver.IronicDriver,
|
||||||
'_validate_instance_and_node')
|
'_validate_instance_and_node')
|
||||||
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
|
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
|
||||||
def test_power_on(self, mock_sp, fake_validate, mock_looping):
|
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()
|
node = ironic_utils.get_test_node()
|
||||||
fake_validate.side_effect = [node, node]
|
fake_validate.side_effect = [node, node]
|
||||||
|
|
||||||
@@ -1397,14 +1384,65 @@ class IronicDriverTestCase(test.NoDBTestCase):
|
|||||||
mock_looping.return_value = fake_looping_call
|
mock_looping.return_value = fake_looping_call
|
||||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||||
node=self.instance_uuid)
|
node=self.instance_uuid)
|
||||||
# Call the method under test here
|
self.driver.power_on(self.ctx, instance,
|
||||||
if method_name == 'power_on':
|
utils.get_test_network_info())
|
||||||
self.driver.power_on(self.ctx, instance,
|
mock_sp.assert_called_once_with(node.uuid, 'on')
|
||||||
utils.get_test_network_info())
|
|
||||||
mock_sp.assert_called_once_with(node.uuid, 'on')
|
@mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
|
||||||
elif method_name == 'power_off':
|
def _test_power_off(self, mock_looping, timeout=0):
|
||||||
self.driver.power_off(instance)
|
fake_looping_call = FakeLoopingCall()
|
||||||
mock_sp.assert_called_once_with(node.uuid, 'off')
|
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')
|
@mock.patch.object(FAKE_CLIENT.node, 'vif_attach')
|
||||||
def test_plug_vifs_with_port(self, mock_vatt):
|
def test_plug_vifs_with_port(self, mock_vatt):
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class FakeNodeClient(object):
|
|||||||
def list_ports(self, node_uuid, detail=False):
|
def list_ports(self, node_uuid, detail=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_power_state(self, node_uuid, target, soft=False):
|
def set_power_state(self, node_uuid, target, soft=False, timeout=None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_provision_state(self, node_uuid, target):
|
def set_provision_state(self, node_uuid, target):
|
||||||
|
|||||||
@@ -1038,25 +1038,58 @@ class IronicDriver(virt_driver.ComputeDriver):
|
|||||||
def power_off(self, instance, timeout=0, retry_interval=0):
|
def power_off(self, instance, timeout=0, retry_interval=0):
|
||||||
"""Power off the specified instance.
|
"""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
|
NOTE: Unlike the libvirt driver, this method does not delete
|
||||||
and recreate the instance; it preserves local state.
|
and recreate the instance; it preserves local state.
|
||||||
|
|
||||||
:param instance: The instance object.
|
:param instance: The instance object.
|
||||||
:param timeout: time to wait for node to shutdown. Ignored by
|
:param timeout: time to wait for node to shutdown. If it is set,
|
||||||
this driver.
|
soft power off is attempted before hard power off.
|
||||||
:param retry_interval: How often to signal node while waiting
|
: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)
|
LOG.debug('Power off called for instance', instance=instance)
|
||||||
node = self._validate_instance_and_node(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(
|
timer = loopingcall.FixedIntervalLoopingCall(
|
||||||
self._wait_for_power_state, instance, 'power off')
|
self._wait_for_power_state, instance, 'power off')
|
||||||
timer.start(interval=CONF.ironic.api_retry_interval).wait()
|
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)
|
node.uuid, instance=instance)
|
||||||
|
|
||||||
def power_on(self, context, instance, network_info,
|
def power_on(self, context, instance, network_info,
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user