diff --git a/ironic/drivers/modules/network/common.py b/ironic/drivers/modules/network/common.py index c65cf569133..db4df583889 100644 --- a/ironic/drivers/modules/network/common.py +++ b/ironic/drivers/modules/network/common.py @@ -279,7 +279,108 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None): raise exception.NetworkError(msg) -class NeutronVIFPortIDMixin(object): +class VIFPortIDMixin(object): + """VIF port ID mixin class for non-neutron network interfaces. + + Mixin class that provides VIF-related network interface methods for + non-neutron network interfaces. There are no effects due to VIF + attach/detach that are external to ironic. + + NOTE: This does not yet support the full set of VIF methods, as it does + not provide vif_attach, vif_detach, port_changed, or portgroup_changed. + """ + + @staticmethod + def _save_vif_to_port_like_obj(port_like_obj, vif_id): + """Save the ID of a VIF to a port or portgroup. + + :param port_like_obj: port-like object to save to. + :param vif_id: VIF ID to save. + """ + int_info = port_like_obj.internal_info + int_info[TENANT_VIF_KEY] = vif_id + port_like_obj.internal_info = int_info + port_like_obj.save() + + @staticmethod + def _clear_vif_from_port_like_obj(port_like_obj): + """Clear the VIF ID field from a port or portgroup. + + :param port_like_obj: port-like object to clear from. + """ + int_info = port_like_obj.internal_info + extra = port_like_obj.extra + int_info.pop(TENANT_VIF_KEY, None) + extra.pop('vif_port_id', None) + port_like_obj.extra = extra + port_like_obj.internal_info = int_info + port_like_obj.save() + + def _get_port_like_obj_by_vif_id(self, task, vif_id): + """Lookup a port or portgroup by its attached VIF ID. + + :param task: A TaskManager instance. + :param vif_id: ID of the attached VIF. + :returns: A Port or Portgroup object to which the VIF is attached. + :raises: VifNotAttached if the VIF is not attached. + """ + for port_like_obj in task.portgroups + task.ports: + vif_port_id = self._get_vif_id_by_port_like_obj(port_like_obj) + if vif_port_id == vif_id: + return port_like_obj + raise exception.VifNotAttached(vif=vif_id, node=task.node.uuid) + + @staticmethod + def _get_vif_id_by_port_like_obj(port_like_obj): + """Lookup the VIF attached to a port or portgroup. + + :param port_like_obj: A port or portgroup to check. + :returns: The ID of the attached VIF, or None. + """ + # FIXME(sambetts) Remove this when we no longer support a nova + # driver that uses port.extra + return (port_like_obj.internal_info.get(TENANT_VIF_KEY) or + port_like_obj.extra.get('vif_port_id')) + + def vif_list(self, task): + """List attached VIF IDs for a node + + :param task: A TaskManager instance. + :returns: List of VIF dictionaries, each dictionary will have an 'id' + entry with the ID of the VIF. + """ + vifs = [] + for port_like_obj in task.ports + task.portgroups: + vif_id = self._get_vif_id_by_port_like_obj(port_like_obj) + if vif_id: + vifs.append({'id': vif_id}) + return vifs + + def get_current_vif(self, task, p_obj): + """Returns the currently used VIF associated with port or portgroup + + We are booting the node only in one network at a time, and presence of + cleaning_vif_port_id means we're doing cleaning, of + provisioning_vif_port_id - provisioning. + Otherwise it's a tenant network + + :param task: A TaskManager instance. + :param p_obj: Ironic port or portgroup object. + :returns: VIF ID associated with p_obj or None. + """ + + return (p_obj.internal_info.get('cleaning_vif_port_id') or + p_obj.internal_info.get('provisioning_vif_port_id') or + self._get_vif_id_by_port_like_obj(p_obj) or None) + + +class NeutronVIFPortIDMixin(VIFPortIDMixin): + """VIF port ID mixin class for neutron network interfaces. + + Mixin class that provides VIF-related network interface methods for neutron + network interfaces. On VIF attach/detach, the associated neutron port will + be updated. + """ def port_changed(self, task, port_obj): """Handle any actions required when a port changes @@ -296,8 +397,7 @@ class NeutronVIFPortIDMixin(object): if port_obj.portgroup_id: portgroup_obj = [pg for pg in task.portgroups if pg.id == port_obj.portgroup_id][0] - vif = (port_obj.internal_info.get(TENANT_VIF_KEY) or - port_obj.extra.get('vif_port_id')) + vif = self._get_vif_id_by_port_like_obj(port_obj) if 'address' in port_obj.obj_what_changed(): if vif: neutron.update_port_address(vif, port_obj.address) @@ -363,8 +463,7 @@ class NeutronVIFPortIDMixin(object): # Do not touch neutron port if we removed address on portgroup. if ('address' in portgroup_obj.obj_what_changed() and portgroup_obj.address): - pg_vif = (portgroup_obj.internal_info.get(TENANT_VIF_KEY) or - portgroup_obj.extra.get('vif_port_id')) + pg_vif = self._get_vif_id_by_port_like_obj(portgroup_obj) if pg_vif: neutron.update_port_address(pg_vif, portgroup_obj.address) @@ -382,8 +481,7 @@ class NeutronVIFPortIDMixin(object): ports = [p for p in task.ports if p.portgroup_id == portgroup_obj.id] for p in ports: - vif = p.internal_info.get( - TENANT_VIF_KEY, p.extra.get('vif_port_id')) + vif = self._get_vif_id_by_port_like_obj(p) reason = [] if p.pxe_enabled: reason.append("'pxe_enabled' is set to True") @@ -398,21 +496,6 @@ class NeutronVIFPortIDMixin(object): 'reason': ', '.join(reason)}) raise exception.Conflict(msg) - def vif_list(self, task): - """List attached VIF IDs for a node - - :param task: A TaskManager instance. - :returns: List of VIF dictionaries, each dictionary will have an 'id' - entry with the ID of the VIF. - """ - vifs = [] - for port_like_obj in task.ports + task.portgroups: - vif_id = port_like_obj.internal_info.get( - TENANT_VIF_KEY, port_like_obj.extra.get('vif_port_id')) - if vif_id: - vifs.append({'id': vif_id}) - return vifs - def vif_attach(self, task, vif_info): """Attach a virtual network interface to a node @@ -503,10 +586,8 @@ class NeutronVIFPortIDMixin(object): 'vif': vif_id, 'port': vif_id, 'mac': port_like_obj.address}) - int_info = port_like_obj.internal_info - int_info[TENANT_VIF_KEY] = vif_id - port_like_obj.internal_info = int_info - port_like_obj.save() + self._save_vif_to_port_like_obj(port_like_obj, vif_id) + # NOTE(vsaienko) allow to attach VIF to active instance. if task.node.provision_state == states.ACTIVE: plug_port_to_tenant_network(task, port_like_obj, client=client) @@ -519,44 +600,12 @@ class NeutronVIFPortIDMixin(object): :raises: VifNotAttached if VIF not attached. :raises: NetworkError: if unbind Neutron port failed. """ + # NOTE(mgoddard): Lookup the port first to check that the VIF is + # attached, and fail if not. + port_like_obj = self._get_port_like_obj_by_vif_id(task, vif_id) - # NOTE(vsaienko) We picking object to attach on vif-attach side. - # Here we should only detach VIF and shouldn't duplicate/follow - # attach rules, just walk over all objects and detach VIF. - for port_like_obj in task.portgroups + task.ports: - # FIXME(sambetts) Remove this when we no longer support a nova - # driver that uses port.extra - vif_port_id = port_like_obj.internal_info.get( - TENANT_VIF_KEY, port_like_obj.extra.get("vif_port_id")) - if vif_port_id == vif_id: - int_info = port_like_obj.internal_info - extra = port_like_obj.extra - int_info.pop(TENANT_VIF_KEY, None) - extra.pop('vif_port_id', None) - port_like_obj.extra = extra - port_like_obj.internal_info = int_info - port_like_obj.save() - # NOTE(vsaienko) allow to unplug VIFs from ACTIVE instance. - if task.node.provision_state == states.ACTIVE: - neutron.unbind_neutron_port(vif_port_id) - break - else: - raise exception.VifNotAttached(vif=vif_id, node=task.node.uuid) + self._clear_vif_from_port_like_obj(port_like_obj) - def get_current_vif(self, task, p_obj): - """Returns the currently used VIF associated with port or portgroup - - We are booting the node only in one network at a time, and presence of - cleaning_vif_port_id means we're doing cleaning, of - provisioning_vif_port_id - provisioning. - Otherwise it's a tenant network - - :param task: A TaskManager instance. - :param p_obj: Ironic port or portgroup object. - :returns: VIF ID associated with p_obj or None. - """ - - return (p_obj.internal_info.get('cleaning_vif_port_id') or - p_obj.internal_info.get('provisioning_vif_port_id') or - p_obj.internal_info.get(TENANT_VIF_KEY) or - p_obj.extra.get('vif_port_id') or None) + # NOTE(vsaienko) allow to unplug VIFs from ACTIVE instance. + if task.node.provision_state == states.ACTIVE: + neutron.unbind_neutron_port(vif_id) diff --git a/ironic/tests/unit/drivers/modules/network/test_common.py b/ironic/tests/unit/drivers/modules/network/test_common.py index fc5c44d5e86..af6d2451cec 100644 --- a/ironic/tests/unit/drivers/modules/network/test_common.py +++ b/ironic/tests/unit/drivers/modules/network/test_common.py @@ -436,13 +436,13 @@ class TestCommonFunctions(db_base.DbTestCase): task, self.port) -class TestNeutronVifPortIDMixin(db_base.DbTestCase): +class TestVifPortIDMixin(db_base.DbTestCase): def setUp(self): - super(TestNeutronVifPortIDMixin, self).setUp() + super(TestVifPortIDMixin, self).setUp() self.config(enabled_drivers=['fake']) mgr_utils.mock_the_extension_manager() - self.interface = common.NeutronVIFPortIDMixin() + self.interface = common.VIFPortIDMixin() self.node = obj_utils.create_test_node(self.context, network_interface='neutron') self.port = obj_utils.create_test_port( @@ -450,8 +450,121 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): address='52:54:00:cf:2d:32', extra={'vif_port_id': uuidutils.generate_uuid(), 'client-id': 'fake1'}) - self.neutron_port = {'id': '132f871f-eaec-4fed-9475-0d54465e0f00', - 'mac_address': '52:54:00:cf:2d:32'} + + def test__save_vif_to_port_like_obj_port(self): + self.port.extra = {} + self.port.save() + vif_id = "fake_vif_id" + self.interface._save_vif_to_port_like_obj(self.port, vif_id) + self.port.refresh() + self.assertIn(common.TENANT_VIF_KEY, self.port.internal_info) + self.assertEqual(vif_id, + self.port.internal_info[common.TENANT_VIF_KEY]) + self.assertEqual({}, self.port.extra) + + def test__save_vif_to_port_like_obj_portgroup(self): + vif_id = "fake_vif_id" + pg = obj_utils.create_test_portgroup( + self.context, node_id=self.node.id) + obj_utils.create_test_port( + self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', + portgroup_id=pg.id, uuid=uuidutils.generate_uuid() + ) + self.interface._save_vif_to_port_like_obj(pg, vif_id) + pg.refresh() + self.assertIn(common.TENANT_VIF_KEY, pg.internal_info) + self.assertEqual(vif_id, + pg.internal_info[common.TENANT_VIF_KEY]) + self.assertEqual({}, pg.extra) + + def test__clear_vif_from_port_like_obj_in_extra_port(self): + self.interface._clear_vif_from_port_like_obj(self.port) + self.port.refresh() + self.assertNotIn('vif_port_id', self.port.extra) + self.assertNotIn(common.TENANT_VIF_KEY, self.port.internal_info) + + def test__clear_vif_from_port_like_obj_in_internal_info_port(self): + self.port.internal_info = { + common.TENANT_VIF_KEY: self.port.extra['vif_port_id']} + self.port.extra = {} + self.port.save() + + self.interface._clear_vif_from_port_like_obj(self.port) + self.port.refresh() + self.assertNotIn('vif_port_id', self.port.extra) + self.assertNotIn(common.TENANT_VIF_KEY, self.port.internal_info) + + def test__clear_vif_from_port_like_obj_in_extra_portgroup(self): + vif_id = uuidutils.generate_uuid() + pg = obj_utils.create_test_portgroup( + self.context, node_id=self.node.id, + extra={'vif_port_id': vif_id}) + obj_utils.create_test_port( + self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', + portgroup_id=pg.id, uuid=uuidutils.generate_uuid() + ) + self.interface._clear_vif_from_port_like_obj(pg) + pg.refresh() + self.assertNotIn('vif_port_id', pg.extra) + self.assertNotIn(common.TENANT_VIF_KEY, pg.internal_info) + + def test__clear_vif_from_port_like_obj_in_internal_info_portgroup(self): + vif_id = uuidutils.generate_uuid() + pg = obj_utils.create_test_portgroup( + self.context, node_id=self.node.id, + internal_info={common.TENANT_VIF_KEY: vif_id}) + obj_utils.create_test_port( + self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', + portgroup_id=pg.id, uuid=uuidutils.generate_uuid() + ) + self.interface._clear_vif_from_port_like_obj(pg) + pg.refresh() + self.assertNotIn('vif_port_id', pg.extra) + self.assertNotIn(common.TENANT_VIF_KEY, pg.internal_info) + + def test__get_port_like_obj_by_vif_id_in_extra(self): + vif_id = self.port.extra["vif_port_id"] + with task_manager.acquire(self.context, self.node.id) as task: + result = self.interface._get_port_like_obj_by_vif_id(task, vif_id) + self.assertEqual(self.port.id, result.id) + + def test__get_port_like_obj_by_vif_id_in_internal_info(self): + vif_id = self.port.extra["vif_port_id"] + self.port.internal_info = {common.TENANT_VIF_KEY: vif_id} + self.port.extra = {} + self.port.save() + with task_manager.acquire(self.context, self.node.id) as task: + result = self.interface._get_port_like_obj_by_vif_id(task, vif_id) + self.assertEqual(self.port.id, result.id) + + def test__get_port_like_obj_by_vif_id_not_attached(self): + vif_id = self.port.extra["vif_port_id"] + self.port.extra = {} + self.port.save() + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaisesRegex(exception.VifNotAttached, + "it is not attached to it.", + self.interface._get_port_like_obj_by_vif_id, + task, vif_id) + + def test__get_vif_id_by_port_like_obj_in_extra(self): + vif_id = self.port.extra["vif_port_id"] + result = self.interface._get_vif_id_by_port_like_obj(self.port) + self.assertEqual(vif_id, result) + + def test__get_vif_id_by_port_like_obj_in_internal_info(self): + vif_id = self.port.extra["vif_port_id"] + self.port.internal_info = {common.TENANT_VIF_KEY: vif_id} + self.port.extra = {} + self.port.save() + result = self.interface._get_vif_id_by_port_like_obj(self.port) + self.assertEqual(vif_id, result) + + def test__get_vif_id_by_port_like_obj_not_attached(self): + self.port.extra = {} + self.port.save() + result = self.interface._get_vif_id_by_port_like_obj(self.port) + self.assertIsNone(result) def test_vif_list_extra(self): vif_id = uuidutils.generate_uuid() @@ -495,265 +608,6 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): vifs = self.interface.vif_list(task) self.assertEqual([{'id': vif_id}], vifs) - @mock.patch.object(common, 'get_free_port_like_object', autospec=True) - @mock.patch.object(neutron_common, 'get_client', autospec=True) - @mock.patch.object(neutron_common, 'update_port_address', autospec=True) - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach(self, mock_gpbpi, mock_upa, mock_client, mock_gfp): - self.port.extra = {} - self.port.save() - vif = {'id': "fake_vif_id"} - mock_gfp.return_value = self.port - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_attach(task, vif) - self.port.refresh() - self.assertEqual("fake_vif_id", self.port.internal_info.get( - common.TENANT_VIF_KEY)) - mock_client.assert_called_once_with() - self.assertFalse(mock_gpbpi.called) - mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) - mock_client.return_value.show_port.assert_called_once_with( - 'fake_vif_id') - mock_upa.assert_called_once_with("fake_vif_id", self.port.address) - - @mock.patch.object(common, 'get_free_port_like_object', autospec=True) - @mock.patch.object(neutron_common, 'get_client', autospec=True) - @mock.patch.object(neutron_common, 'update_port_address', autospec=True) - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach_with_physnet(self, mock_gpbpi, mock_upa, mock_client, - mock_gfp): - self.port.extra = {} - self.port.physical_network = 'physnet1' - self.port.save() - vif = {'id': "fake_vif_id"} - mock_gpbpi.return_value = {'physnet1'} - mock_gfp.return_value = self.port - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_attach(task, vif) - self.port.refresh() - self.assertEqual("fake_vif_id", self.port.internal_info.get( - common.TENANT_VIF_KEY)) - mock_client.assert_called_once_with() - mock_gpbpi.assert_called_once_with(mock_client.return_value, - 'fake_vif_id') - mock_gfp.assert_called_once_with(task, 'fake_vif_id', {'physnet1'}) - mock_client.return_value.show_port.assert_called_once_with( - 'fake_vif_id') - mock_upa.assert_called_once_with("fake_vif_id", self.port.address) - - @mock.patch.object(common, 'get_free_port_like_object', autospec=True) - @mock.patch.object(neutron_common, 'get_client', autospec=True) - @mock.patch.object(neutron_common, 'update_port_address') - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach_portgroup_no_address(self, mock_gpbpi, mock_upa, - mock_client, mock_gfp): - pg = obj_utils.create_test_portgroup( - self.context, node_id=self.node.id, address=None) - mock_gfp.return_value = pg - obj_utils.create_test_port( - self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', - uuid=uuidutils.generate_uuid(), portgroup_id=pg.id) - vif = {'id': "fake_vif_id"} - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_attach(task, vif) - pg.refresh() - self.assertEqual(vif['id'], - pg.internal_info[common.TENANT_VIF_KEY]) - mock_client.assert_called_once_with() - self.assertFalse(mock_gpbpi.called) - mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) - self.assertFalse(mock_client.return_value.show_port.called) - self.assertFalse(mock_upa.called) - - @mock.patch.object(neutron_common, 'get_client', autospec=True) - @mock.patch.object(neutron_common, 'update_port_address') - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach_update_port_exception(self, mock_gpbpi, mock_upa, - mock_client): - self.port.extra = {} - self.port.physical_network = 'physnet1' - self.port.save() - vif = {'id': "fake_vif_id"} - mock_gpbpi.return_value = {'physnet1'} - mock_upa.side_effect = ( - exception.FailedToUpdateMacOnPort(port_id='fake')) - with task_manager.acquire(self.context, self.node.id) as task: - self.assertRaisesRegex( - exception.NetworkError, "can not update Neutron port", - self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() - mock_gpbpi.assert_called_once_with(mock_client.return_value, - 'fake_vif_id') - mock_client.return_value.show_port.assert_called_once_with( - 'fake_vif_id') - - @mock.patch.object(common, 'get_free_port_like_object', autospec=True) - @mock.patch.object(neutron_common, 'get_client', autospec=True) - @mock.patch.object(neutron_common, 'update_port_address') - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach_neutron_absent(self, mock_gpbpi, mock_upa, - mock_client, mock_gfp): - self.port.extra = {} - self.port.physical_network = 'physnet1' - self.port.save() - vif = {'id': "fake_vif_id"} - mock_gfp.return_value = self.port - mock_client.return_value.show_port.side_effect = ( - neutron_exceptions.NeutronClientException) - mock_gpbpi.side_effect = exception.NetworkError - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_attach(task, vif) - mock_client.assert_called_once_with() - mock_gpbpi.assert_called_once_with(mock_client.return_value, - 'fake_vif_id') - mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) - mock_client.return_value.show_port.assert_called_once_with( - 'fake_vif_id') - self.assertFalse(mock_upa.called) - - @mock.patch.object(common, 'get_free_port_like_object', autospec=True) - @mock.patch.object(neutron_common, 'get_client') - @mock.patch.object(neutron_common, 'update_port_address') - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach_portgroup_physnet_inconsistent(self, mock_gpbpi, - mock_upa, mock_client, - mock_gfp): - self.port.extra = {} - self.port.physical_network = 'physnet1' - self.port.save() - vif = {'id': "fake_vif_id"} - mock_gpbpi.return_value = {'anyphysnet'} - mock_gfp.side_effect = exception.PortgroupPhysnetInconsistent( - portgroup='fake-portgroup-id', physical_networks='physnet1') - with task_manager.acquire(self.context, self.node.id) as task: - self.assertRaises( - exception.PortgroupPhysnetInconsistent, - self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() - mock_gpbpi.assert_called_once_with(mock_client.return_value, - 'fake_vif_id') - self.assertFalse(mock_upa.called) - - @mock.patch.object(common, 'get_free_port_like_object', autospec=True) - @mock.patch.object(neutron_common, 'get_client') - @mock.patch.object(neutron_common, 'update_port_address') - @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', - autospec=True) - def test_vif_attach_multiple_segment_mappings(self, mock_gpbpi, mock_upa, - mock_client, mock_gfp): - self.port.extra = {} - self.port.physical_network = 'physnet1' - self.port.save() - obj_utils.create_test_port( - self.context, node_id=self.node.id, uuid=uuidutils.generate_uuid(), - address='52:54:00:cf:2d:33', physical_network='physnet2') - vif = {'id': "fake_vif_id"} - mock_gpbpi.return_value = {'physnet1', 'physnet2'} - with task_manager.acquire(self.context, self.node.id) as task: - self.assertRaises( - exception.VifInvalidForAttach, - self.interface.vif_attach, task, vif) - mock_client.assert_called_once_with() - mock_gpbpi.assert_called_once_with(mock_client.return_value, - 'fake_vif_id') - self.assertFalse(mock_gfp.called) - self.assertFalse(mock_upa.called) - - def test_vif_detach_in_extra(self): - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_detach( - task, self.port.extra['vif_port_id']) - self.port.refresh() - self.assertFalse('vif_port_id' in self.port.extra) - self.assertFalse(common.TENANT_VIF_KEY in self.port.internal_info) - - @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) - def test_vif_detach_in_internal_info(self, mock_unp): - self.port.internal_info = { - common.TENANT_VIF_KEY: self.port.extra['vif_port_id']} - self.port.extra = {} - self.port.save() - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_detach( - task, self.port.internal_info[common.TENANT_VIF_KEY]) - self.port.refresh() - self.assertFalse('vif_port_id' in self.port.extra) - self.assertFalse(common.TENANT_VIF_KEY in self.port.internal_info) - self.assertFalse(mock_unp.called) - - def test_vif_detach_in_extra_portgroup(self): - vif_id = uuidutils.generate_uuid() - pg = obj_utils.create_test_portgroup( - self.context, node_id=self.node.id, - extra={'vif_port_id': vif_id}) - obj_utils.create_test_port( - self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', - portgroup_id=pg.id, uuid=uuidutils.generate_uuid() - ) - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_detach(task, vif_id) - pg.refresh() - self.assertFalse('vif_port_id' in pg.extra) - self.assertFalse(common.TENANT_VIF_KEY in pg.internal_info) - - def test_vif_detach_in_extra_portgroup_manually_attached_to_port(self): - vif_id = uuidutils.generate_uuid() - pg = obj_utils.create_test_portgroup( - self.context, node_id=self.node.id) - port = obj_utils.create_test_port( - self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', - portgroup_id=pg.id, uuid=uuidutils.generate_uuid(), - extra={'vif_port_id': vif_id} - ) - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_detach(task, vif_id) - port.refresh() - pg.refresh() - self.assertFalse('vif_port_id' in port.extra) - self.assertFalse(common.TENANT_VIF_KEY in pg.internal_info) - - def test_vif_detach_in_internal_info_portgroup(self): - vif_id = uuidutils.generate_uuid() - pg = obj_utils.create_test_portgroup( - self.context, node_id=self.node.id, - internal_info={common.TENANT_VIF_KEY: vif_id}) - obj_utils.create_test_port( - self.context, node_id=self.node.id, address='52:54:00:cf:2d:01', - portgroup_id=pg.id, uuid=uuidutils.generate_uuid() - ) - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_detach(task, vif_id) - pg.refresh() - self.assertFalse('vif_port_id' in pg.extra) - self.assertFalse(common.TENANT_VIF_KEY in pg.internal_info) - - def test_vif_detach_not_attached(self): - with task_manager.acquire(self.context, self.node.id) as task: - self.assertRaisesRegex( - exception.VifNotAttached, "it is not attached to it.", - self.interface.vif_detach, task, 'aaa') - - @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) - def test_vif_detach_active_node(self, mock_unp): - vif_id = self.port.extra['vif_port_id'] - self.port.internal_info = { - common.TENANT_VIF_KEY: vif_id} - self.port.extra = {} - self.port.save() - self.node.provision_state = states.ACTIVE - self.node.save() - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.vif_detach( - task, self.port.internal_info[common.TENANT_VIF_KEY]) - self.port.refresh() - mock_unp.assert_called_once_with(vif_id) - def test_get_current_vif_extra_vif_port_id(self): extra = {'vif_port_id': 'foo'} self.port.extra = extra @@ -797,6 +651,326 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase): vif = self.interface.get_current_vif(task, self.port) self.assertIsNone(vif) + +class TestNeutronVifPortIDMixin(db_base.DbTestCase): + + def setUp(self): + super(TestNeutronVifPortIDMixin, self).setUp() + self.config(enabled_drivers=['fake']) + mgr_utils.mock_the_extension_manager() + self.interface = common.NeutronVIFPortIDMixin() + self.node = obj_utils.create_test_node(self.context, + network_interface='neutron') + self.port = obj_utils.create_test_port( + self.context, node_id=self.node.id, + address='52:54:00:cf:2d:32', + extra={'vif_port_id': uuidutils.generate_uuid(), + 'client-id': 'fake1'}) + self.neutron_port = {'id': '132f871f-eaec-4fed-9475-0d54465e0f00', + 'mac_address': '52:54:00:cf:2d:32'} + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address', autospec=True) + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach(self, mock_gpbpi, mock_upa, mock_client, mock_gfp, + mock_save): + vif = {'id': "fake_vif_id"} + mock_gfp.return_value = self.port + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_attach(task, vif) + mock_client.assert_called_once_with() + self.assertFalse(mock_gpbpi.called) + mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) + mock_client.return_value.show_port.assert_called_once_with( + 'fake_vif_id') + mock_upa.assert_called_once_with("fake_vif_id", self.port.address) + mock_save.assert_called_once_with(self.port, "fake_vif_id") + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address', autospec=True) + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_failure(self, mock_gpbpi, mock_upa, mock_client, + mock_gfp, mock_save): + vif = {'id': "fake_vif_id"} + mock_gfp.side_effect = exception.NoFreePhysicalPorts(vif='fake-vif') + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaises(exception.NoFreePhysicalPorts, + self.interface.vif_attach, task, vif) + mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) + self.assertFalse(mock_save.called) + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address', autospec=True) + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_with_physnet(self, mock_gpbpi, mock_upa, mock_client, + mock_gfp, mock_save): + self.port.physical_network = 'physnet1' + self.port.save() + vif = {'id': "fake_vif_id"} + mock_gpbpi.return_value = {'physnet1'} + mock_gfp.return_value = self.port + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_attach(task, vif) + mock_client.assert_called_once_with() + mock_gpbpi.assert_called_once_with(mock_client.return_value, + 'fake_vif_id') + mock_gfp.assert_called_once_with(task, 'fake_vif_id', {'physnet1'}) + mock_client.return_value.show_port.assert_called_once_with( + 'fake_vif_id') + mock_upa.assert_called_once_with("fake_vif_id", self.port.address) + mock_save.assert_called_once_with(self.port, "fake_vif_id") + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'plug_port_to_tenant_network', autospec=True) + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address', autospec=True) + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_active_node(self, mock_gpbpi, mock_upa, mock_client, + mock_gfp, mock_plug, mock_save): + self.node.provision_state = states.ACTIVE + self.node.save() + vif = {'id': "fake_vif_id"} + mock_gfp.return_value = self.port + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_attach(task, vif) + mock_client.assert_called_once_with() + self.assertFalse(mock_gpbpi.called) + mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) + mock_client.return_value.show_port.assert_called_once_with( + 'fake_vif_id') + mock_upa.assert_called_once_with("fake_vif_id", self.port.address) + mock_save.assert_called_once_with(self.port, "fake_vif_id") + mock_plug.assert_called_once_with(task, self.port, mock.ANY) + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'plug_port_to_tenant_network', autospec=True) + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address', autospec=True) + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_active_node_failure(self, mock_gpbpi, mock_upa, + mock_client, mock_gfp, mock_plug, + mock_save): + self.node.provision_state = states.ACTIVE + self.node.save() + vif = {'id': "fake_vif_id"} + mock_gfp.return_value = self.port + mock_plug.side_effect = exception.NetworkError + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaises(exception.NetworkError, + self.interface.vif_attach, task, vif) + mock_client.assert_called_once_with() + self.assertFalse(mock_gpbpi.called) + mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) + mock_client.return_value.show_port.assert_called_once_with( + 'fake_vif_id') + mock_upa.assert_called_once_with("fake_vif_id", self.port.address) + mock_save.assert_called_once_with(self.port, "fake_vif_id") + mock_plug.assert_called_once_with(task, self.port, mock.ANY) + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address') + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_portgroup_no_address(self, mock_gpbpi, mock_upa, + mock_client, mock_gfp, mock_save): + pg = obj_utils.create_test_portgroup( + self.context, node_id=self.node.id, address=None) + mock_gfp.return_value = pg + vif = {'id': "fake_vif_id"} + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_attach(task, vif) + mock_client.assert_called_once_with() + self.assertFalse(mock_gpbpi.called) + mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) + self.assertFalse(mock_client.return_value.show_port.called) + self.assertFalse(mock_upa.called) + mock_save.assert_called_once_with(pg, "fake_vif_id") + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address') + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_update_port_exception(self, mock_gpbpi, mock_upa, + mock_client, mock_save): + self.port.extra = {} + self.port.physical_network = 'physnet1' + self.port.save() + vif = {'id': "fake_vif_id"} + mock_gpbpi.return_value = {'physnet1'} + mock_upa.side_effect = ( + exception.FailedToUpdateMacOnPort(port_id='fake')) + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaisesRegex( + exception.NetworkError, "can not update Neutron port", + self.interface.vif_attach, task, vif) + mock_client.assert_called_once_with() + mock_gpbpi.assert_called_once_with(mock_client.return_value, + 'fake_vif_id') + mock_client.return_value.show_port.assert_called_once_with( + 'fake_vif_id') + self.assertFalse(mock_save.called) + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @mock.patch.object(neutron_common, 'update_port_address') + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_neutron_absent(self, mock_gpbpi, mock_upa, + mock_client, mock_gfp, mock_save): + self.port.physical_network = 'physnet1' + self.port.save() + vif = {'id': "fake_vif_id"} + mock_gfp.return_value = self.port + mock_client.return_value.show_port.side_effect = ( + neutron_exceptions.NeutronClientException) + mock_gpbpi.side_effect = exception.NetworkError + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_attach(task, vif) + mock_client.assert_called_once_with() + mock_gpbpi.assert_called_once_with(mock_client.return_value, + 'fake_vif_id') + mock_gfp.assert_called_once_with(task, 'fake_vif_id', set()) + mock_client.return_value.show_port.assert_called_once_with( + 'fake_vif_id') + self.assertFalse(mock_upa.called) + self.assertTrue(mock_save.called) + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client') + @mock.patch.object(neutron_common, 'update_port_address') + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_portgroup_physnet_inconsistent(self, mock_gpbpi, + mock_upa, mock_client, + mock_gfp, mock_save): + self.port.physical_network = 'physnet1' + self.port.save() + vif = {'id': "fake_vif_id"} + mock_gpbpi.return_value = {'anyphysnet'} + mock_gfp.side_effect = exception.PortgroupPhysnetInconsistent( + portgroup='fake-portgroup-id', physical_networks='physnet1') + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaises( + exception.PortgroupPhysnetInconsistent, + self.interface.vif_attach, task, vif) + mock_client.assert_called_once_with() + mock_gpbpi.assert_called_once_with(mock_client.return_value, + 'fake_vif_id') + self.assertFalse(mock_upa.called) + self.assertFalse(mock_save.called) + + @mock.patch.object(common.VIFPortIDMixin, '_save_vif_to_port_like_obj') + @mock.patch.object(common, 'get_free_port_like_object', autospec=True) + @mock.patch.object(neutron_common, 'get_client') + @mock.patch.object(neutron_common, 'update_port_address') + @mock.patch.object(neutron_common, 'get_physnets_by_port_uuid', + autospec=True) + def test_vif_attach_multiple_segment_mappings(self, mock_gpbpi, mock_upa, + mock_client, mock_gfp, + mock_save): + self.port.physical_network = 'physnet1' + self.port.save() + obj_utils.create_test_port( + self.context, node_id=self.node.id, uuid=uuidutils.generate_uuid(), + address='52:54:00:cf:2d:33', physical_network='physnet2') + vif = {'id': "fake_vif_id"} + mock_gpbpi.return_value = {'physnet1', 'physnet2'} + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaises( + exception.VifInvalidForAttach, + self.interface.vif_attach, task, vif) + mock_client.assert_called_once_with() + mock_gpbpi.assert_called_once_with(mock_client.return_value, + 'fake_vif_id') + self.assertFalse(mock_gfp.called) + self.assertFalse(mock_upa.called) + self.assertFalse(mock_save.called) + + @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj') + @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) + @mock.patch.object(common.VIFPortIDMixin, '_get_port_like_obj_by_vif_id') + def test_vif_detach(self, mock_get, mock_unp, mock_clear): + mock_get.return_value = self.port + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_detach(task, 'fake_vif_id') + mock_get.assert_called_once_with(task, 'fake_vif_id') + self.assertFalse(mock_unp.called) + mock_clear.assert_called_once_with(self.port) + + @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj') + @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) + @mock.patch.object(common.VIFPortIDMixin, '_get_port_like_obj_by_vif_id') + def test_vif_detach_portgroup(self, mock_get, mock_unp, mock_clear): + pg = obj_utils.create_test_portgroup( + self.context, node_id=self.node.id) + mock_get.return_value = pg + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_detach(task, 'fake_vif_id') + mock_get.assert_called_once_with(task, 'fake_vif_id') + self.assertFalse(mock_unp.called) + mock_clear.assert_called_once_with(pg) + + @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj') + @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) + @mock.patch.object(common.VIFPortIDMixin, '_get_port_like_obj_by_vif_id') + def test_vif_detach_not_attached(self, mock_get, mock_unp, mock_clear): + mock_get.side_effect = exception.VifNotAttached(vif='fake-vif', + node='fake-node') + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaisesRegex( + exception.VifNotAttached, "it is not attached to it.", + self.interface.vif_detach, task, 'fake_vif_id') + mock_get.assert_called_once_with(task, 'fake_vif_id') + self.assertFalse(mock_unp.called) + self.assertFalse(mock_clear.called) + + @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj') + @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) + @mock.patch.object(common.VIFPortIDMixin, '_get_port_like_obj_by_vif_id') + def test_vif_detach_active_node(self, mock_get, mock_unp, mock_clear): + self.node.provision_state = states.ACTIVE + self.node.save() + mock_get.return_value = self.port + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.vif_detach(task, 'fake_vif_id') + mock_get.assert_called_once_with(task, 'fake_vif_id') + mock_clear.assert_called_once_with(self.port) + mock_unp.assert_called_once_with('fake_vif_id') + + @mock.patch.object(common.VIFPortIDMixin, '_clear_vif_from_port_like_obj') + @mock.patch.object(neutron_common, 'unbind_neutron_port', autospec=True) + @mock.patch.object(common.VIFPortIDMixin, '_get_port_like_obj_by_vif_id') + def test_vif_detach_active_node_failure(self, mock_get, mock_unp, + mock_clear): + self.node.provision_state = states.ACTIVE + self.node.save() + mock_get.return_value = self.port + mock_unp.side_effect = exception.NetworkError + with task_manager.acquire(self.context, self.node.id) as task: + self.assertRaises(exception.NetworkError, + self.interface.vif_detach, task, 'fake_vif_id') + mock_get.assert_called_once_with(task, 'fake_vif_id') + mock_clear.assert_called_once_with(self.port) + mock_unp.assert_called_once_with('fake_vif_id') + @mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id', autospec=True) @mock.patch.object(neutron_common, 'update_port_address', autospec=True)