From fa72daa9d23df994fffeb775ae49ea601113054e Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Thu, 27 Dec 2018 10:57:38 +0200 Subject: [PATCH] NSX|V3: Add L2GW connection validation 1. Do not allow creating a connection with the same seg-id & bridge cluster again as the NSX fails it 2. In case creation failed, allow delete to succedd (for rollback of the create) Change-Id: I9db62a2b895bfac93381ba5200f96c96163e39a5 --- vmware_nsx/common/exceptions.py | 4 - vmware_nsx/db/db.py | 7 +- .../services/l2gateway/nsx_v3/driver.py | 82 +++++++++++++++---- .../services/l2gateway/test_nsxv3_driver.py | 51 ++++++++++++ 4 files changed, 123 insertions(+), 21 deletions(-) diff --git a/vmware_nsx/common/exceptions.py b/vmware_nsx/common/exceptions.py index 9913b3c74a..0fc8e03b2b 100644 --- a/vmware_nsx/common/exceptions.py +++ b/vmware_nsx/common/exceptions.py @@ -126,10 +126,6 @@ class NoRouterAvailable(n_exc.ResourceExhausted): "No tenant router is available for allocation.") -class NsxL2GWConnectionMappingNotFound(n_exc.NotFound): - message = _('Unable to find mapping for L2 gateway connection: %(conn)s') - - class NsxL2GWDeviceNotFound(n_exc.NotFound): message = _('Unable to find logical L2 gateway device.') diff --git a/vmware_nsx/db/db.py b/vmware_nsx/db/db.py index dd6cb7e0a0..e7e421be79 100644 --- a/vmware_nsx/db/db.py +++ b/vmware_nsx/db/db.py @@ -388,7 +388,12 @@ def get_l2gw_connection_mapping(session, connection_id): return (session.query(nsx_models.NsxL2GWConnectionMapping). filter_by(connection_id=connection_id).one()) except exc.NoResultFound: - raise nsx_exc.NsxL2GWConnectionMappingNotFound(conn=connection_id) + pass + + +def get_l2gw_connection_mappings_by_bridge(session, bridge_endpoint_id): + return (session.query(nsx_models.NsxL2GWConnectionMapping). + filter_by(bridge_endpoint_id=bridge_endpoint_id).all()) # NSXv3 QoS policy id <-> switch Id mapping diff --git a/vmware_nsx/services/l2gateway/nsx_v3/driver.py b/vmware_nsx/services/l2gateway/nsx_v3/driver.py index 1511aaf811..7d57784c77 100644 --- a/vmware_nsx/services/l2gateway/nsx_v3/driver.py +++ b/vmware_nsx/services/l2gateway/nsx_v3/driver.py @@ -29,6 +29,7 @@ from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context +from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from neutron_lib.plugins import utils as plugin_utils @@ -211,26 +212,70 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin): network_id = gw_connection.get(l2gw_const.NETWORK_ID) self._validate_network(context, network_id) + def _get_bridge_cluster(self, context, l2gw_id): + # In NSXv3, there will be only one device configured per L2 gateway. + # The name of the device shall carry the backend bridge cluster's UUID. + devices = self._get_l2_gateway_devices(context, l2gw_id) + return devices[0].get('device_name') + + def _get_conn_seg_id(self, context, gw_connection): + if not gw_connection: + return + seg_id = gw_connection.get(l2gw_const.SEG_ID) + if not seg_id: + # Seg-id was not passed as part of connection-create. Retrieve + # seg-id from L2 gateway's interface. + l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID) + devices = self._get_l2_gateway_devices(context, l2gw_id) + interface = self._get_l2_gw_interfaces(context, devices[0]['id']) + seg_id = interface[0].get(l2gw_const.SEG_ID) + return seg_id + def create_l2_gateway_connection_precommit(self, context, gw_connection): - pass + """Validate the L2 gateway connection + Do not allow another connection with the same bride cluster and seg_id + """ + admin_ctx = context.elevated() + l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID) + seg_id = self._get_conn_seg_id(admin_ctx, gw_connection) + bridge_cluster = self._get_bridge_cluster(admin_ctx, l2gw_id) + + # get all bridge endpoint ports + with db_api.CONTEXT_WRITER.using(admin_ctx): + port_filters = {'device_owner': [nsx_constants.BRIDGE_ENDPOINT]} + ports = self._core_plugin.get_ports( + admin_ctx, filters=port_filters) + for port in ports: + # get the nsx mapping by bridge endpoint + if port.get('device_id'): + mappings = nsx_db.get_l2gw_connection_mappings_by_bridge( + admin_ctx.session, port['device_id']) + for mapping in mappings: + conn_id = mapping.connection_id + # get the matching GW connection + conn = self._get_l2_gateway_connection( + admin_ctx, conn_id) + con_seg_id = self._get_conn_seg_id(admin_ctx, conn) + if (conn and con_seg_id and + int(con_seg_id) == int(seg_id)): + # compare the bridge cluster + conn_bridge_cluster = self._get_bridge_cluster( + admin_ctx, conn.l2_gateway_id) + if conn_bridge_cluster == bridge_cluster: + msg = (_("Cannot create multiple connections " + "with the same segmentation id " + "%(seg_id)s for bridge cluster " + "%(bridge)s") % { + 'seg_id': seg_id, + 'bridge': bridge_cluster}) + raise n_exc.InvalidInput(error_message=msg) def create_l2_gateway_connection_postcommit(self, context, gw_connection): """Create a L2 gateway connection.""" l2gw_id = gw_connection.get(l2gw_const.L2GATEWAY_ID) network_id = gw_connection.get(l2gw_const.NETWORK_ID) - devices = self._get_l2_gateway_devices(context, l2gw_id) - # In NSXv3, there will be only one device configured per L2 gateway. - # The name of the device shall carry the backend bridge cluster's UUID. - device_name = devices[0].get('device_name') - # The seg-id will be provided either during gateway create or gateway - # connection create. l2gateway_db_mixin makes sure that it is - # configured one way or the other. - seg_id = gw_connection.get(l2gw_const.SEG_ID) - if not seg_id: - # Seg-id was not passed as part of connection-create. Retrieve - # seg-id from L2 gateway's interface. - interface = self._get_l2_gw_interfaces(context, devices[0]['id']) - seg_id = interface[0].get(l2gw_const.SEG_ID) + device_name = self._get_bridge_cluster(context, l2gw_id) + seg_id = self._get_conn_seg_id(context, gw_connection) self._validate_segment_id(seg_id) tenant_id = gw_connection['tenant_id'] if context.is_admin and not tenant_id: @@ -272,9 +317,9 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin): fixed_ip['ip_address']) LOG.debug("IP addresses deallocated on port %s", port['id']) except (nsxlib_exc.ManagerError, - n_exc.NeutronException): + n_exc.NeutronException) as e: LOG.exception("Unable to create L2 gateway port, " - "rolling back changes on neutron") + "rolling back changes on neutron: %s", e) self._core_plugin.nsxlib.bridge_endpoint.delete( bridge_endpoint['id']) raise l2gw_exc.L2GatewayServiceDriverError( @@ -309,6 +354,11 @@ class NsxV3Driver(l2gateway_db.L2GatewayMixin): conn_mapping = nsx_db.get_l2gw_connection_mapping( session=context.session, connection_id=gw_connection) + if not conn_mapping: + LOG.error("Unable to delete gateway connection %(id)s: mapping " + "not found", {'id': gw_connection}) + # Do not block the deletion + return bridge_endpoint_id = conn_mapping.get('bridge_endpoint_id') # Delete the logical port from the bridge endpoint. self._core_plugin.delete_port(context=context, diff --git a/vmware_nsx/tests/unit/services/l2gateway/test_nsxv3_driver.py b/vmware_nsx/tests/unit/services/l2gateway/test_nsxv3_driver.py index 6716ad791a..855dcce226 100644 --- a/vmware_nsx/tests/unit/services/l2gateway/test_nsxv3_driver.py +++ b/vmware_nsx/tests/unit/services/l2gateway/test_nsxv3_driver.py @@ -191,6 +191,56 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase, self.assertEqual(net['id'], l2gw_conn['network_id']) self.assertEqual(l2gw['id'], l2gw_conn['l2_gateway_id']) + def test_create_l2_gateway_connections_same_params(self): + type(self.driver)._core_plugin = self.core_plugin + bc_uuid = uuidutils.generate_uuid() + l2gw_data1 = self._get_l2_gateway_data(name='def-l2gw1', + device_name=bc_uuid) + l2gw1 = self._create_l2gateway(l2gw_data1) + l2gw_data2 = self._get_l2_gateway_data(name='def-l2gw2', + device_name=bc_uuid) + l2gw2 = self._create_l2gateway(l2gw_data2) + net_data = self._get_nw_data() + net = self.core_plugin.create_network(self.context, net_data) + l2gw_conn_data1 = {constants.CONNECTION_RESOURCE_NAME: { + 'l2_gateway_id': l2gw1['id'], + 'tenant_id': 'fake_tenant_id', + 'network_id': net['id']}} + self.l2gw_plugin.create_l2_gateway_connection( + self.context, l2gw_conn_data1) + l2gw_conn_data2 = {constants.CONNECTION_RESOURCE_NAME: { + 'l2_gateway_id': l2gw2['id'], + 'tenant_id': 'fake_tenant_id', + 'network_id': net['id']}} + self.assertRaises(n_exc.InvalidInput, + self.l2gw_plugin.create_l2_gateway_connection, + self.context, l2gw_conn_data2) + + def test_create_l2_gateway_connections_different_bridge(self): + type(self.driver)._core_plugin = self.core_plugin + bc_uuid1 = uuidutils.generate_uuid() + bc_uuid2 = uuidutils.generate_uuid() + l2gw_data1 = self._get_l2_gateway_data(name='def-l2gw1', + device_name=bc_uuid1) + l2gw1 = self._create_l2gateway(l2gw_data1) + l2gw_data2 = self._get_l2_gateway_data(name='def-l2gw2', + device_name=bc_uuid2) + l2gw2 = self._create_l2gateway(l2gw_data2) + net_data = self._get_nw_data() + net = self.core_plugin.create_network(self.context, net_data) + l2gw_conn_data1 = {constants.CONNECTION_RESOURCE_NAME: { + 'l2_gateway_id': l2gw1['id'], + 'tenant_id': 'fake_tenant_id', + 'network_id': net['id']}} + self.l2gw_plugin.create_l2_gateway_connection( + self.context, l2gw_conn_data1) + l2gw_conn_data2 = {constants.CONNECTION_RESOURCE_NAME: { + 'l2_gateway_id': l2gw2['id'], + 'tenant_id': 'fake_tenant_id', + 'network_id': net['id']}} + self.l2gw_plugin.create_l2_gateway_connection( + self.context, l2gw_conn_data2) + def test_delete_l2_gateway_connection(self): type(self.driver)._core_plugin = self.core_plugin bc_uuid = uuidutils.generate_uuid() @@ -202,6 +252,7 @@ class TestNsxV3L2GatewayDriver(test_l2gw_db.L2GWTestCase, l2gw_conn_data = {constants.CONNECTION_RESOURCE_NAME: { 'l2_gateway_id': l2gw['id'], 'tenant_id': 'fake_tenant_id', + 'project_id': 'fake_tenant_id', 'network_id': net['id']}} l2gw_conn = self.l2gw_plugin.create_l2_gateway_connection( self.context,