A service for managing and provisioning Bare Metal servers.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

7574 lines
367 KiB

# coding=utf-8
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2013 International Business Machines Corporation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Test class for Ironic ManagerService."""
from collections import namedtuple
import datetime
import queue
import re
import eventlet
from futurist import waiters
import mock
from oslo_config import cfg
import oslo_messaging as messaging
from oslo_utils import uuidutils
from oslo_versionedobjects import base as ovo_base
from oslo_versionedobjects import fields
from ironic.common import boot_devices
from ironic.common import components
from ironic.common import driver_factory
from ironic.common import exception
from ironic.common import images
from ironic.common import indicator_states
from ironic.common import nova
from ironic.common import states
from ironic.conductor import cleaning
from ironic.conductor import deployments
from ironic.conductor import manager
from ironic.conductor import notification_utils
from ironic.conductor import steps as conductor_steps
from ironic.conductor import task_manager
from ironic.conductor import utils as conductor_utils
from ironic.db import api as dbapi
from ironic.drivers import base as drivers_base
from ironic.drivers.modules import fake
from ironic.drivers.modules.network import flat as n_flat
from ironic import objects
from ironic.objects import base as obj_base
from ironic.objects import fields as obj_fields
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils
CONF = cfg.CONF
@mgr_utils.mock_record_keepalive
class ChangeNodePowerStateTestCase(mgr_utils.ServiceSetUpMixin,
db_base.DbTestCase):
@mock.patch.object(fake.FakePower, 'get_power_state', autospec=True)
def test_change_node_power_state_power_on(self, get_power_mock):
# Test change_node_power_state including integration with
# conductor.utils.node_power_action and lower.
get_power_mock.return_value = states.POWER_OFF
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_OFF)
self._start_service()
self.service.change_node_power_state(self.context,
node.uuid,
states.POWER_ON)
self._stop_service()
get_power_mock.assert_called_once_with(mock.ANY, mock.ANY)
node.refresh()
self.assertEqual(states.POWER_ON, node.power_state)
self.assertIsNone(node.target_power_state)
self.assertIsNone(node.last_error)
# Verify the reservation has been cleared by
# background task's link callback.
self.assertIsNone(node.reservation)
@mock.patch.object(fake.FakePower, 'get_power_state', autospec=True)
def test_change_node_power_state_soft_power_off_timeout(self,
get_power_mock):
# Test change_node_power_state with timeout optional parameter
# including integration with conductor.utils.node_power_action and
# lower.
get_power_mock.return_value = states.POWER_ON
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_ON)
self._start_service()
self.service.change_node_power_state(self.context,
node.uuid,
states.SOFT_POWER_OFF,
timeout=2)
self._stop_service()
get_power_mock.assert_called_once_with(mock.ANY, mock.ANY)
node.refresh()
self.assertEqual(states.POWER_OFF, node.power_state)
self.assertIsNone(node.target_power_state)
self.assertIsNone(node.last_error)
# Verify the reservation has been cleared by
# background task's link callback.
self.assertIsNone(node.reservation)
@mock.patch.object(conductor_utils, 'node_power_action')
def test_change_node_power_state_node_already_locked(self,
pwr_act_mock):
# Test change_node_power_state with mocked
# conductor.utils.node_power_action.
fake_reservation = 'fake-reserv'
pwr_state = states.POWER_ON
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
power_state=pwr_state,
reservation=fake_reservation)
self._start_service()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.change_node_power_state,
self.context,
node.uuid,
states.POWER_ON)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeLocked, exc.exc_info[0])
# In this test worker should not be spawned, but waiting to make sure
# the below perform_mock assertion is valid.
self._stop_service()
self.assertFalse(pwr_act_mock.called, 'node_power_action has been '
'unexpectedly called.')
# Verify existing reservation wasn't broken.
node.refresh()
self.assertEqual(fake_reservation, node.reservation)
def test_change_node_power_state_worker_pool_full(self):
# Test change_node_power_state including integration with
# conductor.utils.node_power_action and lower.
initial_state = states.POWER_OFF
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
power_state=initial_state)
self._start_service()
with mock.patch.object(self.service,
'_spawn_worker') as spawn_mock:
spawn_mock.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.change_node_power_state,
self.context,
node.uuid,
states.POWER_ON)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
spawn_mock.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY, timeout=mock.ANY)
node.refresh()
self.assertEqual(initial_state, node.power_state)
self.assertIsNone(node.target_power_state)
self.assertIsNotNone(node.last_error)
# Verify the picked reservation has been cleared due to full pool.
self.assertIsNone(node.reservation)
@mock.patch.object(fake.FakePower, 'set_power_state', autospec=True)
@mock.patch.object(fake.FakePower, 'get_power_state', autospec=True)
def test_change_node_power_state_exception_in_background_task(
self, get_power_mock, set_power_mock):
# Test change_node_power_state including integration with
# conductor.utils.node_power_action and lower.
initial_state = states.POWER_OFF
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
power_state=initial_state)
self._start_service()
get_power_mock.return_value = states.POWER_OFF
new_state = states.POWER_ON
set_power_mock.side_effect = exception.PowerStateFailure(
pstate=new_state
)
self.service.change_node_power_state(self.context,
node.uuid,
new_state)
self._stop_service()
get_power_mock.assert_called_once_with(mock.ANY, mock.ANY)
set_power_mock.assert_called_once_with(mock.ANY, mock.ANY,
new_state, timeout=None)
node.refresh()
self.assertEqual(initial_state, node.power_state)
self.assertIsNone(node.target_power_state)
self.assertIsNotNone(node.last_error)
# Verify the reservation has been cleared by background task's
# link callback despite exception in background task.
self.assertIsNone(node.reservation)
@mock.patch.object(fake.FakePower, 'validate', autospec=True)
def test_change_node_power_state_validate_fail(self, validate_mock):
# Test change_node_power_state where task.driver.power.validate
# fails and raises an exception
initial_state = states.POWER_ON
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
power_state=initial_state)
self._start_service()
validate_mock.side_effect = exception.InvalidParameterValue(
'wrong power driver info')
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.change_node_power_state,
self.context,
node.uuid,
states.POWER_ON)
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
node.refresh()
validate_mock.assert_called_once_with(mock.ANY, mock.ANY)
self.assertEqual(states.POWER_ON, node.power_state)
self.assertIsNone(node.target_power_state)
self.assertIsNone(node.last_error)
@mock.patch('ironic.objects.node.NodeSetPowerStateNotification')
def test_node_set_power_state_notif_success(self, mock_notif):
# Test that successfully changing a node's power state sends the
# correct .start and .end notifications
self.config(notification_level='info')
self.config(host='my-host')
# Required for exception handling
mock_notif.__name__ = 'NodeSetPowerStateNotification'
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_OFF)
self._start_service()
self.service.change_node_power_state(self.context,
node.uuid,
states.POWER_ON)
# Give async worker a chance to finish
self._stop_service()
# 2 notifications should be sent: 1 .start and 1 .end
self.assertEqual(2, mock_notif.call_count)
self.assertEqual(2, mock_notif.return_value.emit.call_count)
first_notif_args = mock_notif.call_args_list[0][1]
second_notif_args = mock_notif.call_args_list[1][1]
self.assertNotificationEqual(first_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.start',
obj_fields.NotificationLevel.INFO)
self.assertNotificationEqual(second_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.end',
obj_fields.NotificationLevel.INFO)
@mock.patch.object(fake.FakePower, 'get_power_state', autospec=True)
@mock.patch('ironic.objects.node.NodeSetPowerStateNotification')
def test_node_set_power_state_notif_get_power_fail(self, mock_notif,
get_power_mock):
# Test that correct notifications are sent when changing node power
# state and retrieving the node's current power state fails
self.config(notification_level='info')
self.config(host='my-host')
# Required for exception handling
mock_notif.__name__ = 'NodeSetPowerStateNotification'
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_OFF)
self._start_service()
get_power_mock.side_effect = Exception('I have failed')
self.service.change_node_power_state(self.context,
node.uuid,
states.POWER_ON)
# Give async worker a chance to finish
self._stop_service()
get_power_mock.assert_called_once_with(mock.ANY, mock.ANY)
# 2 notifications should be sent: 1 .start and 1 .error
self.assertEqual(2, mock_notif.call_count)
self.assertEqual(2, mock_notif.return_value.emit.call_count)
first_notif_args = mock_notif.call_args_list[0][1]
second_notif_args = mock_notif.call_args_list[1][1]
self.assertNotificationEqual(first_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.start',
obj_fields.NotificationLevel.INFO)
self.assertNotificationEqual(second_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.error',
obj_fields.NotificationLevel.ERROR)
@mock.patch.object(fake.FakePower, 'set_power_state', autospec=True)
@mock.patch('ironic.objects.node.NodeSetPowerStateNotification')
def test_node_set_power_state_notif_set_power_fail(self, mock_notif,
set_power_mock):
# Test that correct notifications are sent when changing node power
# state and setting the node's power state fails
self.config(notification_level='info')
self.config(host='my-host')
# Required for exception handling
mock_notif.__name__ = 'NodeSetPowerStateNotification'
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_OFF)
self._start_service()
set_power_mock.side_effect = Exception('I have failed')
self.service.change_node_power_state(self.context,
node.uuid,
states.POWER_ON)
# Give async worker a chance to finish
self._stop_service()
set_power_mock.assert_called_once_with(mock.ANY, mock.ANY,
states.POWER_ON, timeout=None)
# 2 notifications should be sent: 1 .start and 1 .error
self.assertEqual(2, mock_notif.call_count)
self.assertEqual(2, mock_notif.return_value.emit.call_count)
first_notif_args = mock_notif.call_args_list[0][1]
second_notif_args = mock_notif.call_args_list[1][1]
self.assertNotificationEqual(first_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.start',
obj_fields.NotificationLevel.INFO)
self.assertNotificationEqual(second_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.error',
obj_fields.NotificationLevel.ERROR)
@mock.patch('ironic.objects.node.NodeSetPowerStateNotification')
def test_node_set_power_state_notif_spawn_fail(self, mock_notif):
# Test that failure notification is not sent when spawning the
# background conductor worker fails
self.config(notification_level='info')
self.config(host='my-host')
# Required for exception handling
mock_notif.__name__ = 'NodeSetPowerStateNotification'
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_OFF)
self._start_service()
with mock.patch.object(self.service,
'_spawn_worker') as spawn_mock:
spawn_mock.side_effect = exception.NoFreeConductorWorker()
self.assertRaises(messaging.rpc.ExpectedException,
self.service.change_node_power_state,
self.context,
node.uuid,
states.POWER_ON)
spawn_mock.assert_called_once_with(
conductor_utils.node_power_action, mock.ANY, states.POWER_ON,
timeout=None)
self.assertFalse(mock_notif.called)
@mock.patch('ironic.objects.node.NodeSetPowerStateNotification')
def test_node_set_power_state_notif_no_state_change(self, mock_notif):
# Test that correct notifications are sent when changing node power
# state and no state change is necessary
self.config(notification_level='info')
self.config(host='my-host')
# Required for exception handling
mock_notif.__name__ = 'NodeSetPowerStateNotification'
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
power_state=states.POWER_OFF)
self._start_service()
self.service.change_node_power_state(self.context,
node.uuid,
states.POWER_OFF)
# Give async worker a chance to finish
self._stop_service()
# 2 notifications should be sent: 1 .start and 1 .end
self.assertEqual(2, mock_notif.call_count)
self.assertEqual(2, mock_notif.return_value.emit.call_count)
first_notif_args = mock_notif.call_args_list[0][1]
second_notif_args = mock_notif.call_args_list[1][1]
self.assertNotificationEqual(first_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.start',
obj_fields.NotificationLevel.INFO)
self.assertNotificationEqual(second_notif_args,
'ironic-conductor', CONF.host,
'baremetal.node.power_set.end',
obj_fields.NotificationLevel.INFO)
@mock.patch.object(fake.FakePower, 'get_supported_power_states',
autospec=True)
def test_change_node_power_state_unsupported_state(self, supported_mock):
# Test change_node_power_state where unsupported power state raises
# an exception
initial_state = states.POWER_ON
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
power_state=initial_state)
self._start_service()
supported_mock.return_value = [
states.POWER_ON, states.POWER_OFF, states.REBOOT]
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.change_node_power_state,
self.context,
node.uuid,
states.SOFT_POWER_OFF)
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
node.refresh()
supported_mock.assert_called_once_with(mock.ANY, mock.ANY)
self.assertEqual(states.POWER_ON, node.power_state)
self.assertIsNone(node.target_power_state)
self.assertIsNone(node.last_error)
@mgr_utils.mock_record_keepalive
class CreateNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
def test_create_node(self):
node = obj_utils.get_test_node(self.context, driver='fake-hardware',
extra={'test': 'one'})
res = self.service.create_node(self.context, node)
self.assertEqual({'test': 'one'}, res['extra'])
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertEqual({'test': 'one'}, res['extra'])
@mock.patch.object(driver_factory, 'check_and_update_node_interfaces',
autospec=True)
def test_create_node_validation_fails(self, mock_validate):
node = obj_utils.get_test_node(self.context, driver='fake-hardware',
extra={'test': 'one'})
mock_validate.side_effect = exception.InterfaceNotFoundInEntrypoint(
'boom')
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.create_node,
self.context, node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InterfaceNotFoundInEntrypoint,
exc.exc_info[0])
self.assertRaises(exception.NotFound,
objects.Node.get_by_uuid, self.context, node['uuid'])
@mgr_utils.mock_record_keepalive
class UpdateNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
def test_update_node(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
extra={'test': 'one'})
# check that ManagerService.update_node actually updates the node
node.extra = {'test': 'two'}
res = self.service.update_node(self.context, node)
self.assertEqual({'test': 'two'}, res['extra'])
def test_update_node_maintenance_set_false(self):
node = obj_utils.create_test_node(self.context,
driver='fake-hardware',
maintenance=True,
fault='clean failure',
maintenance_reason='reason')
# check that ManagerService.update_node actually updates the node
node.maintenance = False
res = self.service.update_node(self.context, node)
self.assertFalse(res['maintenance'])
self.assertIsNone(res['maintenance_reason'])
self.assertIsNone(res['fault'])
def test_update_node_protected_set(self):
for state in ('active', 'rescue'):
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
provision_state=state)
node.protected = True
res = self.service.update_node(self.context, node)
self.assertTrue(res['protected'])
self.assertIsNone(res['protected_reason'])
def test_update_node_protected_unset(self):
# NOTE(dtantsur): we allow unsetting protected in any state to make
# sure a node cannot get stuck in it.
for state in ('active', 'rescue', 'rescue failed'):
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
provision_state=state,
protected=True,
protected_reason='reason')
# check that ManagerService.update_node actually updates the node
node.protected = False
res = self.service.update_node(self.context, node)
self.assertFalse(res['protected'])
self.assertIsNone(res['protected_reason'])
def test_update_node_protected_invalid_state(self):
node = obj_utils.create_test_node(self.context,
provision_state='available')
node.protected = True
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidState, exc.exc_info[0])
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertFalse(res['protected'])
self.assertIsNone(res['protected_reason'])
def test_update_node_protected_reason_without_protected(self):
node = obj_utils.create_test_node(self.context,
provision_state='active')
node.protected_reason = 'reason!'
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertFalse(res['protected'])
self.assertIsNone(res['protected_reason'])
def test_update_node_retired_set(self):
for state in ('active', 'rescue', 'manageable'):
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
provision_state=state)
node.retired = True
res = self.service.update_node(self.context, node)
self.assertTrue(res['retired'])
self.assertIsNone(res['retired_reason'])
def test_update_node_retired_invalid_state(self):
# NOTE(arne_wiebalck): nodes in available cannot be 'retired'.
# This is to ensure backwards comaptibility.
node = obj_utils.create_test_node(self.context,
provision_state='available')
node.retired = True
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidState, exc.exc_info[0])
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertFalse(res['retired'])
self.assertIsNone(res['retired_reason'])
def test_update_node_retired_unset(self):
for state in ('active', 'manageable', 'rescue', 'rescue failed'):
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
provision_state=state,
retired=True,
retired_reason='EOL')
# check that ManagerService.update_node actually updates the node
node.retired = False
res = self.service.update_node(self.context, node)
self.assertFalse(res['retired'])
self.assertIsNone(res['retired_reason'])
def test_update_node_retired_reason_without_retired(self):
node = obj_utils.create_test_node(self.context,
provision_state='active')
node.retired_reason = 'warranty expired'
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertFalse(res['retired'])
self.assertIsNone(res['retired_reason'])
def test_update_node_already_locked(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
extra={'test': 'one'})
# check that it fails if something else has locked it already
with task_manager.acquire(self.context, node['id'], shared=False):
node.extra = {'test': 'two'}
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeLocked, exc.exc_info[0])
# verify change did not happen
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertEqual({'test': 'one'}, res['extra'])
def test_update_node_already_associated(self):
old_instance = uuidutils.generate_uuid()
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
instance_uuid=old_instance)
node.instance_uuid = uuidutils.generate_uuid()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeAssociated, exc.exc_info[0])
# verify change did not happen
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertEqual(old_instance, res['instance_uuid'])
@mock.patch('ironic.drivers.modules.fake.FakePower.get_power_state')
def _test_associate_node(self, power_state, mock_get_power_state):
mock_get_power_state.return_value = power_state
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
instance_uuid=None,
power_state=states.NOSTATE)
uuid1 = uuidutils.generate_uuid()
uuid2 = uuidutils.generate_uuid()
node.instance_uuid = uuid1
self.service.update_node(self.context, node)
# Check if the change was applied
node.instance_uuid = uuid2
node.refresh()
self.assertEqual(uuid1, node.instance_uuid)
def test_associate_node_powered_off(self):
self._test_associate_node(states.POWER_OFF)
def test_associate_node_powered_on(self):
self._test_associate_node(states.POWER_ON)
def test_update_node_invalid_driver(self):
existing_driver = 'fake-hardware'
wrong_driver = 'wrong-driver'
node = obj_utils.create_test_node(self.context,
driver=existing_driver,
extra={'test': 'one'},
instance_uuid=None)
# check that it fails because driver not found
node.driver = wrong_driver
node.driver_info = {}
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context, node)
self.assertEqual(exception.DriverNotFound, exc.exc_info[0])
# verify change did not happen
node.refresh()
self.assertEqual(existing_driver, node.driver)
def test_update_node_from_invalid_driver(self):
existing_driver = 'fake-hardware'
wrong_driver = 'wrong-driver'
node = obj_utils.create_test_node(self.context, driver=wrong_driver)
node.driver = existing_driver
result = self.service.update_node(self.context, node)
self.assertEqual(existing_driver, result.driver)
node.refresh()
self.assertEqual(existing_driver, node.driver)
UpdateInterfaces = namedtuple('UpdateInterfaces', ('old', 'new'))
# NOTE(dtantsur): "old" interfaces here do not match the defaults, so that
# we can test resetting them.
IFACE_UPDATE_DICT = {
'boot_interface': UpdateInterfaces('pxe', 'fake'),
'console_interface': UpdateInterfaces('no-console', 'fake'),
'deploy_interface': UpdateInterfaces('iscsi', 'fake'),
'inspect_interface': UpdateInterfaces('no-inspect', 'fake'),
'management_interface': UpdateInterfaces(None, 'fake'),
'network_interface': UpdateInterfaces('noop', 'flat'),
'power_interface': UpdateInterfaces(None, 'fake'),
'raid_interface': UpdateInterfaces('no-raid', 'fake'),
'rescue_interface': UpdateInterfaces('no-rescue', 'fake'),
'storage_interface': UpdateInterfaces('fake', 'noop'),
}
def _create_node_with_interfaces(self, prov_state, maintenance=False):
old_ifaces = {}
for iface_name, ifaces in self.IFACE_UPDATE_DICT.items():
old_ifaces[iface_name] = ifaces.old
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
uuid=uuidutils.generate_uuid(),
provision_state=prov_state,
maintenance=maintenance,
**old_ifaces)
return node
def _test_update_node_interface_allowed(self, node, iface_name, new_iface):
setattr(node, iface_name, new_iface)
self.service.update_node(self.context, node)
node.refresh()
self.assertEqual(new_iface, getattr(node, iface_name))
def _test_update_node_interface_in_allowed_state(self, prov_state,
maintenance=False):
node = self._create_node_with_interfaces(prov_state,
maintenance=maintenance)
for iface_name, ifaces in self.IFACE_UPDATE_DICT.items():
self._test_update_node_interface_allowed(node, iface_name,
ifaces.new)
node.destroy()
def test_update_node_interface_in_allowed_state(self):
for state in [states.ENROLL, states.MANAGEABLE, states.INSPECTING,
states.INSPECTWAIT, states.AVAILABLE]:
self._test_update_node_interface_in_allowed_state(state)
def test_update_node_interface_in_maintenance(self):
self._test_update_node_interface_in_allowed_state(states.ACTIVE,
maintenance=True)
def _test_update_node_interface_not_allowed(self, node, iface_name,
new_iface):
old_iface = getattr(node, iface_name)
setattr(node, iface_name, new_iface)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context, node)
self.assertEqual(exception.InvalidState, exc.exc_info[0])
node.refresh()
self.assertEqual(old_iface, getattr(node, iface_name))
def _test_update_node_interface_in_not_allowed_state(self, prov_state):
node = self._create_node_with_interfaces(prov_state)
for iface_name, ifaces in self.IFACE_UPDATE_DICT.items():
self._test_update_node_interface_not_allowed(node, iface_name,
ifaces.new)
node.destroy()
def test_update_node_interface_in_not_allowed_state(self):
for state in [states.ACTIVE, states.DELETING]:
self._test_update_node_interface_in_not_allowed_state(state)
def _test_update_node_interface_invalid(self, node, iface_name):
old_iface = getattr(node, iface_name)
setattr(node, iface_name, 'invalid')
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context, node)
self.assertEqual(exception.InterfaceNotFoundInEntrypoint,
exc.exc_info[0])
node.refresh()
self.assertEqual(old_iface, getattr(node, iface_name))
def test_update_node_interface_invalid(self):
node = self._create_node_with_interfaces(states.MANAGEABLE)
for iface_name in self.IFACE_UPDATE_DICT:
self._test_update_node_interface_invalid(node, iface_name)
def test_update_node_with_reset_interfaces(self):
# Modify only one interface at a time
for iface_name, ifaces in self.IFACE_UPDATE_DICT.items():
node = self._create_node_with_interfaces(states.AVAILABLE)
setattr(node, iface_name, ifaces.new)
# Updating a driver is mandatory for reset_interfaces to work
node.driver = 'fake-hardware'
self.service.update_node(self.context, node,
reset_interfaces=True)
node.refresh()
self.assertEqual(ifaces.new, getattr(node, iface_name))
# Other interfaces must be reset to their defaults
for other_iface_name, ifaces in self.IFACE_UPDATE_DICT.items():
if other_iface_name == iface_name:
continue
# For this to work, the "old" interfaces in IFACE_UPDATE_DICT
# must not match the defaults.
self.assertNotEqual(ifaces.old,
getattr(node, other_iface_name),
"%s does not match the default after "
"reset with setting %s: %s" %
(other_iface_name, iface_name,
getattr(node, other_iface_name)))
def _test_update_node_change_resource_class(self, state,
resource_class=None,
new_resource_class='new',
expect_error=False,
maintenance=False):
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
uuid=uuidutils.generate_uuid(),
provision_state=state,
resource_class=resource_class,
maintenance=maintenance)
self.addCleanup(node.destroy)
node.resource_class = new_resource_class
if expect_error:
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context,
node)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidState, exc.exc_info[0])
expected_msg_regex = \
(r'^Node {} can not have resource_class updated unless it is '
r'in one of allowed \(.*\) states.$').format(
re.escape(node.uuid))
self.assertRegex(str(exc.exc_info[1]), expected_msg_regex)
# verify change did not happen
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertEqual(resource_class, res['resource_class'])
else:
self.service.update_node(self.context, node)
res = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertEqual('new', res['resource_class'])
def test_update_resource_class_allowed_state(self):
for state in [states.ENROLL, states.MANAGEABLE, states.INSPECTING,
states.AVAILABLE]:
self._test_update_node_change_resource_class(
state, resource_class='old', expect_error=False)
def test_update_resource_class_no_previous_value(self):
for state in [states.ENROLL, states.MANAGEABLE, states.INSPECTING,
states.AVAILABLE, states.ACTIVE]:
self._test_update_node_change_resource_class(
state, resource_class=None, expect_error=False)
def test_update_resource_class_not_allowed(self):
self._test_update_node_change_resource_class(
states.ACTIVE, resource_class='old', new_resource_class='new',
expect_error=True)
self._test_update_node_change_resource_class(
states.ACTIVE, resource_class='old', new_resource_class=None,
expect_error=True)
self._test_update_node_change_resource_class(
states.ACTIVE, resource_class='old', new_resource_class=None,
expect_error=True, maintenance=True)
def test_update_node_hardware_type(self):
existing_hardware = 'fake-hardware'
existing_interface = 'fake'
new_hardware = 'manual-management'
new_interface = 'pxe'
node = obj_utils.create_test_node(self.context,
driver=existing_hardware,
boot_interface=existing_interface)
node.driver = new_hardware
node.boot_interface = new_interface
self.service.update_node(self.context, node)
node.refresh()
self.assertEqual(new_hardware, node.driver)
self.assertEqual(new_interface, node.boot_interface)
def test_update_node_deleting_allocation(self):
node = obj_utils.create_test_node(self.context)
alloc = obj_utils.create_test_allocation(self.context)
# Establish cross-linking between the node and the allocation
alloc.node_id = node.id
alloc.save()
node.refresh()
self.assertEqual(alloc.id, node.allocation_id)
self.assertEqual(alloc.uuid, node.instance_uuid)
node.instance_uuid = None
res = self.service.update_node(self.context, node)
self.assertRaises(exception.AllocationNotFound,
objects.Allocation.get_by_id,
self.context, alloc.id)
self.assertIsNone(res['instance_uuid'])
self.assertIsNone(res['allocation_id'])
node.refresh()
self.assertIsNone(node.instance_uuid)
self.assertIsNone(node.allocation_id)
def test_update_node_deleting_allocation_forbidden(self):
node = obj_utils.create_test_node(self.context,
provision_state='active',
maintenance=False)
alloc = obj_utils.create_test_allocation(self.context)
# Establish cross-linking between the node and the allocation
alloc.node_id = node.id
alloc.save()
node.refresh()
self.assertEqual(alloc.id, node.allocation_id)
self.assertEqual(alloc.uuid, node.instance_uuid)
node.instance_uuid = None
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_node,
self.context, node)
self.assertEqual(exception.InvalidState, exc.exc_info[0])
node.refresh()
self.assertEqual(alloc.id, node.allocation_id)
self.assertEqual(alloc.uuid, node.instance_uuid)
def test_update_node_deleting_allocation_in_maintenance(self):
node = obj_utils.create_test_node(self.context,
provision_state='active',
maintenance=True)
alloc = obj_utils.create_test_allocation(self.context)
# Establish cross-linking between the node and the allocation
alloc.node_id = node.id
alloc.save()
node.refresh()
self.assertEqual(alloc.id, node.allocation_id)
self.assertEqual(alloc.uuid, node.instance_uuid)
node.instance_uuid = None
res = self.service.update_node(self.context, node)
self.assertRaises(exception.AllocationNotFound,
objects.Allocation.get_by_id,
self.context, alloc.id)
self.assertIsNone(res['instance_uuid'])
self.assertIsNone(res['allocation_id'])
node.refresh()
self.assertIsNone(node.instance_uuid)
self.assertIsNone(node.allocation_id)
@mgr_utils.mock_record_keepalive
class VendorPassthruTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
@mock.patch.object(task_manager.TaskManager, 'upgrade_lock')
@mock.patch.object(task_manager.TaskManager, 'spawn_after')
def test_vendor_passthru_async(self, mock_spawn,
mock_upgrade):
node = obj_utils.create_test_node(self.context,
vendor_interface='fake')
info = {'bar': 'baz'}
self._start_service()
response = self.service.vendor_passthru(self.context, node.uuid,
'second_method', 'POST',
info)
# Waiting to make sure the below assertions are valid.
self._stop_service()
# Assert spawn_after was called
self.assertTrue(mock_spawn.called)
self.assertIsNone(response['return'])
self.assertTrue(response['async'])
# Assert lock was upgraded to an exclusive one
self.assertEqual(1, mock_upgrade.call_count)
node.refresh()
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch.object(task_manager.TaskManager, 'upgrade_lock')
@mock.patch.object(task_manager.TaskManager, 'spawn_after')
def test_vendor_passthru_sync(self, mock_spawn, mock_upgrade):
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
info = {'bar': 'meow'}
self._start_service()
response = self.service.vendor_passthru(self.context, node.uuid,
'third_method_sync',
'POST', info)
# Waiting to make sure the below assertions are valid.
self._stop_service()
# Assert no workers were used
self.assertFalse(mock_spawn.called)
self.assertTrue(response['return'])
self.assertFalse(response['async'])
# Assert lock was upgraded to an exclusive one
self.assertEqual(1, mock_upgrade.call_count)
node.refresh()
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch.object(task_manager.TaskManager, 'upgrade_lock')
@mock.patch.object(task_manager.TaskManager, 'spawn_after')
def test_vendor_passthru_shared_lock(self, mock_spawn, mock_upgrade):
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
info = {'bar': 'woof'}
self._start_service()
response = self.service.vendor_passthru(self.context, node.uuid,
'fourth_method_shared_lock',
'POST', info)
# Waiting to make sure the below assertions are valid.
self._stop_service()
# Assert spawn_after was called
self.assertTrue(mock_spawn.called)
self.assertIsNone(response['return'])
self.assertTrue(response['async'])
# Assert lock was never upgraded to an exclusive one
self.assertFalse(mock_upgrade.called)
node.refresh()
self.assertIsNone(node.last_error)
# Verify there's no reservation on the node
self.assertIsNone(node.reservation)
def test_vendor_passthru_http_method_not_supported(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
self._start_service()
# GET not supported by first_method
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.vendor_passthru,
self.context, node.uuid,
'second_method', 'GET', {})
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
node.refresh()
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
def test_vendor_passthru_node_already_locked(self):
fake_reservation = 'test_reserv'
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
reservation=fake_reservation)
info = {'bar': 'baz'}
self._start_service()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.vendor_passthru,
self.context, node.uuid, 'second_method',
'POST', info)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeLocked, exc.exc_info[0])
node.refresh()
self.assertIsNone(node.last_error)
# Verify the existing reservation is not broken.
self.assertEqual(fake_reservation, node.reservation)
def test_vendor_passthru_unsupported_method(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
info = {'bar': 'baz'}
self._start_service()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.vendor_passthru,
self.context, node.uuid,
'unsupported_method', 'POST', info)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue,
exc.exc_info[0])
node.refresh()
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
def test_vendor_passthru_missing_method_parameters(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
info = {'invalid_param': 'whatever'}
self._start_service()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.vendor_passthru,
self.context, node.uuid,
'second_method', 'POST', info)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.MissingParameterValue, exc.exc_info[0])
node.refresh()
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
def test_vendor_passthru_worker_pool_full(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
info = {'bar': 'baz'}
self._start_service()
with mock.patch.object(self.service,
'_spawn_worker') as spawn_mock:
spawn_mock.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.vendor_passthru,
self.context, node.uuid,
'second_method', 'POST', info)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
# Waiting to make sure the below assertions are valid.
self._stop_service()
node.refresh()
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch.object(driver_factory, 'get_interface', autospec=True)
def test_get_node_vendor_passthru_methods(self, mock_iface):
fake_routes = {'test_method': {'async': True,
'description': 'foo',
'http_methods': ['POST'],
'func': None}}
mock_iface.return_value.vendor_routes = fake_routes
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
self._start_service()
data = self.service.get_node_vendor_passthru_methods(self.context,
node.uuid)
# The function reference should not be returned
del fake_routes['test_method']['func']
self.assertEqual(fake_routes, data)
@mock.patch.object(driver_factory, 'get_interface')
@mock.patch.object(manager.ConductorManager, '_spawn_worker')
def test_driver_vendor_passthru_sync(self, mock_spawn, mock_get_if):
expected = {'foo': 'bar'}
vendor_mock = mock.Mock(spec=drivers_base.VendorInterface)
mock_get_if.return_value = vendor_mock
driver_name = 'fake-hardware'
test_method = mock.MagicMock(return_value=expected)
vendor_mock.driver_routes = {
'test_method': {'func': test_method,
'async': False,
'attach': False,
'http_methods': ['POST']}}
self.service.init_host()
# init_host() called _spawn_worker because of the heartbeat
mock_spawn.reset_mock()
# init_host() called get_interface during driver loading
mock_get_if.reset_mock()
vendor_args = {'test': 'arg'}
response = self.service.driver_vendor_passthru(
self.context, driver_name, 'test_method', 'POST', vendor_args)
# Assert that the vendor interface has no custom
# driver_vendor_passthru()
self.assertFalse(hasattr(vendor_mock, 'driver_vendor_passthru'))
self.assertEqual(expected, response['return'])
self.assertFalse(response['async'])
test_method.assert_called_once_with(self.context, **vendor_args)
# No worker was spawned
self.assertFalse(mock_spawn.called)
mock_get_if.assert_called_once_with(mock.ANY, 'vendor', 'fake')
@mock.patch.object(driver_factory, 'get_interface', autospec=True)
@mock.patch.object(manager.ConductorManager, '_spawn_worker',
autospec=True)
def test_driver_vendor_passthru_async(self, mock_spawn, mock_iface):
test_method = mock.MagicMock()
mock_iface.return_value.driver_routes = {
'test_sync_method': {'func': test_method,
'async': True,
'attach': False,
'http_methods': ['POST']}}
self.service.init_host()
# init_host() called _spawn_worker because of the heartbeat
mock_spawn.reset_mock()
vendor_args = {'test': 'arg'}
response = self.service.driver_vendor_passthru(
self.context, 'fake-hardware', 'test_sync_method', 'POST',
vendor_args)
self.assertIsNone(response['return'])
self.assertTrue(response['async'])
mock_spawn.assert_called_once_with(self.service, test_method,
self.context, **vendor_args)
@mock.patch.object(driver_factory, 'get_interface', autospec=True)
def test_driver_vendor_passthru_http_method_not_supported(self,
mock_iface):
mock_iface.return_value.driver_routes = {
'test_method': {'func': mock.MagicMock(),
'async': True,
'http_methods': ['POST']}}
self.service.init_host()
# GET not supported by test_method
exc = self.assertRaises(messaging.ExpectedException,
self.service.driver_vendor_passthru,
self.context, 'fake-hardware', 'test_method',
'GET', {})
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue,
exc.exc_info[0])
def test_driver_vendor_passthru_method_not_supported(self):
# Test for when the vendor interface is set, but hasn't passed a
# driver_passthru_mapping to MixinVendorInterface
self.service.init_host()
exc = self.assertRaises(messaging.ExpectedException,
self.service.driver_vendor_passthru,
self.context, 'fake-hardware', 'test_method',
'POST', {})
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue,
exc.exc_info[0])
def test_driver_vendor_passthru_driver_not_found(self):
self.service.init_host()
self.assertRaises(messaging.ExpectedException,
self.service.driver_vendor_passthru,
self.context, 'does_not_exist', 'test_method',
'POST', {})
@mock.patch.object(driver_factory, 'default_interface', autospec=True)
def test_driver_vendor_passthru_no_default_interface(self,
mock_def_iface):
self.service.init_host()
# NOTE(rloo): service.init_host() will call
# driver_factory.default_interface() and we want these to
# succeed, so we set the side effect *after* that call.
mock_def_iface.reset_mock()
mock_def_iface.side_effect = exception.NoValidDefaultForInterface('no')
exc = self.assertRaises(messaging.ExpectedException,
self.service.driver_vendor_passthru,
self.context, 'fake-hardware', 'test_method',
'POST', {})
mock_def_iface.assert_called_once_with(mock.ANY, 'vendor',
driver_name='fake-hardware')
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoValidDefaultForInterface,
exc.exc_info[0])
@mock.patch.object(driver_factory, 'get_interface', autospec=True)
def test_get_driver_vendor_passthru_methods(self, mock_get_if):
vendor_mock = mock.Mock(spec=drivers_base.VendorInterface)
mock_get_if.return_value = vendor_mock
driver_name = 'fake-hardware'
fake_routes = {'test_method': {'async': True,
'description': 'foo',
'http_methods': ['POST'],
'func': None}}
vendor_mock.driver_routes = fake_routes
self.service.init_host()
# init_host() will call get_interface
mock_get_if.reset_mock()
data = self.service.get_driver_vendor_passthru_methods(self.context,
driver_name)
# The function reference should not be returned
del fake_routes['test_method']['func']
self.assertEqual(fake_routes, data)
mock_get_if.assert_called_once_with(mock.ANY, 'vendor', 'fake')
@mock.patch.object(driver_factory, 'default_interface', autospec=True)
def test_get_driver_vendor_passthru_methods_no_default_interface(
self, mock_def_iface):
self.service.init_host()
# NOTE(rloo): service.init_host() will call
# driver_factory.default_interface() and we want these to
# succeed, so we set the side effect *after* that call.
mock_def_iface.reset_mock()
mock_def_iface.side_effect = exception.NoValidDefaultForInterface('no')
exc = self.assertRaises(
messaging.rpc.ExpectedException,
self.service.get_driver_vendor_passthru_methods,
self.context, 'fake-hardware')
mock_def_iface.assert_called_once_with(mock.ANY, 'vendor',
driver_name='fake-hardware')
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoValidDefaultForInterface,
exc.exc_info[0])
@mock.patch.object(driver_factory, 'get_interface', autospec=True)
def test_driver_vendor_passthru_validation_failed(self, mock_iface):
mock_iface.return_value.driver_validate.side_effect = (
exception.MissingParameterValue('error'))
test_method = mock.Mock()
mock_iface.return_value.driver_routes = {
'test_method': {'func': test_method,
'async': False,
'http_methods': ['POST']}}
self.service.init_host()
exc = self.assertRaises(messaging.ExpectedException,
self.service.driver_vendor_passthru,
self.context, 'fake-hardware', 'test_method',
'POST', {})
self.assertEqual(exception.MissingParameterValue,
exc.exc_info[0])
self.assertFalse(test_method.called)
@mgr_utils.mock_record_keepalive
@mock.patch.object(images, 'is_whole_disk_image')
class ServiceDoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
db_base.DbTestCase):
def test_do_node_deploy_invalid_state(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# test that node deploy fails if the node is already provisioned
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node['uuid'])
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
# This is a sync operation last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
self.assertFalse(mock_iwdi.called)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
def test_do_node_deploy_maintenance(self, mock_iwdi):
mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
maintenance=True)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node['uuid'])
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeInMaintenance, exc.exc_info[0])
# This is a sync operation last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
self.assertFalse(mock_iwdi.called)
def _test_do_node_deploy_validate_fail(self, mock_validate, mock_iwdi):
mock_iwdi.return_value = False
# InvalidParameterValue should be re-raised as InstanceDeployFailure
mock_validate.side_effect = exception.InvalidParameterValue('error')
node = obj_utils.create_test_node(self.context, driver='fake-hardware')
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InstanceDeployFailure, exc.exc_info[0])
self.assertEqual(exc.exc_info[1].code, 400)
# Check the message of InstanceDeployFailure. In a
# messaging.rpc.ExpectedException sys.exc_info() is stored in exc_info
# in the exception object. So InstanceDeployFailure will be in
# exc_info[1]
self.assertIn(r'node 1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
str(exc.exc_info[1]))
# This is a sync operation last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.validate')
def test_do_node_deploy_validate_fail(self, mock_validate, mock_iwdi):
self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch('ironic.drivers.modules.fake.FakePower.validate')
def test_do_node_deploy_power_validate_fail(self, mock_validate,
mock_iwdi):
self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch.object(conductor_utils, 'validate_instance_info_traits')
def test_do_node_deploy_traits_validate_fail(self, mock_validate,
mock_iwdi):
self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch.object(conductor_steps, 'validate_deploy_templates')
def test_do_node_deploy_validate_template_fail(self, mock_validate,
mock_iwdi):
self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
def test_do_node_deploy_partial_ok(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
thread = self.service._spawn_worker(lambda: None)
with mock.patch.object(self.service, '_spawn_worker',
autospec=True) as mock_spawn:
mock_spawn.return_value = thread
node = obj_utils.create_test_node(
self.context,
driver='fake-hardware',
provision_state=states.AVAILABLE,
driver_internal_info={'agent_url': 'url'})
self.service.do_node_deploy(self.context, node.uuid)
self._stop_service()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# This is a sync operation last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_spawn.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY, None)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
self.assertNotIn('agent_url', node.driver_internal_info)
def test_do_node_deploy_rebuild_active_state_error(self, mock_iwdi):
# Tests manager.do_node_deploy() & deployments.do_next_deploy_step(),
# when getting an unexpected state returned from a deploy_step.
mock_iwdi.return_value = True
self._start_service()
# NOTE(rloo): We have to mock this here as opposed to using a
# decorator. With a decorator, when initialization is done, the
# mocked deploy() method isn't considered a deploy step. So we defer
# mock'ing until after the init is done.
with mock.patch.object(fake.FakeDeploy,
'deploy', autospec=True) as mock_deploy:
mock_deploy.return_value = states.DEPLOYING
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid(),
'kernel': 'aaaa', 'ramdisk': 'bbbb'},
driver_internal_info={'is_whole_disk_image': False})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self._stop_service()
node.refresh()
self.assertEqual(states.DEPLOYFAIL, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY, mock.ANY)
# Verify instance_info values have been cleared.
self.assertNotIn('kernel', node.instance_info)
self.assertNotIn('ramdisk', node.instance_info)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
# Verify is_whole_disk_image reflects correct value on rebuild.
self.assertTrue(node.driver_internal_info['is_whole_disk_image'])
self.assertEqual(1, len(node.driver_internal_info['deploy_steps']))
def test_do_node_deploy_rebuild_active_state_waiting(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# NOTE(rloo): We have to mock this here as opposed to using a
# decorator. With a decorator, when initialization is done, the
# mocked deploy() method isn't considered a deploy step. So we defer
# mock'ing until after the init is done.
with mock.patch.object(fake.FakeDeploy,
'deploy', autospec=True) as mock_deploy:
mock_deploy.return_value = states.DEPLOYWAIT
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid()})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self._stop_service()
node.refresh()
self.assertEqual(states.DEPLOYWAIT, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY, mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
self.assertEqual(1, len(node.driver_internal_info['deploy_steps']))
def test_do_node_deploy_rebuild_active_state_done(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# NOTE(rloo): We have to mock this here as opposed to using a
# decorator. With a decorator, when initialization is done, the
# mocked deploy() method isn't considered a deploy step. So we defer
# mock'ing until after the init is done.
with mock.patch.object(fake.FakeDeploy,
'deploy', autospec=True) as mock_deploy:
mock_deploy.return_value = None
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self._stop_service()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY, mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
self.assertIsNone(node.driver_internal_info['deploy_steps'])
def test_do_node_deploy_rebuild_deployfail_state(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# NOTE(rloo): We have to mock this here as opposed to using a
# decorator. With a decorator, when initialization is done, the
# mocked deploy() method isn't considered a deploy step. So we defer
# mock'ing until after the init is done.
with mock.patch.object(fake.FakeDeploy,
'deploy', autospec=True) as mock_deploy:
mock_deploy.return_value = None
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DEPLOYFAIL,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self._stop_service()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY, mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
self.assertIsNone(node.driver_internal_info['deploy_steps'])
def test_do_node_deploy_rebuild_error_state(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# NOTE(rloo): We have to mock this here as opposed to using a
# decorator. With a decorator, when initialization is done, the
# mocked deploy() method isn't considered a deploy step. So we defer
# mock'ing until after the init is done.
with mock.patch.object(fake.FakeDeploy,
'deploy', autospec=True) as mock_deploy:
mock_deploy.return_value = None
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ERROR,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self._stop_service()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY, mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
self.assertIsNone(node.driver_internal_info['deploy_steps'])
def test_do_node_deploy_rebuild_from_available_state(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# test node will not rebuild if state is AVAILABLE
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=states.AVAILABLE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node['uuid'], rebuild=True)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
# Last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
self.assertFalse(mock_iwdi.called)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
def test_do_node_deploy_rebuild_protected(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
protected=True)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node['uuid'], rebuild=True)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeProtected, exc.exc_info[0])
# Last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
self.assertFalse(mock_iwdi.called)
def test_do_node_deploy_worker_pool_full(self, mock_iwdi):
mock_iwdi.return_value = False
prv_state = states.AVAILABLE
tgt_prv_state = states.NOSTATE
node = obj_utils.create_test_node(self.context,
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None,
driver='fake-hardware')
self._start_service()
with mock.patch.object(self.service, '_spawn_worker',
autospec=True) as mock_spawn:
mock_spawn.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self._stop_service()
node.refresh()
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mgr_utils.mock_record_keepalive
class ContinueNodeDeployTestCase(mgr_utils.ServiceSetUpMixin,
db_base.DbTestCase):
def setUp(self):
super(ContinueNodeDeployTestCase, self).setUp()
self.deploy_start = {
'step': 'deploy_start', 'priority': 50, 'interface': 'deploy'}
self.deploy_end = {
'step': 'deploy_end', 'priority': 20, 'interface': 'deploy'}
self.deploy_steps = [self.deploy_start, self.deploy_end]
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_continue_node_deploy_worker_pool_full(self, mock_spawn):
# Test the appropriate exception is raised if the worker pool is full
prv_state = states.DEPLOYWAIT
tgt_prv_state = states.ACTIVE
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None)
self._start_service()
mock_spawn.side_effect = exception.NoFreeConductorWorker()
self.assertRaises(exception.NoFreeConductorWorker,
self.service.continue_node_deploy,
self.context, node.uuid)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_continue_node_deploy_wrong_state(self, mock_spawn):
# Test the appropriate exception is raised if node isn't already
# in DEPLOYWAIT state
prv_state = states.DEPLOYFAIL
tgt_prv_state = states.ACTIVE
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None)
self._start_service()
self.assertRaises(exception.InvalidStateRequested,
self.service.continue_node_deploy,
self.context, node.uuid)
self._stop_service()
node.refresh()
# Make sure node wasn't modified
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_continue_node_deploy(self, mock_spawn):
# test a node can continue deploying via RPC
prv_state = states.DEPLOYWAIT
tgt_prv_state = states.ACTIVE
driver_info = {'deploy_steps': self.deploy_steps,
'deploy_step_index': 0}
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None,
driver_internal_info=driver_info,
deploy_step=self.deploy_steps[0])
self._start_service()
self.service.continue_node_deploy(self.context, node.uuid)
self._stop_service()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
mock_spawn.assert_called_with(mock.ANY,
deployments.do_next_deploy_step,
mock.ANY, 1, mock.ANY)
@mock.patch.object(task_manager.TaskManager, 'process_event',
autospec=True)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_continue_node_deploy_deprecated(self, mock_spawn, mock_event):
# TODO(rloo): delete this when we remove support for handling
# deploy steps; node will always be in DEPLOYWAIT then.
# test a node can continue deploying via RPC
prv_state = states.DEPLOYING
tgt_prv_state = states.ACTIVE
driver_info = {'deploy_steps': self.deploy_steps,
'deploy_step_index': 0}
self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None,
driver_internal_info=driver_info,
deploy_step=self.deploy_steps[0])
self.service.continue_node_deploy(self.context, node.uuid)
self._stop_service()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
mock_spawn.assert_called_with(mock.ANY,
deployments.do_next_deploy_step,
mock.ANY, 1, mock.ANY)
self.assertFalse(mock_event.called)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def _continue_node_deploy_skip_step(self, mock_spawn, skip=True):
# test that skipping current step mechanism works
driver_info = {'deploy_steps': self.deploy_steps,
'deploy_step_index': 0}
if not skip:
driver_info['skip_current_deploy_step'] = skip
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DEPLOYWAIT,
target_provision_state=states.MANAGEABLE,
driver_internal_info=driver_info, deploy_step=self.deploy_steps[0])
self._start_service()
self.service.continue_node_deploy(self.context, node.uuid)
self._stop_service()
node.refresh()
if skip:
expected_step_index = 1
else:
self.assertNotIn(
'skip_current_deploy_step', node.driver_internal_info)
expected_step_index = 0
mock_spawn.assert_called_with(mock.ANY,
deployments.do_next_deploy_step,
mock.ANY, expected_step_index, mock.ANY)
def test_continue_node_deploy_skip_step(self):
self._continue_node_deploy_skip_step()
def test_continue_node_deploy_no_skip_step(self):
self._continue_node_deploy_skip_step(skip=False)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_continue_node_deploy_polling(self, mock_spawn):
# test that deployment_polling flag is cleared
driver_info = {'deploy_steps': self.deploy_steps,
'deploy_step_index': 0,
'deployment_polling': True}
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DEPLOYWAIT,
target_provision_state=states.MANAGEABLE,
driver_internal_info=driver_info, deploy_step=self.deploy_steps[0])
self._start_service()
self.service.continue_node_deploy(self.context, node.uuid)
self._stop_service()
node.refresh()
self.assertNotIn('deployment_polling', node.driver_internal_info)
mock_spawn.assert_called_with(mock.ANY,
deployments.do_next_deploy_step,
mock.ANY, 1, mock.ANY)
@mgr_utils.mock_record_keepalive
class CheckTimeoutsTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.clean_up')
def test__check_deploy_timeouts(self, mock_cleanup):
self._start_service()
CONF.set_override('deploy_callback_timeout', 1, group='conductor')
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DEPLOYWAIT,
target_provision_state=states.ACTIVE,
provision_updated_at=datetime.datetime(2000, 1, 1, 0, 0))
self.service._check_deploy_timeouts(self.context)
self._stop_service()
node.refresh()
self.assertEqual(states.DEPLOYFAIL, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
self.assertIsNotNone(node.last_error)
mock_cleanup.assert_called_once_with(mock.ANY)
def _check_cleanwait_timeouts(self, manual=False):
self._start_service()
CONF.set_override('clean_callback_timeout', 1, group='conductor')
tgt_prov_state = states.MANAGEABLE if manual else states.AVAILABLE
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.CLEANWAIT,
target_provision_state=tgt_prov_state,
provision_updated_at=datetime.datetime(2000, 1, 1, 0, 0),
clean_step={
'interface': 'deploy',
'step': 'erase_devices'},
driver_internal_info={
'cleaning_reboot': manual,
'clean_step_index': 0})
self.service._check_cleanwait_timeouts(self.context)
self._stop_service()
node.refresh()
self.assertEqual(states.CLEANFAIL, node.provision_state)
self.assertEqual(tgt_prov_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Test that cleaning parameters have been purged in order
# to prevent looping of the cleaning sequence
self.assertEqual({}, node.clean_step)
self.assertNotIn('clean_step_index', node.driver_internal_info)
self.assertNotIn('cleaning_reboot', node.driver_internal_info)
def test__check_cleanwait_timeouts_automated_clean(self):
self._check_cleanwait_timeouts()
def test__check_cleanwait_timeouts_manual_clean(self):
self._check_cleanwait_timeouts(manual=True)
@mock.patch('ironic.drivers.modules.fake.FakeRescue.clean_up')
@mock.patch.object(conductor_utils, 'node_power_action')
def test_check_rescuewait_timeouts(self, node_power_mock,
mock_clean_up):
self._start_service()
CONF.set_override('rescue_callback_timeout', 1, group='conductor')
tgt_prov_state = states.RESCUE
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
rescue_interface='fake',
network_interface='flat',
provision_state=states.RESCUEWAIT,
target_provision_state=tgt_prov_state,
provision_updated_at=datetime.datetime(2000, 1, 1, 0, 0))
self.service._check_rescuewait_timeouts(self.context)
self._stop_service()
node.refresh()
self.assertEqual(states.RESCUEFAIL, node.provision_state)
self.assertEqual(tgt_prov_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
self.assertIn('Timeout reached while waiting for rescue ramdisk',
node.last_error)
mock_clean_up.assert_called_once_with(mock.ANY)
node_power_mock.assert_called_once_with(mock.ANY, states.POWER_OFF)
@mgr_utils.mock_record_keepalive
class DoNodeTearDownTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
def test_do_node_tear_down_invalid_state(self):
self._start_service()
# test node.provision_state is incorrect for tear_down
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=states.AVAILABLE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_tear_down,
self.context, node['uuid'])
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
def test_do_node_tear_down_protected(self):
self._start_service()
# test node.provision_state is incorrect for tear_down
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
protected=True)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_tear_down,
self.context, node['uuid'])
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeProtected, exc.exc_info[0])
@mock.patch('ironic.drivers.modules.fake.FakePower.validate')
def test_do_node_tear_down_validate_fail(self, mock_validate):
# InvalidParameterValue should be re-raised as InstanceDeployFailure
mock_validate.side_effect = exception.InvalidParameterValue('error')
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_tear_down,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InstanceDeployFailure, exc.exc_info[0])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def test_do_node_tear_down_driver_raises_error(self, mock_tear_down):
# test when driver.deploy.tear_down raises exception
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_info={'foo': 'bar'},
driver_internal_info={'is_whole_disk_image': False})
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
mock_tear_down.side_effect = exception.InstanceDeployFailure('test')
self.assertRaises(exception.InstanceDeployFailure,
self.service._do_node_tear_down, task,
node.provision_state)
node.refresh()
self.assertEqual(states.ERROR, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Assert instance_info was erased
self.assertEqual({}, node.instance_info)
mock_tear_down.assert_called_once_with(task)
@mock.patch('ironic.drivers.modules.fake.FakeConsole.stop_console')
def test_do_node_tear_down_console_raises_error(self, mock_console):
# test when _set_console_mode raises exception
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_info={'foo': 'bar'},
console_enabled=True,
driver_internal_info={'is_whole_disk_image': False})
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
mock_console.side_effect = exception.ConsoleError('test')
self.assertRaises(exception.ConsoleError,
self.service._do_node_tear_down, task,
node.provision_state)
node.refresh()
self.assertEqual(states.ERROR, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Assert instance_info was erased
self.assertEqual({}, node.instance_info)
mock_console.assert_called_once_with(task)
# TODO(TheJulia): Since we're functionally bound to neutron support
# by default, the fake drivers still invoke neutron.
@mock.patch('ironic.drivers.modules.fake.FakeConsole.stop_console')
@mock.patch('ironic.common.neutron.unbind_neutron_port')
@mock.patch('ironic.conductor.cleaning.do_node_clean')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def _test__do_node_tear_down_ok(self, mock_tear_down, mock_clean,
mock_unbind, mock_console,
enabled_console=False,
with_allocation=False):
# test when driver.deploy.tear_down succeeds
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_uuid=(uuidutils.generate_uuid()
if not with_allocation else None),
instance_info={'foo': 'bar'},
console_enabled=enabled_console,
driver_internal_info={'is_whole_disk_image': False,
'clean_steps': {},
'root_uuid_or_disk_id': 'foo',
'instance': {'ephemeral_gb': 10}})
port = obj_utils.create_test_port(
self.context, node_id=node.id,
internal_info={'tenant_vif_port_id': 'foo'})
if with_allocation:
alloc = obj_utils.create_test_allocation(self.context)
# Establish cross-linking between the node and the allocation
alloc.node_id = node.id
alloc.save()
node.refresh()
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
self.service._do_node_tear_down(task, node.provision_state)
node.refresh()
port.refresh()
# Node will be moved to AVAILABLE after cleaning, not tested here
self.assertEqual(states.CLEANING, node.provision_state)
self.assertEqual(states.AVAILABLE, node.target_provision_state)
self.assertIsNone(node.last_error)
self.assertIsNone(node.instance_uuid)
self.assertIsNone(node.allocation_id)
self.assertEqual({}, node.instance_info)
self.assertNotIn('instance', node.driver_internal_info)
self.assertNotIn('clean_steps', node.driver_internal_info)
self.assertNotIn('root_uuid_or_disk_id', node.driver_internal_info)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
mock_tear_down.assert_called_once_with(task)
mock_clean.assert_called_once_with(task)
self.assertEqual({}, port.internal_info)
mock_unbind.assert_called_once_with('foo', context=mock.ANY)
if enabled_console:
mock_console.assert_called_once_with(task)
else:
self.assertFalse(mock_console.called)
if with_allocation:
self.assertRaises(exception.AllocationNotFound,
objects.Allocation.get_by_id,
self.context, alloc.id)
def test__do_node_tear_down_ok_without_console(self):
self._test__do_node_tear_down_ok(enabled_console=False)
def test__do_node_tear_down_ok_with_console(self):
self._test__do_node_tear_down_ok(enabled_console=True)
def test__do_node_tear_down_with_allocation(self):
self._test__do_node_tear_down_ok(with_allocation=True)
@mock.patch('ironic.drivers.modules.fake.FakeRescue.clean_up')
@mock.patch('ironic.conductor.cleaning.do_node_clean')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def _test_do_node_tear_down_from_state(self, init_state, is_rescue_state,
mock_tear_down, mock_clean,
mock_rescue_clean):
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
uuid=uuidutils.generate_uuid(),
provision_state=init_state,
target_provision_state=states.AVAILABLE,
driver_internal_info={'is_whole_disk_image': False})
self._start_service()
self.service.do_node_tear_down(self.context, node.uuid)
self._stop_service()
node.refresh()
# Node will be moved to AVAILABLE after cleaning, not tested here
self.assertEqual(states.CLEANING, node.provision_state)
self.assertEqual(states.AVAILABLE, node.target_provision_state)
self.assertIsNone(node.last_error)
self.assertEqual({}, node.instance_info)
mock_tear_down.assert_called_once_with(mock.ANY)
mock_clean.assert_called_once_with(mock.ANY)
if is_rescue_state:
mock_rescue_clean.assert_called_once_with(mock.ANY)
else:
self.assertFalse(mock_rescue_clean.called)
def test__do_node_tear_down_from_valid_states(self):
valid_states = [states.ACTIVE, states.DEPLOYWAIT, states.DEPLOYFAIL,
states.ERROR]
for state in valid_states:
self._test_do_node_tear_down_from_state(state, False)
valid_rescue_states = [states.RESCUEWAIT, states.RESCUE,
states.UNRESCUEFAIL, states.RESCUEFAIL]
for state in valid_rescue_states:
self._test_do_node_tear_down_from_state(state, True)
# NOTE(tenbrae): partial tear-down was broken. A node left in a state of
# DELETING could not have tear_down called on it a second
# time Thus, I have removed the unit test, which faultily
# asserted only that a node could be left in a state of
# incomplete deletion -- not that such a node's deletion
# could later be completed.
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_do_node_tear_down_worker_pool_full(self, mock_spawn):
prv_state = states.ACTIVE
tgt_prv_state = states.NOSTATE
fake_instance_info = {'foo': 'bar'}
driver_internal_info = {'is_whole_disk_image': False}
node = obj_utils.create_test_node(
self.context, driver='fake-hardware', provision_state=prv_state,
target_provision_state=tgt_prv_state,
instance_info=fake_instance_info,
driver_internal_info=driver_internal_info, last_error=None)
self._start_service()
mock_spawn.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_tear_down,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self._stop_service()
node.refresh()
# Assert instance_info/driver_internal_info was not touched
self.assertEqual(fake_instance_info, node.instance_info)
self.assertEqual(driver_internal_info, node.driver_internal_info)
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mgr_utils.mock_record_keepalive
class DoProvisioningActionTestCase(mgr_utils.ServiceSetUpMixin,
db_base.DbTestCase):
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_do_provisioning_action_worker_pool_full(self, mock_spawn):
prv_state = states.MANAGEABLE
tgt_prv_state = states.NOSTATE
node = obj_utils.create_test_node(self.context, driver='fake-hardware',
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None)
self._start_service()
mock_spawn.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_provisioning_action,
self.context, node.uuid, 'provide')
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self._stop_service()
node.refresh()
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_do_provision_action_provide(self, mock_spawn):
# test when a node is cleaned going from manageable to available
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.MANAGEABLE,
target_provision_state=states.AVAILABLE)
self._start_service()
self.service.do_provisioning_action(self.context, node.uuid, 'provide')
node.refresh()
# Node will be moved to AVAILABLE after cleaning, not tested here
self.assertEqual(states.CLEANING, node.provision_state)
self.assertEqual(states.AVAILABLE, node.target_provision_state)
self.assertIsNone(node.last_error)
mock_spawn.assert_called_with(self.service,
cleaning.do_node_clean, mock.ANY)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_do_provision_action_provide_in_maintenance(self, mock_spawn):
CONF.set_override('allow_provisioning_in_maintenance', False,
group='conductor')
# test when a node is cleaned going from manageable to available
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.MANAGEABLE,
target_provision_state=None,
maintenance=True)
self._start_service()
mock_spawn.reset_mock()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_provisioning_action,
self.context, node.uuid, 'provide')
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeInMaintenance, exc.exc_info[0])
node.refresh()
self.assertEqual(states.MANAGEABLE, node.provision_state)
self.assertIsNone(node.target_provision_state)
self.assertIsNone(node.last_error)
self.assertFalse(mock_spawn.called)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def test_do_provision_action_manage(self, mock_spawn):
# test when a node is verified going from enroll to manageable
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.ENROLL,
target_provision_state=states.MANAGEABLE)
self._start_service()
self.service.do_provisioning_action(self.context, node.uuid, 'manage')
node.refresh()
# Node will be moved to MANAGEABLE after verification, not tested here
self.assertEqual(states.VERIFYING, node.provision_state)
self.assertEqual(states.MANAGEABLE, node.target_provision_state)
self.assertIsNone(node.last_error)
mock_spawn.assert_called_with(self.service,
self.service._do_node_verify, mock.ANY)
@mock.patch('ironic.conductor.manager.ConductorManager._spawn_worker',
autospec=True)
def _do_provision_action_abort(self, mock_spawn, manual=False):
tgt_prov_state = states.MANAGEABLE if manual else states.AVAILABLE
node = obj_utils.create_test_node(
self.context, driver='fake-hardware',
provision_state=states.CLEANWAIT,
target_provision_state=tgt_prov_state)
self._start_service()
self.service.do_provisioning_action(self.context, node.uuid, 'abort')
node.refresh()
# Node will be moved to tgt_prov_state after cleaning, not tested here
self.assertEqual(states.CLEANFAIL, node.provision_state)
self.assertEqual(tgt_prov_state, node.target_provision_state)
self.assertIsNone(node.last_error)
mock_spawn.assert_called_with(
self.service, cleaning.do_node_clean_abort, mock.