From c7b46c4778acdb05468b6cab4f3111b298609ed4 Mon Sep 17 00:00:00 2001 From: Sam Betts Date: Thu, 1 Sep 2016 16:44:06 +0100 Subject: [PATCH] Support Ironic interface attach/detach in nova virt In order to support different Ironic network drivers that may need to implement vif to pif mapping in different ways, we've added new APIs in Ironic to abstract this behaviour. This patch switches the Ironic virt driver over to use this API. As part of this change the virt driver will no longer provide the macs_for_instance function, as Ironic will handle updating the Neutron ports during the provisioning process. Update interface mac addresses in configdrive network_metadata with ironic node ports values. Co-Authored-By: Vasyl Saienko Implements: blueprint ironic-plug-unplug-vifs-update Change-Id: I4d70423ca978885a982c7eb5bd1efcc024d2b777 Partial-Bug: #1582188 Closes-Bug: #1656854 --- .../unit/virt/ironic/test_client_wrapper.py | 2 +- nova/tests/unit/virt/ironic/test_driver.py | 198 ++++++------------ nova/tests/unit/virt/ironic/test_patcher.py | 5 +- nova/tests/unit/virt/ironic/utils.py | 9 +- nova/virt/driver.py | 2 + nova/virt/ironic/client_wrapper.py | 2 +- nova/virt/ironic/driver.py | 117 ++++------- nova/virt/ironic/patcher.py | 2 + ...to-vif-attach-detach-cc8583c604510f95.yaml | 6 + 9 files changed, 131 insertions(+), 212 deletions(-) create mode 100644 releasenotes/notes/ironic-virt-driver-switch-to-vif-attach-detach-cc8583c604510f95.yaml diff --git a/nova/tests/unit/virt/ironic/test_client_wrapper.py b/nova/tests/unit/virt/ironic/test_client_wrapper.py index eadf5b56c239..c11e62effcd5 100644 --- a/nova/tests/unit/virt/ironic/test_client_wrapper.py +++ b/nova/tests/unit/virt/ironic/test_client_wrapper.py @@ -72,7 +72,7 @@ class IronicClientWrapperTestCase(test.NoDBTestCase): expected = {'session': 'session', 'max_retries': CONF.ironic.api_max_retries, 'retry_interval': CONF.ironic.api_retry_interval, - 'os_ironic_api_version': '1.21', + 'os_ironic_api_version': '1.28', 'ironic_url': None} mock_ir_cli.assert_called_once_with(1, **expected) diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py index a266f2dd1728..4712bc3537a5 100644 --- a/nova/tests/unit/virt/ironic/test_driver.py +++ b/nova/tests/unit/virt/ironic/test_driver.py @@ -827,27 +827,6 @@ class IronicDriverTestCase(test.NoDBTestCase): self.assertEqual(hardware.InstanceInfo(state=nova_states.NOSTATE), 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(objects.Instance, 'save') @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') @mock.patch.object(FAKE_CLIENT, 'node') @@ -969,6 +948,8 @@ class IronicDriverTestCase(test.NoDBTestCase): 'value': instance.display_name, 'op': 'add'}, {'path': '/instance_info/vcpus', 'op': 'add', 'value': str(instance.flavor.vcpus)}, + {'path': '/instance_info/nova_host_id', 'op': 'add', + 'value': instance.host}, {'path': '/instance_info/memory_mb', 'op': 'add', 'value': str(instance.flavor.memory_mb)}, {'path': '/instance_info/local_gb', 'op': 'add', @@ -1392,31 +1373,20 @@ class IronicDriverTestCase(test.NoDBTestCase): self.driver.power_off(instance) mock_sp.assert_called_once_with(node.uuid, 'off') - @mock.patch.object(FAKE_CLIENT.node, 'list_ports') - @mock.patch.object(FAKE_CLIENT.port, 'update') - @mock.patch.object(ironic_driver.IronicDriver, '_unplug_vifs') - def test_plug_vifs_with_port(self, mock_uvifs, mock_port_udt, mock_lp): + @mock.patch.object(FAKE_CLIENT.node, 'vif_attach') + def test_plug_vifs_with_port(self, mock_vatt): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(uuid=node_uuid) - # make the address be consistent with network_info's - port = ironic_utils.get_test_port(address=utils.FAKE_VIF_MAC) - - mock_lp.return_value = [port] instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) network_info = utils.get_test_network_info() + vif_id = six.text_type(network_info[0]['id']) - port_id = six.text_type(network_info[0]['id']) - expected_patch = [{'op': 'add', - 'path': '/extra/vif_port_id', - 'value': port_id}] self.driver._plug_vifs(node, instance, network_info) # asserts - mock_uvifs.assert_called_once_with(node, instance, network_info) - mock_lp.assert_called_once_with(node_uuid) - mock_port_udt.assert_called_with(port.uuid, expected_patch) + mock_vatt.assert_called_with(node.uuid, vif_id) @mock.patch.object(FAKE_CLIENT.node, 'get') @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs') @@ -1434,82 +1404,67 @@ class IronicDriverTestCase(test.NoDBTestCase): fields=ironic_driver._NODE_FIELDS) mock__plug_vifs.assert_called_once_with(node, instance, network_info) - @mock.patch.object(FAKE_CLIENT.port, 'update') - @mock.patch.object(FAKE_CLIENT.node, 'list_ports') - @mock.patch.object(ironic_driver.IronicDriver, '_unplug_vifs') - def test_plug_vifs_multiple_ports(self, mock_uvifs, mock_lp, - mock_port_udt): + @mock.patch.object(FAKE_CLIENT.node, 'vif_attach') + def test_plug_vifs_multiple_ports(self, mock_vatt): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(uuid=node_uuid) - first_ironic_port_uuid = 'aaaaaaaa-bbbb-1111-dddd-eeeeeeeeeeee' - first_port = ironic_utils.get_test_port(uuid=first_ironic_port_uuid, - node_uuid=node_uuid, - address='11:FF:FF:FF:FF:FF') - second_ironic_port_uuid = 'aaaaaaaa-bbbb-2222-dddd-eeeeeeeeeeee' - second_port = ironic_utils.get_test_port(uuid=second_ironic_port_uuid, - node_uuid=node_uuid, - address='22:FF:FF:FF:FF:FF') - mock_lp.return_value = [second_port, first_port] instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) first_vif_id = 'aaaaaaaa-vv11-cccc-dddd-eeeeeeeeeeee' second_vif_id = 'aaaaaaaa-vv22-cccc-dddd-eeeeeeeeeeee' - first_vif = ironic_utils.get_test_vif( - address='22:FF:FF:FF:FF:FF', - id=second_vif_id) - second_vif = ironic_utils.get_test_vif( - address='11:FF:FF:FF:FF:FF', - id=first_vif_id) + first_vif = ironic_utils.get_test_vif(address='22:FF:FF:FF:FF:FF', + id=first_vif_id) + second_vif = ironic_utils.get_test_vif(address='11:FF:FF:FF:FF:FF', + id=second_vif_id) network_info = [first_vif, second_vif] self.driver._plug_vifs(node, instance, network_info) # asserts - mock_uvifs.assert_called_once_with(node, instance, network_info) - mock_lp.assert_called_once_with(node_uuid) - calls = (mock.call(first_ironic_port_uuid, - [{'op': 'add', 'path': '/extra/vif_port_id', - 'value': first_vif_id}]), - mock.call(second_ironic_port_uuid, - [{'op': 'add', 'path': '/extra/vif_port_id', - 'value': second_vif_id}])) - mock_port_udt.assert_has_calls(calls, any_order=True) + calls = (mock.call(node.uuid, first_vif_id), + mock.call(node.uuid, second_vif_id)) + mock_vatt.assert_has_calls(calls, any_order=True) - @mock.patch.object(FAKE_CLIENT.port, 'update') - @mock.patch.object(FAKE_CLIENT.node, 'list_ports') - @mock.patch.object(ironic_driver.IronicDriver, '_unplug_vifs') - def test_plug_vifs_count_mismatch(self, mock_uvifs, mock_lp, - mock_port_udt): + @mock.patch.object(FAKE_CLIENT, 'node') + def test_plug_vifs_failure(self, mock_node): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(uuid=node_uuid) - port = ironic_utils.get_test_port() - - mock_lp.return_value = [port] - instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) - # len(network_info) > len(ports) - network_info = (utils.get_test_network_info() + - utils.get_test_network_info()) - self.assertRaises(exception.NovaException, + first_vif_id = 'aaaaaaaa-vv11-cccc-dddd-eeeeeeeeeeee' + second_vif_id = 'aaaaaaaa-vv22-cccc-dddd-eeeeeeeeeeee' + first_vif = ironic_utils.get_test_vif(address='22:FF:FF:FF:FF:FF', + id=first_vif_id) + second_vif = ironic_utils.get_test_vif(address='11:FF:FF:FF:FF:FF', + id=second_vif_id) + mock_node.vif_attach.side_effect = [None, + ironic_exception.BadRequest()] + network_info = [first_vif, second_vif] + self.assertRaises(exception.VirtualInterfacePlugException, self.driver._plug_vifs, node, instance, network_info) - # asserts - mock_uvifs.assert_called_once_with(node, instance, network_info) - mock_lp.assert_called_once_with(node_uuid) - # assert port.update() was not called - self.assertFalse(mock_port_udt.called) - - @mock.patch.object(FAKE_CLIENT.port, 'update') - @mock.patch.object(FAKE_CLIENT.node, 'list_ports') - @mock.patch.object(ironic_driver.IronicDriver, '_unplug_vifs') - def test_plug_vifs_no_network_info(self, mock_uvifs, mock_lp, - mock_port_udt): + @mock.patch.object(FAKE_CLIENT, 'node') + def test_plug_vifs_already_attached(self, mock_node): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(uuid=node_uuid) - port = ironic_utils.get_test_port() + instance = fake_instance.fake_instance_obj(self.ctx, + node=node_uuid) + first_vif_id = 'aaaaaaaa-vv11-cccc-dddd-eeeeeeeeeeee' + second_vif_id = 'aaaaaaaa-vv22-cccc-dddd-eeeeeeeeeeee' + first_vif = ironic_utils.get_test_vif(address='22:FF:FF:FF:FF:FF', + id=first_vif_id) + second_vif = ironic_utils.get_test_vif(address='11:FF:FF:FF:FF:FF', + id=second_vif_id) + mock_node.vif_attach.side_effect = [ironic_exception.Conflict(), + None] + network_info = [first_vif, second_vif] + self.driver._plug_vifs(node, instance, network_info) + self.assertEqual(2, mock_node.vif_attach.call_count) - mock_lp.return_value = [port] + @mock.patch.object(FAKE_CLIENT.node, 'vif_attach') + def test_plug_vifs_no_network_info(self, mock_vatt): + node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' + node = ironic_utils.get_test_node(uuid=node_uuid) instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) @@ -1517,60 +1472,45 @@ class IronicDriverTestCase(test.NoDBTestCase): self.driver._plug_vifs(node, instance, network_info) # asserts - mock_uvifs.assert_called_once_with(node, instance, network_info) - mock_lp.assert_called_once_with(node_uuid) - # assert port.update() was not called - self.assertFalse(mock_port_udt.called) + self.assertFalse(mock_vatt.called) - @mock.patch.object(FAKE_CLIENT.port, 'update') @mock.patch.object(FAKE_CLIENT, 'node') - def test_unplug_vifs(self, mock_node, mock_update): + def test_unplug_vifs(self, mock_node): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(uuid=node_uuid) - port = ironic_utils.get_test_port(extra={'vif_port_id': 'fake-vif'}) - mock_node.get.return_value = node - mock_node.list_ports.return_value = [port] instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) - expected_patch = [{'op': 'remove', 'path': - '/extra/vif_port_id'}] - self.driver.unplug_vifs(instance, - utils.get_test_network_info()) + network_info = utils.get_test_network_info() + vif_id = six.text_type(network_info[0]['id']) + self.driver.unplug_vifs(instance, network_info) # asserts mock_node.get.assert_called_once_with( node_uuid, fields=ironic_driver._NODE_FIELDS) - mock_node.list_ports.assert_called_once_with(node_uuid, detail=True) - mock_update.assert_called_once_with(port.uuid, expected_patch) + mock_node.vif_detach.assert_called_once_with(node.uuid, vif_id) - @mock.patch.object(FAKE_CLIENT.port, 'update') @mock.patch.object(FAKE_CLIENT, 'node') - def test_unplug_vifs_port_not_associated(self, mock_node, mock_update): + def test_unplug_vifs_port_not_associated(self, mock_node): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(uuid=node_uuid) - port = ironic_utils.get_test_port(extra={}) mock_node.get.return_value = node - mock_node.list_ports.return_value = [port] instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) - self.driver.unplug_vifs(instance, utils.get_test_network_info()) + network_info = utils.get_test_network_info() + self.driver.unplug_vifs(instance, network_info) mock_node.get.assert_called_once_with( node_uuid, fields=ironic_driver._NODE_FIELDS) - mock_node.list_ports.assert_called_once_with(node_uuid, detail=True) - # assert port.update() was not called - self.assertFalse(mock_update.called) + self.assertEqual(len(network_info), mock_node.vif_detach.call_count) - @mock.patch.object(FAKE_CLIENT.port, 'update') - def test_unplug_vifs_no_network_info(self, mock_update): + @mock.patch.object(FAKE_CLIENT.node, 'vif_detach') + def test_unplug_vifs_no_network_info(self, mock_vdet): instance = fake_instance.fake_instance_obj(self.ctx) network_info = [] self.driver.unplug_vifs(instance, network_info) - - # assert port.update() was not called - self.assertFalse(mock_update.called) + self.assertFalse(mock_vdet.called) @mock.patch.object(firewall.NoopFirewallDriver, 'unfilter_instance', create=True) @@ -1697,33 +1637,19 @@ class IronicDriverTestCase(test.NoDBTestCase): detach_block_devices=None, attach_block_devices=None) @mock.patch.object(FAKE_CLIENT.node, 'get') - def _test_network_binding_host_id(self, network_interface, mock_get): + def test_network_binding_host_id(self, mock_get): node_uuid = uuidutils.generate_uuid() hostname = 'ironic-compute' instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid, host=hostname) - if network_interface == 'neutron': - expected = None - else: - expected = hostname node = ironic_utils.get_test_node(uuid=node_uuid, instance_uuid=self.instance_uuid, instance_type_id=5, - network_interface=network_interface) + network_interface='flat') mock_get.return_value = node - host_id = self.driver.network_binding_host_id(self.ctx, instance) - self.assertEqual(expected, host_id) - - def test_network_binding_host_id_neutron(self): - self._test_network_binding_host_id('neutron') - - def test_network_binding_host_id_flat(self): - self._test_network_binding_host_id('flat') - - def test_network_binding_host_id_noop(self): - self._test_network_binding_host_id('noop') + self.assertIsNone(host_id) @mock.patch.object(instance_metadata, 'InstanceMetadata') diff --git a/nova/tests/unit/virt/ironic/test_patcher.py b/nova/tests/unit/virt/ironic/test_patcher.py index 4ae37ad8be64..4def69488146 100644 --- a/nova/tests/unit/virt/ironic/test_patcher.py +++ b/nova/tests/unit/virt/ironic/test_patcher.py @@ -59,7 +59,10 @@ class IronicDriverFieldsTestCase(test.NoDBTestCase): 'op': 'add'}, {'path': '/instance_info/local_gb', 'value': str(self.node.properties.get('local_gb', 0)), - 'op': 'add'} + 'op': 'add'}, + {'path': '/instance_info/nova_host_id', + 'value': u'fake-host', + 'op': 'add'}, ] def assertPatchEqual(self, expected, observed): diff --git a/nova/tests/unit/virt/ironic/utils.py b/nova/tests/unit/virt/ironic/utils.py index 9df401e8ee52..86f1150e38a8 100644 --- a/nova/tests/unit/virt/ironic/utils.py +++ b/nova/tests/unit/virt/ironic/utils.py @@ -58,6 +58,7 @@ def get_test_port(**kw): 'node_uuid': kw.get('node_uuid', get_test_node().uuid), 'address': kw.get('address', 'FF:FF:FF:FF:FF:FF'), 'extra': kw.get('extra', {}), + 'internal_info': kw.get('internal_info', {}), 'created_at': kw.get('created_at'), 'updated_at': kw.get('updated_at')})() @@ -120,7 +121,7 @@ class FakeNodeClient(object): def get_by_instance_uuid(self, instance_uuid, fields=None): pass - def list_ports(self, node_uuid): + def list_ports(self, node_uuid, detail=False): pass def set_power_state(self, node_uuid, target): @@ -135,6 +136,12 @@ class FakeNodeClient(object): def validate(self, node_uuid): pass + def vif_attach(self, node_uuid, port_id): + pass + + def vif_detach(self, node_uuid, port_id): + pass + class FakeClient(object): diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 506956a25f12..fc15519c4eb6 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -1242,6 +1242,8 @@ class ComputeDriver(object): """Does the driver want networks deallocated on reschedule?""" return False + # NOTE(vsaienko) This method is deprecated, don't use it! + # TODO(vsaienko) Remove this function in Ocata. def macs_for_instance(self, instance): """What MAC addresses must this instance have? diff --git a/nova/virt/ironic/client_wrapper.py b/nova/virt/ironic/client_wrapper.py index 5da5b6e7f1e8..ee740cbf0930 100644 --- a/nova/virt/ironic/client_wrapper.py +++ b/nova/virt/ironic/client_wrapper.py @@ -32,7 +32,7 @@ ironic = None IRONIC_GROUP = nova.conf.ironic.ironic_group # The API version required by the Ironic driver -IRONIC_API_VERSION = (1, 21) +IRONIC_API_VERSION = (1, 28) class IronicClientWrapper(object): diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py index 30fe42e35455..c89173cfdf3a 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py @@ -683,24 +683,6 @@ class IronicDriver(virt_driver.ComputeDriver): """ return True - 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'. - """ - try: - node = self._get_node(instance.node) - except ironic.exc.NotFound: - return None - ports = self.ironicclient.call("node.list_ports", node.uuid) - return set([p.address for p in ports]) - def _get_network_metadata(self, node, network_info): """Gets a more complete representation of the instance network info. @@ -715,10 +697,21 @@ class IronicDriver(virt_driver.ComputeDriver): ports = self.ironicclient.call("node.list_ports", node.uuid, detail=True) - for port in ports: - for link in base_metadata['links']: - if link['ethernet_mac_address'] == port.address: - link['type'] = 'phy' + # TODO(vsaienko) add support of portgroups + vif_id_to_objects = {'ports': {}} + for p in ports: + vif_id = (p.internal_info.get('tenant_vif_port_id') or + p.extra.get('vif_port_id')) + if vif_id: + vif_id_to_objects['ports'][vif_id] = p + + for link in base_metadata['links']: + vif_id = link['vif_id'] + if vif_id in vif_id_to_objects['ports']: + p = vif_id_to_objects['ports'][vif_id] + # Ironic updates neutron port's address during attachment + link.update({'ethernet_mac_address': p.address, + 'type': 'phy'}) return base_metadata @@ -1091,32 +1084,20 @@ class IronicDriver(virt_driver.ComputeDriver): 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) - - ports = self.ironicclient.call("node.list_ports", node.uuid) - - if len(network_info) > len(ports): - raise exception.VirtualInterfacePlugException(_( - "Ironic node: %(id)s virtual to physical interface count" - " mismatch" - " (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 in network_info: - for pif in ports: - if vif['address'] == pif.address: - # attach what neutron needs directly to the port - port_id = six.text_type(vif['id']) - patch = [{'op': 'add', - 'path': '/extra/vif_port_id', - 'value': port_id}] - self.ironicclient.call("port.update", pif.uuid, patch) - break + for vif in network_info: + port_id = six.text_type(vif['id']) + try: + self.ironicclient.call("node.vif_attach", node.uuid, port_id, + retry_on_conflict=False) + except ironic.exc.BadRequest as e: + msg = (_("Cannot attach VIF %(vif)s to the node %(node)s due " + "to error: %(err)s") % {'vif': port_id, + 'node': node.uuid, 'err': e}) + LOG.error(msg) + raise exception.VirtualInterfacePlugException(msg) + except ironic.exc.Conflict: + # NOTE (vsaienko) Pass since VIF already attached. + pass def _unplug_vifs(self, node, instance, network_info): # NOTE(PhilDay): Accessing network_info will block if the thread @@ -1126,19 +1107,16 @@ class IronicDriver(virt_driver.ComputeDriver): 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: - ports = self.ironicclient.call("node.list_ports", node.uuid, - detail=True) - - # not needed if no vif are defined - for pif in ports: - if 'vif_port_id' in pif.extra: - # we can not attach a dict directly - patch = [{'op': 'remove', 'path': '/extra/vif_port_id'}] - try: - self.ironicclient.call("port.update", pif.uuid, patch) - except ironic.exc.BadRequest: - pass + if not network_info: + return + for vif in network_info: + port_id = six.text_type(vif['id']) + try: + self.ironicclient.call("node.vif_detach", node.uuid, + port_id) + except ironic.exc.BadRequest: + LOG.debug("VIF %(vif)s isn't attached to Ironic node %(node)s", + {'vif': port_id, 'node': node.uuid}) def plug_vifs(self, instance, network_info): """Plug VIFs into networks. @@ -1240,8 +1218,7 @@ class IronicDriver(virt_driver.ComputeDriver): Neutron. If using the neutron network interface (separate networks for the control plane and tenants), return None here to indicate that the port should not yet be bound; Ironic will make a port-update call to - Neutron later to tell Neutron to bind the port. Otherwise, use the - default behavior and allow the port to be bound immediately. + Neutron later to tell Neutron to bind the port. NOTE: the late binding is important for security. If an ML2 mechanism manages to connect the tenant network to the baremetal machine before @@ -1257,16 +1234,12 @@ class IronicDriver(virt_driver.ComputeDriver): :param context: request context :param instance: nova.objects.instance.Instance that the network ports will be associated with - :returns: a string representing the host ID, or None + :returns: None """ - - node = self.ironicclient.call('node.get', instance.node, - fields=['network_interface']) - if node.network_interface == 'neutron': - return None - # flat network, go ahead and allow the port to be bound - return super(IronicDriver, self).network_binding_host_id( - context, instance) + # NOTE(vsaienko) Ironic will set binding:host_id later with port-update + # call when updating mac address or setting binding:profile + # to tell Neutron to bind the port. + return None def _get_node_console_with_reset(self, instance): """Acquire console information for an instance. diff --git a/nova/virt/ironic/patcher.py b/nova/virt/ironic/patcher.py index 62c5b999daf6..31942ed76bac 100644 --- a/nova/virt/ironic/patcher.py +++ b/nova/virt/ironic/patcher.py @@ -65,6 +65,8 @@ class GenericDriverFields(object): 'op': 'add', 'value': instance.display_name}) patch.append({'path': '/instance_info/vcpus', 'op': 'add', 'value': str(instance.flavor.vcpus)}) + patch.append({'path': '/instance_info/nova_host_id', 'op': 'add', + 'value': instance.get('host')}) patch.append({'path': '/instance_info/memory_mb', 'op': 'add', 'value': str(instance.flavor.memory_mb)}) patch.append({'path': '/instance_info/local_gb', 'op': 'add', diff --git a/releasenotes/notes/ironic-virt-driver-switch-to-vif-attach-detach-cc8583c604510f95.yaml b/releasenotes/notes/ironic-virt-driver-switch-to-vif-attach-detach-cc8583c604510f95.yaml new file mode 100644 index 000000000000..c6f79ec45f44 --- /dev/null +++ b/releasenotes/notes/ironic-virt-driver-switch-to-vif-attach-detach-cc8583c604510f95.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - The Ironic driver now requires python-ironicclient>=1.9.0, and requires + Ironic service to support API version 1.28 or + higher. As usual, Ironic should be upgraded before Nova for a + smooth upgrade process.