Import Ironic Driver & supporting files - part 4
Import the Ironic virt driver and supporting files (driver part 2 - validation, spawn, destroy, further getters), as well as relevant unit tests, as of commit da967d77894be6f23d81fb5cc948f9d13898ba84 This is the dicing up of review/103167 into smaller chunks ready for review. Incorporating the bug fix https://review.openstack.org/#/c/118189 Change-Id: If1d966c937c56a8dbf27588b94462c0a71759cca Co-authored-by: Adam Gandelman <adamg@ubuntu.com> Co-authored-by: Andrey Kurilin <akurilin@mirantis.com> Co-authored-by: ChangBo Guo(gcb) <eric.guo@easystack.cn> Co-authored-by: Chris Behrens <cbehrens@codestud.com> Co-authored-by: Chris Krelle <nobodycam@gmail.com> Co-authored-by: David Shrewsbury <shrewsbury.dave@gmail.com> Co-authored-by: Devananda van der Veen <devananda.vdv@gmail.com> Co-authored-by: Dmitry Tantsur <dtantsur@redhat.com> Co-authored-by: Jim Rollenhagen <jim@jimrollenhagen.com> Co-authored-by: Lucas Alvares Gomes <lucasagomes@gmail.com> Co-authored-by: Matthew Gilliard <matthew.gilliard@hp.com> Co-authored-by: Mikhail Durnosvistov <mdurnosvistov@mirantis.com> Co-authored-by: Pablo Fernando Cargnelutti <pablo.fernando.cargnelutti@intel.com> Co-authored-by: Robert Collins <rbtcollins@hp.com> Co-authored-by: ryo.kurahashi <kurahashi-rxa@necst.nec.co.jp>
This commit is contained in:
committed by
Sean Dague
parent
9864a729fa
commit
bceb634299
@@ -21,8 +21,11 @@ from oslo.config import cfg
|
||||
|
||||
from nova.compute import power_state as nova_states
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova.objects import flavor as flavor_obj
|
||||
from nova.objects import instance as instance_obj
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova.openstack.common import loopingcall
|
||||
from nova.openstack.common import uuidutils
|
||||
from nova import test
|
||||
from nova.tests import fake_instance
|
||||
@@ -97,6 +100,50 @@ class IronicDriverTestCase(test.NoDBTestCase, test_driver.DriverAPITestHelper):
|
||||
def test__get_hypervisor_version(self):
|
||||
self.assertEqual(1, self.driver._get_hypervisor_version())
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid')
|
||||
def test__validate_instance_and_node(self, mock_gbiui):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
instance_uuid = uuidutils.generate_uuid()
|
||||
node = ironic_utils.get_test_node(uuid=node_uuid,
|
||||
instance_uuid=instance_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
uuid=instance_uuid)
|
||||
icli = cw.IronicClientWrapper()
|
||||
|
||||
mock_gbiui.return_value = node
|
||||
result = ironic_driver._validate_instance_and_node(icli, instance)
|
||||
self.assertEqual(result.uuid, node_uuid)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid')
|
||||
def test__validate_instance_and_node_failed(self, mock_gbiui):
|
||||
icli = cw.IronicClientWrapper()
|
||||
mock_gbiui.side_effect = ironic_exception.NotFound()
|
||||
instance_uuid = uuidutils.generate_uuid(),
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
uuid=instance_uuid)
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
ironic_driver._validate_instance_and_node,
|
||||
icli, instance)
|
||||
|
||||
def test__node_resource(self):
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
instance_uuid = uuidutils.generate_uuid()
|
||||
props = _get_properties()
|
||||
stats = _get_stats()
|
||||
node = ironic_utils.get_test_node(uuid=node_uuid,
|
||||
instance_uuid=instance_uuid,
|
||||
properties=props)
|
||||
|
||||
result = self.driver._node_resource(node)
|
||||
self.assertEqual(props['cpus'], result['vcpus'])
|
||||
self.assertEqual(props['cpus'], result['vcpus_used'])
|
||||
self.assertEqual(props['memory_mb'], result['memory_mb'])
|
||||
self.assertEqual(props['memory_mb'], result['memory_mb_used'])
|
||||
self.assertEqual(props['local_gb'], result['local_gb'])
|
||||
self.assertEqual(props['local_gb'], result['local_gb_used'])
|
||||
self.assertEqual(node_uuid, result['hypervisor_hostname'])
|
||||
self.assertEqual(stats, jsonutils.loads(result['stats']))
|
||||
|
||||
def test__node_resource_canonicalizes_arch(self):
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
props = _get_properties()
|
||||
@@ -116,6 +163,70 @@ class IronicDriverTestCase(test.NoDBTestCase, test_driver.DriverAPITestHelper):
|
||||
result = self.driver._node_resource(node)
|
||||
self.assertEqual([], jsonutils.loads(result['supported_instances']))
|
||||
|
||||
def test__node_resource_exposes_capabilities(self):
|
||||
props = _get_properties()
|
||||
props['capabilities'] = 'test:capability'
|
||||
node = ironic_utils.get_test_node(properties=props)
|
||||
result = self.driver._node_resource(node)
|
||||
stats = jsonutils.loads(result['stats'])
|
||||
self.assertIsNone(stats.get('capabilities'))
|
||||
self.assertEqual('capability', stats.get('test'))
|
||||
|
||||
def test__node_resource_no_capabilities(self):
|
||||
props = _get_properties()
|
||||
props['capabilities'] = None
|
||||
node = ironic_utils.get_test_node(properties=props)
|
||||
result = self.driver._node_resource(node)
|
||||
self.assertIsNone(jsonutils.loads(result['stats']).get('capabilities'))
|
||||
|
||||
def test__node_resource_malformed_capabilities(self):
|
||||
props = _get_properties()
|
||||
props['capabilities'] = 'test:capability,:no_key,no_val:'
|
||||
node = ironic_utils.get_test_node(properties=props)
|
||||
result = self.driver._node_resource(node)
|
||||
stats = jsonutils.loads(result['stats'])
|
||||
self.assertEqual('capability', stats.get('test'))
|
||||
|
||||
def test__node_resource_no_instance_uuid(self):
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
props = _get_properties()
|
||||
stats = _get_stats()
|
||||
node = ironic_utils.get_test_node(uuid=node_uuid,
|
||||
instance_uuid=None,
|
||||
power_state=ironic_states.POWER_OFF,
|
||||
properties=props)
|
||||
|
||||
result = self.driver._node_resource(node)
|
||||
self.assertEqual(props['cpus'], result['vcpus'])
|
||||
self.assertEqual(0, result['vcpus_used'])
|
||||
self.assertEqual(props['memory_mb'], result['memory_mb'])
|
||||
self.assertEqual(0, result['memory_mb_used'])
|
||||
self.assertEqual(props['local_gb'], result['local_gb'])
|
||||
self.assertEqual(0, result['local_gb_used'])
|
||||
self.assertEqual(node_uuid, result['hypervisor_hostname'])
|
||||
self.assertEqual(stats, jsonutils.loads(result['stats']))
|
||||
|
||||
@mock.patch.object(ironic_driver.IronicDriver,
|
||||
'_node_resources_unavailable')
|
||||
def test__node_resource_unavailable_node_res(self, mock_res_unavail):
|
||||
mock_res_unavail.return_value = True
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
props = _get_properties()
|
||||
stats = _get_stats()
|
||||
node = ironic_utils.get_test_node(uuid=node_uuid,
|
||||
instance_uuid=None,
|
||||
properties=props)
|
||||
|
||||
result = self.driver._node_resource(node)
|
||||
self.assertEqual(0, result['vcpus'])
|
||||
self.assertEqual(0, result['vcpus_used'])
|
||||
self.assertEqual(0, result['memory_mb'])
|
||||
self.assertEqual(0, result['memory_mb_used'])
|
||||
self.assertEqual(0, result['local_gb'])
|
||||
self.assertEqual(0, result['local_gb_used'])
|
||||
self.assertEqual(node_uuid, result['hypervisor_hostname'])
|
||||
self.assertEqual(stats, jsonutils.loads(result['stats']))
|
||||
|
||||
@mock.patch.object(cw.IronicClientWrapper, 'call')
|
||||
def test_instance_exists(self, mock_call):
|
||||
instance_uuid = 'fake-uuid'
|
||||
@@ -182,6 +293,28 @@ class IronicDriverTestCase(test.NoDBTestCase, test_driver.DriverAPITestHelper):
|
||||
mock_get.side_effect = ironic_exception.NotFound
|
||||
self.assertFalse(self.driver.node_is_available(node.uuid))
|
||||
|
||||
def test__node_resources_unavailable(self):
|
||||
node_dicts = [
|
||||
# a node in maintenance /w no instance and power OFF
|
||||
{'uuid': uuidutils.generate_uuid(),
|
||||
'maintenance': True,
|
||||
'power_state': ironic_states.POWER_OFF},
|
||||
# a node in maintenance /w no instance and ERROR power state
|
||||
{'uuid': uuidutils.generate_uuid(),
|
||||
'maintenance': True,
|
||||
'power_state': ironic_states.ERROR},
|
||||
# a node not in maintenance /w no instance and bad power state
|
||||
{'uuid': uuidutils.generate_uuid(),
|
||||
'power_state': ironic_states.NOSTATE},
|
||||
]
|
||||
for n in node_dicts:
|
||||
node = ironic_utils.get_test_node(**n)
|
||||
self.assertTrue(self.driver._node_resources_unavailable(node))
|
||||
|
||||
avail_node = ironic_utils.get_test_node(
|
||||
power_state=ironic_states.POWER_OFF)
|
||||
self.assertFalse(self.driver._node_resources_unavailable(avail_node))
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'list')
|
||||
def test_get_available_nodes(self, mock_list):
|
||||
node_dicts = [
|
||||
@@ -252,6 +385,370 @@ class IronicDriverTestCase(test.NoDBTestCase, test_driver.DriverAPITestHelper):
|
||||
result = self.driver.get_info(instance)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
def test_macs_for_instance(self, mock_node):
|
||||
node = ironic_utils.get_test_node()
|
||||
port = ironic_utils.get_test_port()
|
||||
mock_node.get.return_value = node
|
||||
mock_node.list_ports.return_value = [port]
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
node=node.uuid)
|
||||
result = self.driver.macs_for_instance(instance)
|
||||
self.assertEqual(set([port.address]), result)
|
||||
mock_node.list_ports.assert_called_once_with(node.uuid)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'get')
|
||||
def test_macs_for_instance_http_not_found(self, mock_get):
|
||||
mock_get.side_effect = ironic_exception.NotFound()
|
||||
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.ctx, node=uuidutils.generate_uuid())
|
||||
result = self.driver.macs_for_instance(instance)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch.object(instance_obj.Instance, 'save')
|
||||
@mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_add_driver_fields')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
|
||||
def test_spawn(self, mock_sf, mock_pvifs, mock_adf, mock_wait_active,
|
||||
mock_fg_bid, mock_node, mock_looping, mock_save):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
fake_flavor = {'ephemeral_gb': 0}
|
||||
|
||||
mock_node.get.return_value = node
|
||||
mock_node.validate.return_value = ironic_utils.get_test_validation()
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
mock_node.set_provision_state.return_value = mock.MagicMock()
|
||||
mock_fg_bid.return_value = fake_flavor
|
||||
|
||||
fake_looping_call = FakeLoopingCall()
|
||||
mock_looping.return_value = fake_looping_call
|
||||
|
||||
self.driver.spawn(self.ctx, instance, None, [], None)
|
||||
|
||||
mock_node.get.assert_called_once_with(node_uuid)
|
||||
mock_node.validate.assert_called_once_with(node_uuid)
|
||||
mock_fg_bid.assert_called_once_with(self.ctx,
|
||||
instance['instance_type_id'])
|
||||
mock_adf.assert_called_once_with(node, instance, None, fake_flavor)
|
||||
mock_pvifs.assert_called_once_with(node, instance, None)
|
||||
mock_sf.assert_called_once_with(instance, None)
|
||||
mock_node.set_provision_state.assert_called_once_with(node_uuid,
|
||||
'active')
|
||||
|
||||
self.assertIsNone(instance['default_ephemeral_device'])
|
||||
self.assertFalse(mock_save.called)
|
||||
|
||||
mock_looping.assert_called_once_with(mock_wait_active,
|
||||
FAKE_CLIENT_WRAPPER,
|
||||
instance)
|
||||
fake_looping_call.start.assert_called_once_with(
|
||||
interval=CONF.ironic.api_retry_interval)
|
||||
fake_looping_call.wait.assert_called_once()
|
||||
|
||||
@mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, 'destroy')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_add_driver_fields')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
|
||||
def test_spawn_destroyed_after_failure(self, mock_sf, mock_pvifs, mock_adf,
|
||||
mock_wait_active, mock_destroy,
|
||||
mock_fg_bid, mock_node,
|
||||
mock_looping):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
fake_flavor = {'ephemeral_gb': 0}
|
||||
|
||||
mock_node.get.return_value = node
|
||||
mock_node.validate.return_value = ironic_utils.get_test_validation()
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
mock_node.set_provision_state.return_value = mock.MagicMock()
|
||||
mock_fg_bid.return_value = fake_flavor
|
||||
|
||||
fake_looping_call = FakeLoopingCall()
|
||||
mock_looping.return_value = fake_looping_call
|
||||
|
||||
deploy_exc = exception.InstanceDeployFailure('foo')
|
||||
fake_looping_call.wait.side_effect = deploy_exc
|
||||
self.assertRaises(
|
||||
exception.InstanceDeployFailure,
|
||||
self.driver.spawn, self.ctx, instance, None, [], None)
|
||||
mock_destroy.assert_called_once_with(self.ctx, instance, None)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'update')
|
||||
def test__add_driver_fields_good(self, mock_update):
|
||||
node = ironic_utils.get_test_node(driver='fake')
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
node=node.uuid)
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
flavor = ironic_utils.get_test_flavor()
|
||||
self.driver._add_driver_fields(node, instance, image_meta, flavor)
|
||||
expected_patch = [{'path': '/instance_info/image_source', 'op': 'add',
|
||||
'value': image_meta['id']},
|
||||
{'path': '/instance_info/root_gb', 'op': 'add',
|
||||
'value': str(instance['root_gb'])},
|
||||
{'path': '/instance_info/swap_mb', 'op': 'add',
|
||||
'value': str(flavor['swap'])},
|
||||
{'path': '/instance_uuid', 'op': 'add',
|
||||
'value': instance['uuid']}]
|
||||
mock_update.assert_called_once_with(node.uuid, expected_patch)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'update')
|
||||
def test__add_driver_fields_fail(self, mock_update):
|
||||
mock_update.side_effect = ironic_exception.BadRequest()
|
||||
node = ironic_utils.get_test_node(driver='fake')
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
node=node.uuid)
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
flavor = ironic_utils.get_test_flavor()
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.driver._add_driver_fields,
|
||||
node, instance, image_meta, flavor)
|
||||
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'update')
|
||||
def test__cleanup_deploy_good(self, mock_update, mock_flavor):
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor(extra_specs={})
|
||||
node = ironic_utils.get_test_node(driver='fake',
|
||||
instance_uuid='fake-id')
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
node=node.uuid)
|
||||
self.driver._cleanup_deploy(node, instance, None)
|
||||
expected_patch = [{'path': '/instance_uuid', 'op': 'remove'}]
|
||||
mock_update.assert_called_once_with(node.uuid, expected_patch)
|
||||
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'update')
|
||||
def test__cleanup_deploy_fail(self, mock_update, mock_flavor):
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor(extra_specs={})
|
||||
mock_update.side_effect = ironic_exception.BadRequest()
|
||||
node = ironic_utils.get_test_node(driver='fake',
|
||||
instance_uuid='fake-id')
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
node=node.uuid)
|
||||
self.assertRaises(exception.InstanceTerminationFailure,
|
||||
self.driver._cleanup_deploy,
|
||||
node, instance, None)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
def test_spawn_node_driver_validation_fail(self, mock_flavor, mock_node):
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor()
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
|
||||
mock_node.validate.return_value = ironic_utils.get_test_validation(
|
||||
power=False, deploy=False)
|
||||
mock_node.get.return_value = node
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
|
||||
self.assertRaises(exception.ValidationError, self.driver.spawn,
|
||||
self.ctx, instance, image_meta, [], None)
|
||||
mock_node.get.assert_called_once_with(node_uuid)
|
||||
mock_node.validate.assert_called_once_with(node_uuid)
|
||||
mock_flavor.assert_called_with(mock.ANY, instance['instance_type_id'])
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
|
||||
def test_spawn_node_prepare_for_deploy_fail(self, mock_cleanup_deploy,
|
||||
mock_pvifs, mock_sf,
|
||||
mock_flavor, mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
mock_node.get.return_value = node
|
||||
mock_node.validate.return_value = ironic_utils.get_test_validation()
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor()
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
mock_sf.side_effect = TestException()
|
||||
self.assertRaises(TestException, self.driver.spawn,
|
||||
self.ctx, instance, image_meta, [], None)
|
||||
|
||||
mock_node.get.assert_called_once_with(node_uuid)
|
||||
mock_node.validate.assert_called_once_with(node_uuid)
|
||||
mock_flavor.assert_called_once_with(self.ctx,
|
||||
instance['instance_type_id'])
|
||||
mock_cleanup_deploy.assert_called_with(node, instance, None)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
|
||||
def test_spawn_node_trigger_deploy_fail(self, mock_cleanup_deploy,
|
||||
mock_pvifs, mock_sf,
|
||||
mock_flavor, mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor()
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
|
||||
mock_node.get.return_value = node
|
||||
mock_node.validate.return_value = ironic_utils.get_test_validation()
|
||||
|
||||
mock_node.set_provision_state.side_effect = exception.NovaException()
|
||||
self.assertRaises(exception.NovaException, self.driver.spawn,
|
||||
self.ctx, instance, image_meta, [], None)
|
||||
|
||||
mock_node.get.assert_called_once_with(node_uuid)
|
||||
mock_node.validate.assert_called_once_with(node_uuid)
|
||||
mock_flavor.assert_called_once_with(self.ctx,
|
||||
instance['instance_type_id'])
|
||||
mock_cleanup_deploy.assert_called_once_with(node, instance, None)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
|
||||
def test_spawn_node_trigger_deploy_fail2(self, mock_cleanup_deploy,
|
||||
mock_pvifs, mock_sf,
|
||||
mock_flavor, mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor()
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
|
||||
mock_node.get.return_value = node
|
||||
mock_node.validate.return_value = ironic_utils.get_test_validation()
|
||||
mock_node.set_provision_state.side_effect = ironic_exception.BadRequest
|
||||
self.assertRaises(ironic_exception.BadRequest,
|
||||
self.driver.spawn,
|
||||
self.ctx, instance, image_meta, [], None)
|
||||
|
||||
mock_node.get.assert_called_once_with(node_uuid)
|
||||
mock_node.validate.assert_called_once_with(node_uuid)
|
||||
mock_flavor.assert_called_once_with(self.ctx,
|
||||
instance['instance_type_id'])
|
||||
mock_cleanup_deploy.assert_called_once_with(node, instance, None)
|
||||
|
||||
@mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
|
||||
@mock.patch.object(instance_obj.Instance, 'save')
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(flavor_obj.Flavor, 'get_by_id')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
|
||||
def test_spawn_sets_default_ephemeral_device(self, mock_sf, mock_pvifs,
|
||||
mock_wait, mock_flavor,
|
||||
mock_node, mock_save,
|
||||
mock_looping):
|
||||
mock_flavor.return_value = ironic_utils.get_test_flavor(ephemeral_gb=1)
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
mock_node.set_provision_state.return_value = mock.MagicMock()
|
||||
image_meta = ironic_utils.get_test_image_meta()
|
||||
|
||||
self.driver.spawn(self.ctx, instance, image_meta, [], None)
|
||||
mock_flavor.assert_called_once_with(self.ctx,
|
||||
instance['instance_type_id'])
|
||||
self.assertTrue(mock_save.called)
|
||||
self.assertEqual('/dev/sda1', instance['default_ephemeral_device'])
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
|
||||
def test_destroy(self, mock_cleanup_deploy, mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
network_info = 'foo'
|
||||
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
|
||||
provision_state=ironic_states.ACTIVE)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
|
||||
def fake_set_provision_state(*_):
|
||||
node.provision_state = None
|
||||
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
mock_node.set_provision_state.side_effect = fake_set_provision_state
|
||||
self.driver.destroy(self.ctx, instance, network_info, None)
|
||||
mock_node.set_provision_state.assert_called_once_with(node_uuid,
|
||||
'deleted')
|
||||
mock_node.get_by_instance_uuid.assert_called_with(instance.uuid)
|
||||
mock_cleanup_deploy.assert_called_with(node, instance, network_info)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
|
||||
def test_destroy_ignore_unexpected_state(self, mock_cleanup_deploy,
|
||||
mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
network_info = 'foo'
|
||||
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
|
||||
provision_state=ironic_states.DELETING)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
self.driver.destroy(self.ctx, instance, network_info, None)
|
||||
self.assertFalse(mock_node.set_provision_state.called)
|
||||
mock_node.get_by_instance_uuid.assert_called_with(instance.uuid)
|
||||
mock_cleanup_deploy.assert_called_with(node, instance, network_info)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'set_provision_state')
|
||||
@mock.patch.object(ironic_driver, '_validate_instance_and_node')
|
||||
def test_destroy_trigger_undeploy_fail(self, fake_validate, mock_sps):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
|
||||
provision_state=ironic_states.ACTIVE)
|
||||
fake_validate.return_value = node
|
||||
instance = fake_instance.fake_instance_obj(self.ctx,
|
||||
node=node_uuid)
|
||||
mock_sps.side_effect = exception.NovaException()
|
||||
self.assertRaises(exception.NovaException, self.driver.destroy,
|
||||
self.ctx, instance, None, None)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
def test_destroy_unprovision_fail(self, mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
|
||||
provision_state=ironic_states.ACTIVE)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
|
||||
def fake_set_provision_state(*_):
|
||||
node.provision_state = ironic_states.ERROR
|
||||
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
self.assertRaises(exception.NovaException, self.driver.destroy,
|
||||
self.ctx, instance, None, None)
|
||||
mock_node.set_provision_state.assert_called_once_with(node_uuid,
|
||||
'deleted')
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT, 'node')
|
||||
def test_destroy_unassociate_fail(self, mock_node):
|
||||
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
|
||||
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
|
||||
provision_state=ironic_states.ACTIVE)
|
||||
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
|
||||
|
||||
mock_node.get_by_instance_uuid.return_value = node
|
||||
mock_node.update.side_effect = exception.NovaException()
|
||||
self.assertRaises(exception.NovaException, self.driver.destroy,
|
||||
self.ctx, instance, None, None)
|
||||
mock_node.set_provision_state.assert_called_once_with(node_uuid,
|
||||
'deleted')
|
||||
mock_node.get_by_instance_uuid.assert_called_with(instance.uuid)
|
||||
|
||||
@mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
|
||||
@mock.patch.object(ironic_driver, '_validate_instance_and_node')
|
||||
def test_reboot(self, mock_val_inst, mock_set_power):
|
||||
|
||||
@@ -24,19 +24,26 @@ import logging as py_logging
|
||||
|
||||
from ironicclient import exc as ironic_exception
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
|
||||
from nova.compute import arch
|
||||
from nova.compute import power_state
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.i18n import _LE
|
||||
from nova.i18n import _LW
|
||||
from nova.objects import flavor as flavor_obj
|
||||
from nova.objects import instance as instance_obj
|
||||
from nova.openstack.common import excutils
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import loopingcall
|
||||
from nova.virt import driver as virt_driver
|
||||
from nova.virt import firewall
|
||||
from nova.virt.ironic import client_wrapper
|
||||
from nova.virt.ironic import ironic_states
|
||||
from nova.virt.ironic import patcher
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -113,6 +120,21 @@ def _get_nodes_supported_instances(cpu_arch=None):
|
||||
return [(cpu_arch, 'baremetal', 'baremetal')]
|
||||
|
||||
|
||||
def _log_ironic_polling(what, node, instance):
|
||||
prov_state = (None if node.provision_state is None else
|
||||
'"%s"' % node.provision_state)
|
||||
tgt_prov_state = (None if node.target_provision_state is None else
|
||||
'"%s"' % node.target_provision_state)
|
||||
LOG.debug('Still waiting for ironic node %(node)s to %(what)s: '
|
||||
'provision_state=%(prov_state)s, '
|
||||
'target_provision_state=%(tgt_prov_state)s',
|
||||
dict(what=what,
|
||||
node=node.uuid,
|
||||
prov_state=prov_state,
|
||||
tgt_prov_state=tgt_prov_state),
|
||||
instance=instance)
|
||||
|
||||
|
||||
class IronicDriver(virt_driver.ComputeDriver):
|
||||
"""Hypervisor driver for Ironic - bare metal provisioning."""
|
||||
|
||||
@@ -224,6 +246,81 @@ class IronicDriver(virt_driver.ComputeDriver):
|
||||
dic.update(nodes_extra_specs)
|
||||
return dic
|
||||
|
||||
def _start_firewall(self, instance, network_info):
|
||||
self.firewall_driver.setup_basic_filtering(instance, network_info)
|
||||
self.firewall_driver.prepare_instance_filter(instance, network_info)
|
||||
self.firewall_driver.apply_instance_filter(instance, network_info)
|
||||
|
||||
def _stop_firewall(self, instance, network_info):
|
||||
self.firewall_driver.unfilter_instance(instance, network_info)
|
||||
|
||||
def _add_driver_fields(self, node, instance, image_meta, flavor,
|
||||
preserve_ephemeral=None):
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
patch = patcher.create(node).get_deploy_patch(instance,
|
||||
image_meta,
|
||||
flavor,
|
||||
preserve_ephemeral)
|
||||
|
||||
# Associate the node with an instance
|
||||
patch.append({'path': '/instance_uuid', 'op': 'add',
|
||||
'value': instance['uuid']})
|
||||
try:
|
||||
icli.call('node.update', node.uuid, patch)
|
||||
except ironic_exception.BadRequest:
|
||||
msg = (_("Failed to add deploy parameters on node %(node)s "
|
||||
"when provisioning the instance %(instance)s")
|
||||
% {'node': node.uuid, 'instance': instance['uuid']})
|
||||
LOG.error(msg)
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
def _cleanup_deploy(self, node, instance, network_info):
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
context = nova_context.get_admin_context()
|
||||
flavor = flavor_obj.Flavor.get_by_id(context,
|
||||
instance['instance_type_id'])
|
||||
patch = patcher.create(node).get_cleanup_patch(instance, network_info,
|
||||
flavor)
|
||||
|
||||
# Unassociate the node
|
||||
patch.append({'op': 'remove', 'path': '/instance_uuid'})
|
||||
try:
|
||||
icli.call('node.update', node.uuid, patch)
|
||||
except ironic_exception.BadRequest:
|
||||
LOG.error(_LE("Failed to clean up the parameters on node %(node)s "
|
||||
"when unprovisioning the instance %(instance)s"),
|
||||
{'node': node.uuid, 'instance': instance['uuid']})
|
||||
reason = (_("Fail to clean up node %s parameters") % node.uuid)
|
||||
raise exception.InstanceTerminationFailure(reason=reason)
|
||||
|
||||
self._unplug_vifs(node, instance, network_info)
|
||||
self._stop_firewall(instance, network_info)
|
||||
|
||||
def _wait_for_active(self, icli, instance):
|
||||
"""Wait for the node to be marked as ACTIVE in Ironic."""
|
||||
node = _validate_instance_and_node(icli, instance)
|
||||
if node.provision_state == ironic_states.ACTIVE:
|
||||
# job is done
|
||||
LOG.debug("Ironic node %(node)s is now ACTIVE",
|
||||
dict(node=node.uuid), instance=instance)
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if node.target_provision_state == ironic_states.DELETED:
|
||||
# ironic is trying to delete it now
|
||||
raise exception.InstanceNotFound(instance_id=instance['uuid'])
|
||||
|
||||
if node.provision_state == ironic_states.NOSTATE:
|
||||
# ironic already deleted it
|
||||
raise exception.InstanceNotFound(instance_id=instance['uuid'])
|
||||
|
||||
if node.provision_state == ironic_states.DEPLOYFAIL:
|
||||
# ironic failed to deploy
|
||||
msg = (_("Failed to provision instance %(inst)s: %(reason)s")
|
||||
% {'inst': instance['uuid'], 'reason': node.last_error})
|
||||
raise exception.InstanceDeployFailure(msg)
|
||||
|
||||
_log_ironic_polling('become ACTIVE', node, instance)
|
||||
|
||||
def init_host(self, host):
|
||||
"""Initialize anything that is necessary for the driver to function.
|
||||
|
||||
@@ -372,6 +469,189 @@ class IronicDriver(virt_driver.ComputeDriver):
|
||||
'cpu_time': 0
|
||||
}
|
||||
|
||||
def macs_for_instance(self, instance):
|
||||
"""List the MAC addresses of an instance.
|
||||
|
||||
List of MAC addresses for the node which this instance is
|
||||
associated with.
|
||||
|
||||
:param instance: the instance object.
|
||||
:return: None, or a set of MAC ids (e.g. set(['12:34:56:78:90:ab'])).
|
||||
None means 'no constraints', a set means 'these and only these
|
||||
MAC addresses'.
|
||||
"""
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
try:
|
||||
node = icli.call("node.get", instance['node'])
|
||||
except ironic_exception.NotFound:
|
||||
return None
|
||||
ports = icli.call("node.list_ports", node.uuid)
|
||||
return set([p.address for p in ports])
|
||||
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=None, block_device_info=None):
|
||||
"""Deploy an instance.
|
||||
|
||||
:param context: The security context.
|
||||
:param instance: The instance object.
|
||||
:param image_meta: Image dict returned by nova.image.glance
|
||||
that defines the image from which to boot this instance.
|
||||
:param injected_files: User files to inject into instance. Ignored
|
||||
by this driver.
|
||||
:param admin_password: Administrator password to set in
|
||||
instance. Ignored by this driver.
|
||||
:param network_info: Instance network information.
|
||||
:param block_device_info: Instance block device
|
||||
information. Ignored by this driver.
|
||||
|
||||
"""
|
||||
# The compute manager is meant to know the node uuid, so missing uuid
|
||||
# is a significant issue. It may mean we've been passed the wrong data.
|
||||
node_uuid = instance.get('node')
|
||||
if not node_uuid:
|
||||
raise ironic_exception.BadRequest(
|
||||
_("Ironic node uuid not supplied to "
|
||||
"driver for instance %s.") % instance['uuid'])
|
||||
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
node = icli.call("node.get", node_uuid)
|
||||
|
||||
flavor = flavor_obj.Flavor.get_by_id(context,
|
||||
instance['instance_type_id'])
|
||||
|
||||
self._add_driver_fields(node, instance, image_meta, flavor)
|
||||
|
||||
# NOTE(Shrews): The default ephemeral device needs to be set for
|
||||
# services (like cloud-init) that depend on it being returned by the
|
||||
# metadata server. Addresses bug https://launchpad.net/bugs/1324286.
|
||||
if flavor['ephemeral_gb']:
|
||||
instance.default_ephemeral_device = '/dev/sda1'
|
||||
instance.save()
|
||||
|
||||
# validate we are ready to do the deploy
|
||||
validate_chk = icli.call("node.validate", node_uuid)
|
||||
if not validate_chk.deploy or not validate_chk.power:
|
||||
# something is wrong. undo what we have done
|
||||
self._cleanup_deploy(node, instance, network_info)
|
||||
raise exception.ValidationError(_(
|
||||
"Ironic node: %(id)s failed to validate."
|
||||
" (deploy: %(deploy)s, power: %(power)s)")
|
||||
% {'id': node.uuid,
|
||||
'deploy': validate_chk.deploy,
|
||||
'power': validate_chk.power})
|
||||
|
||||
# prepare for the deploy
|
||||
try:
|
||||
self._plug_vifs(node, instance, network_info)
|
||||
self._start_firewall(instance, network_info)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Error preparing deploy for instance "
|
||||
"%(instance)s on baremetal node %(node)s."),
|
||||
{'instance': instance['uuid'],
|
||||
'node': node_uuid})
|
||||
self._cleanup_deploy(node, instance, network_info)
|
||||
|
||||
# trigger the node deploy
|
||||
try:
|
||||
icli.call("node.set_provision_state", node_uuid,
|
||||
ironic_states.ACTIVE)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
msg = (_LE("Failed to request Ironic to provision instance "
|
||||
"%(inst)s: %(reason)s"),
|
||||
{'inst': instance['uuid'],
|
||||
'reason': six.text_type(e)})
|
||||
LOG.error(msg)
|
||||
self._cleanup_deploy(node, instance, network_info)
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active,
|
||||
icli, instance)
|
||||
try:
|
||||
timer.start(interval=CONF.ironic.api_retry_interval).wait()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(_LE("Error deploying instance %(instance)s on "
|
||||
"baremetal node %(node)s."),
|
||||
{'instance': instance['uuid'],
|
||||
'node': node_uuid})
|
||||
self.destroy(context, instance, network_info)
|
||||
|
||||
def _unprovision(self, icli, instance, node):
|
||||
"""This method is called from destroy() to unprovision
|
||||
already provisioned node after required checks.
|
||||
"""
|
||||
try:
|
||||
icli.call("node.set_provision_state", node.uuid, "deleted")
|
||||
except Exception as e:
|
||||
# if the node is already in a deprovisioned state, continue
|
||||
# This should be fixed in Ironic.
|
||||
# TODO(deva): This exception should be added to
|
||||
# python-ironicclient and matched directly,
|
||||
# rather than via __name__.
|
||||
if getattr(e, '__name__', None) != 'InstanceDeployFailure':
|
||||
raise
|
||||
|
||||
# using a dict because this is modified in the local method
|
||||
data = {'tries': 0}
|
||||
|
||||
def _wait_for_provision_state():
|
||||
node = _validate_instance_and_node(icli, instance)
|
||||
if not node.provision_state:
|
||||
LOG.debug("Ironic node %(node)s is now unprovisioned",
|
||||
dict(node=node.uuid), instance=instance)
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if data['tries'] >= CONF.ironic.api_max_retries:
|
||||
msg = (_("Error destroying the instance on node %(node)s. "
|
||||
"Provision state still '%(state)s'.")
|
||||
% {'state': node.provision_state,
|
||||
'node': node.uuid})
|
||||
LOG.error(msg)
|
||||
raise exception.NovaException(msg)
|
||||
else:
|
||||
data['tries'] += 1
|
||||
|
||||
_log_ironic_polling('unprovision', node, instance)
|
||||
|
||||
# wait for the state transition to finish
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_provision_state)
|
||||
timer.start(interval=CONF.ironic.api_retry_interval).wait()
|
||||
|
||||
def destroy(self, context, instance, network_info,
|
||||
block_device_info=None, destroy_disks=True, migrate_data=None):
|
||||
"""Destroy the specified instance, if it can be found.
|
||||
|
||||
:param context: The security context.
|
||||
:param instance: The instance object.
|
||||
:param network_info: Instance network information.
|
||||
:param block_device_info: Instance block device
|
||||
information. Ignored by this driver.
|
||||
:param destroy_disks: Indicates if disks should be
|
||||
destroyed. Ignored by this driver.
|
||||
:param migrate_data: implementation specific params.
|
||||
Ignored by this driver.
|
||||
"""
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
try:
|
||||
node = _validate_instance_and_node(icli, instance)
|
||||
except exception.InstanceNotFound:
|
||||
LOG.warning(_LW("Destroy called on non-existing instance %s."),
|
||||
instance['uuid'])
|
||||
# NOTE(deva): if nova.compute.ComputeManager._delete_instance()
|
||||
# is called on a non-existing instance, the only way
|
||||
# to delete it is to return from this method
|
||||
# without raising any exceptions.
|
||||
return
|
||||
|
||||
if node.provision_state in (ironic_states.ACTIVE,
|
||||
ironic_states.DEPLOYFAIL,
|
||||
ironic_states.ERROR,
|
||||
ironic_states.DEPLOYWAIT):
|
||||
self._unprovision(icli, instance, node)
|
||||
|
||||
self._cleanup_deploy(node, instance, network_info)
|
||||
|
||||
def reboot(self, context, instance, network_info, reboot_type,
|
||||
block_device_info=None, bad_volumes_callback=None):
|
||||
"""Reboot the specified instance.
|
||||
@@ -420,3 +700,96 @@ class IronicDriver(virt_driver.ComputeDriver):
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
node = _validate_instance_and_node(icli, instance)
|
||||
icli.call("node.set_power_state", node.uuid, 'on')
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
"""Return the currently known stats for all Ironic nodes.
|
||||
|
||||
:param refresh: Boolean value; If True run update first. Ignored by
|
||||
this driver.
|
||||
:returns: a list of dictionaries; each dictionary contains the
|
||||
stats for a node.
|
||||
|
||||
"""
|
||||
caps = []
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
node_list = icli.call("node.list")
|
||||
for node in node_list:
|
||||
data = self._node_resource(node)
|
||||
caps.append(data)
|
||||
return caps
|
||||
|
||||
def _plug_vifs(self, node, instance, network_info):
|
||||
# NOTE(PhilDay): Accessing network_info will block if the thread
|
||||
# it wraps hasn't finished, so do this ahead of time so that we
|
||||
# don't block while holding the logging lock.
|
||||
network_info_str = str(network_info)
|
||||
LOG.debug("plug: instance_uuid=%(uuid)s vif=%(network_info)s",
|
||||
{'uuid': instance['uuid'],
|
||||
'network_info': network_info_str})
|
||||
# start by ensuring the ports are clear
|
||||
self._unplug_vifs(node, instance, network_info)
|
||||
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
ports = icli.call("node.list_ports", node.uuid)
|
||||
|
||||
if len(network_info) > len(ports):
|
||||
raise exception.NovaException(_(
|
||||
"Ironic node: %(id)s virtual to physical interface count"
|
||||
" missmatch"
|
||||
" (Vif count: %(vif_count)d, Pif count: %(pif_count)d)")
|
||||
% {'id': node.uuid,
|
||||
'vif_count': len(network_info),
|
||||
'pif_count': len(ports)})
|
||||
|
||||
if len(network_info) > 0:
|
||||
# not needed if no vif are defined
|
||||
for vif, pif in zip(network_info, ports):
|
||||
# attach what neutron needs directly to the port
|
||||
port_id = unicode(vif['id'])
|
||||
patch = [{'op': 'add',
|
||||
'path': '/extra/vif_port_id',
|
||||
'value': port_id}]
|
||||
icli.call("port.update", pif.uuid, patch)
|
||||
|
||||
def _unplug_vifs(self, node, instance, network_info):
|
||||
# NOTE(PhilDay): Accessing network_info will block if the thread
|
||||
# it wraps hasn't finished, so do this ahead of time so that we
|
||||
# don't block while holding the logging lock.
|
||||
network_info_str = str(network_info)
|
||||
LOG.debug("unplug: instance_uuid=%(uuid)s vif=%(network_info)s",
|
||||
{'uuid': instance['uuid'],
|
||||
'network_info': network_info_str})
|
||||
if network_info and len(network_info) > 0:
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
ports = icli.call("node.list_ports", node.uuid)
|
||||
|
||||
# not needed if no vif are defined
|
||||
for vif, pif in zip(network_info, ports):
|
||||
# we can not attach a dict directly
|
||||
patch = [{'op': 'remove', 'path': '/extra/vif_port_id'}]
|
||||
try:
|
||||
icli.call("port.update", pif.uuid, patch)
|
||||
except ironic_exception.BadRequest:
|
||||
pass
|
||||
|
||||
def plug_vifs(self, instance, network_info):
|
||||
"""Plug VIFs into networks.
|
||||
|
||||
:param instance: The instance object.
|
||||
:param network_info: Instance network information.
|
||||
|
||||
"""
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
node = icli.call("node.get", instance['node'])
|
||||
self._plug_vifs(node, instance, network_info)
|
||||
|
||||
def unplug_vifs(self, instance, network_info):
|
||||
"""Unplug VIFs from networks.
|
||||
|
||||
:param instance: The instance object.
|
||||
:param network_info: Instance network information.
|
||||
|
||||
"""
|
||||
icli = client_wrapper.IronicClientWrapper()
|
||||
node = icli.call("node.get", instance['node'])
|
||||
self._unplug_vifs(node, instance, network_info)
|
||||
|
||||
Reference in New Issue
Block a user