From 4289d86b32ba438d09f886b163a6e174b006bd88 Mon Sep 17 00:00:00 2001 From: Artom Lifshitz Date: Fri, 17 Feb 2017 11:17:05 +0000 Subject: [PATCH] Delete nic metadata when detaching interface If an interface that was tagged with a device role tag is detached, this patch deletes its metadata from the instance's device_metadata. Change-Id: Iede60a31e877adbda98d7676f2e6a5b31ded05f3 Implements: blueprint virt-device-tagged-attach-detach --- .../openstack/compute/attach_interfaces.py | 3 ++- nova/network/neutronv2/api.py | 10 +++++++ .../compute/test_attach_interfaces.py | 27 ++++++++++++------- nova/tests/unit/network/test_neutronv2.py | 25 ++++++++++++++--- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/compute/attach_interfaces.py b/nova/api/openstack/compute/attach_interfaces.py index 7ddc05a00f15..e3341777b81b 100644 --- a/nova/api/openstack/compute/attach_interfaces.py +++ b/nova/api/openstack/compute/attach_interfaces.py @@ -162,7 +162,8 @@ class InterfaceAttachmentController(wsgi.Controller): context.can(ai_policies.POLICY_ROOT % 'delete') port_id = id - instance = common.get_instance(self.compute_api, context, server_id) + instance = common.get_instance(self.compute_api, context, server_id, + expected_attrs=['device_metadata']) try: self.compute_api.detach_interface(context, instance, port_id=port_id) diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 7cb29c1a4ea4..5bd2d5b1289f 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1224,6 +1224,8 @@ class API(base_api.NetworkAPI): # Delete the VirtualInterface for the given port_id. vif = objects.VirtualInterface.get_by_uuid(context, port_id) if vif: + if 'tag' in vif and vif.tag: + self._delete_nic_metadata(self, instance, vif) vif.destroy() else: LOG.debug('VirtualInterface not found for port: %s', @@ -1231,6 +1233,14 @@ class API(base_api.NetworkAPI): return self.get_instance_nw_info(context, instance) + def _delete_nic_metadata(self, instance, vif): + for device in instance.device_metadata.devices: + if (isinstance(device, objects.NetworkInterfaceMetadata) + and device.mac == vif.address): + instance.device_metadata.devices.remove(device) + instance.save() + break + def list_ports(self, context, **search_opts): """List ports for the client based on search options.""" return get_client(context).list_ports(**search_opts) diff --git a/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py b/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py index 2e09ea2be384..8e8f5f34d8a8 100644 --- a/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py +++ b/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py @@ -16,6 +16,7 @@ import mock from webob import exc +from nova.api.openstack import common from nova.api.openstack.compute import attach_interfaces \ as attach_interfaces_v21 from nova.compute import api as compute_api @@ -177,15 +178,23 @@ class InterfaceAttachTestsV21(test.NoDBTestCase): self.stub_out('nova.compute.api.API.detach_interface', fake_detach_interface) - result = self.attachments.delete(self.req, FAKE_UUID1, FAKE_PORT_ID1) - # NOTE: on v2.1, http status code is set as wsgi_code of API - # method instead of status_int in a response object. - if isinstance(self.attachments, - attach_interfaces_v21.InterfaceAttachmentController): - status_int = self.attachments.delete.wsgi_code - else: - status_int = result.status_int - self.assertEqual(202, status_int) + inst = objects.Instance(uuid=FAKE_UUID1) + with mock.patch.object(common, 'get_instance', + return_value=inst) as mock_get_instance: + result = self.attachments.delete(self.req, FAKE_UUID1, + FAKE_PORT_ID1) + # NOTE: on v2.1, http status code is set as wsgi_code of API + # method instead of status_int in a response object. + if isinstance(self.attachments, + attach_interfaces_v21.InterfaceAttachmentController): + status_int = self.attachments.delete.wsgi_code + else: + status_int = result.status_int + self.assertEqual(202, status_int) + ctxt = self.req.environ['nova.context'] + mock_get_instance.assert_called_with( + self.attachments.compute_api, ctxt, FAKE_UUID1, + expected_attrs=['device_metadata']) def test_detach_interface_instance_locked(self): def fake_detach_interface_from_locked_server(self, context, diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 2429ea9b9868..e4c8d284106c 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -4501,6 +4501,7 @@ class TestNeutronv2WithMock(test.TestCase): raise_if_fail=True) mock_delete_vifs.assert_called_once_with(mock.sentinel.ctx, 'inst-1') + @mock.patch('nova.network.neutronv2.api.API._delete_nic_metadata') @mock.patch('nova.network.neutronv2.api.API.get_instance_nw_info') @mock.patch('nova.network.neutronv2.api.API._unbind_ports') @mock.patch('nova.objects.Instance.get_network_info') @@ -4511,7 +4512,8 @@ class TestNeutronv2WithMock(test.TestCase): mock_ntrn, mock_inst_get_nwinfo, mock_unbind, - mock_netinfo): + mock_netinfo, + mock_del_nic_meta): mock_inst = mock.Mock(project_id="proj-1", availability_zone='zone-1', uuid='inst-1') @@ -4521,14 +4523,29 @@ class TestNeutronv2WithMock(test.TestCase): id='3', preserve_on_delete=True)] mock_client = mock.Mock() mock_ntrn.return_value = mock_client - mock_vif = mock.MagicMock(spec=objects.VirtualInterface) - mock_get_vif_by_uuid.return_value = mock_vif + vif = objects.VirtualInterface() + vif.tag = 'foo' + vif.destroy = mock.MagicMock() + mock_get_vif_by_uuid.return_value = vif self.api.deallocate_port_for_instance(mock.sentinel.ctx, mock_inst, '2') mock_unbind.assert_called_once_with(mock.sentinel.ctx, ['2'], mock_client) mock_get_vif_by_uuid.assert_called_once_with(mock.sentinel.ctx, '2') - mock_vif.destroy.assert_called_once_with() + mock_del_nic_meta.assert_called_once_with(self.api, mock_inst, + vif) + vif.destroy.assert_called_once_with() + + def test_delete_nic_metadata(self): + vif = objects.VirtualInterface(address='aa:bb:cc:dd:ee:ff', tag='foo') + instance = fake_instance.fake_instance_obj(self.context) + instance.device_metadata = objects.InstanceDeviceMetadata( + devices=[objects.NetworkInterfaceMetadata( + mac='aa:bb:cc:dd:ee:ff', tag='foo')]) + instance.save = mock.Mock() + self.api._delete_nic_metadata(instance, vif) + self.assertEqual(0, len(instance.device_metadata.devices)) + instance.save.assert_called_once_with() @mock.patch('nova.network.neutronv2.api.API.' '_check_external_network_attach')