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:
parent
f3c774a96b
commit
cf02e5161d
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user