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.