diff --git a/neutron/plugins/vmware/dbexts/networkgw_db.py b/neutron/plugins/vmware/dbexts/networkgw_db.py index 7463d0b1ac..d499e32233 100644 --- a/neutron/plugins/vmware/dbexts/networkgw_db.py +++ b/neutron/plugins/vmware/dbexts/networkgw_db.py @@ -60,6 +60,11 @@ class GatewayDeviceNotFound(exceptions.NotFound): message = _("Network Gateway Device %(device_id)s could not be found.") +class GatewayDevicesNotFound(exceptions.NotFound): + message = _("One or more Network Gateway Devices could not be found: " + "%(device_ids)s.") + + class NetworkGatewayPortInUse(exceptions.InUse): message = _("Port '%(port_id)s' is owned by '%(device_owner)s' and " "therefore cannot be deleted directly via the port API.") @@ -244,7 +249,24 @@ class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase): raise NetworkGatewayPortInUse(port_id=port['id'], device_owner=port['device_owner']) - def create_network_gateway(self, context, network_gateway): + def _validate_device_list(self, context, tenant_id, gateway_data): + device_query = self._query_gateway_devices( + context, filters={'id': [device['id'] + for device in gateway_data['devices']]}) + retrieved_device_ids = set() + for device in device_query: + retrieved_device_ids.add(device['id']) + if device['tenant_id'] != tenant_id: + raise GatewayDeviceNotFound(device_id=device['id']) + missing_device_ids = ( + set(device['id'] for device in gateway_data['devices']) - + retrieved_device_ids) + if missing_device_ids: + raise GatewayDevicesNotFound( + device_ids=",".join(missing_device_ids)) + + def create_network_gateway(self, context, network_gateway, + validate_device_list=True): gw_data = network_gateway[self.gateway_resource] tenant_id = self._get_tenant_id_for_create(context, gw_data) with context.session.begin(subtransactions=True): @@ -252,13 +274,10 @@ class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase): id=gw_data.get('id', uuidutils.generate_uuid()), tenant_id=tenant_id, name=gw_data.get('name')) - # Device list is guaranteed to be a valid list - device_query = self._query_gateway_devices( - context, filters={'id': [device['id'] - for device in gw_data['devices']]}) - for device in device_query: - if device['tenant_id'] != tenant_id: - raise GatewayDeviceNotFound(device_id=device['id']) + # Device list is guaranteed to be a valid list, but some devices + # might still either not exist or belong to a different tenant + if validate_device_list: + self._validate_device_list(context, tenant_id, gw_data) gw_db.devices.extend([NetworkGatewayDeviceReference(**device) for device in gw_data['devices']]) context.session.add(gw_db) diff --git a/neutron/plugins/vmware/plugins/base.py b/neutron/plugins/vmware/plugins/base.py index 6bc2e4d4a3..9b1f95a5ee 100644 --- a/neutron/plugins/vmware/plugins/base.py +++ b/neutron/plugins/vmware/plugins/base.py @@ -1998,11 +1998,12 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin, Create the gateway service on NSX platform and corresponding data structures in Neutron datase. """ - # Ensure the default gateway in the config file is in sync with the db - self._ensure_default_network_gateway() - # Need to re-do authZ checks here in order to avoid creation on NSX gw_data = network_gateway[networkgw.GATEWAY_RESOURCE_NAME] tenant_id = self._get_tenant_id_for_create(context, gw_data) + # Ensure the default gateway in the config file is in sync with the db + self._ensure_default_network_gateway() + # Validate provided gateway device list + self._validate_device_list(context, tenant_id, gw_data) devices = gw_data['devices'] # Populate default physical network where not specified for device in devices: @@ -2029,7 +2030,7 @@ class NsxPluginV2(addr_pair_db.AllowedAddressPairsMixin, raise nsx_exc.NsxPluginException(err_msg=err_msg) gw_data['id'] = nsx_uuid return super(NsxPluginV2, self).create_network_gateway( - context, network_gateway) + context, network_gateway, validate_device_list=False) def delete_network_gateway(self, context, gateway_id): """Remove a layer-2 network gateway. diff --git a/neutron/tests/unit/vmware/extensions/test_networkgw.py b/neutron/tests/unit/vmware/extensions/test_networkgw.py index 7427527545..58ec9ce8c4 100644 --- a/neutron/tests/unit/vmware/extensions/test_networkgw.py +++ b/neutron/tests/unit/vmware/extensions/test_networkgw.py @@ -557,6 +557,13 @@ class NetworkGatewayDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase): 'json', _uuid(), name=name, devices=devices) self.assertEqual(404, res.status_int) + def test_create_network_gateway_non_existent_device_raises_404(self): + name = 'test-gw' + devices = [{'id': _uuid(), 'interface_name': 'xxx'}] + res = self._create_network_gateway( + 'json', _uuid(), name=name, devices=devices) + self.assertEqual(404, res.status_int) + def test_delete_network_gateway(self): tenant_id = _uuid() with self._gateway_device(tenant_id=tenant_id) as dev: @@ -979,9 +986,12 @@ class TestNetworkGateway(test_nsx_plugin.NsxPluginV2TestCase, with mock.patch.object(nsxlib.l2gateway, 'create_l2_gw_service', new=raise_nsx_api_exc): - with self._gateway_device() as dev: + tenant_id = _uuid() + with self._gateway_device(tenant_id=tenant_id) as dev: res = self._create_network_gateway( - self.fmt, 'xxx', name='yyy', + self.fmt, + tenant_id, + name='yyy', devices=[{'id': dev[self.dev_resource]['id']}]) self.assertEqual(500, res.status_int) @@ -989,9 +999,12 @@ class TestNetworkGateway(test_nsx_plugin.NsxPluginV2TestCase, with mock.patch.object(nsxlib.l2gateway, 'create_l2_gw_service', side_effect=api_exc.Conflict): - with self._gateway_device() as dev: + tenant_id = _uuid() + with self._gateway_device(tenant_id=tenant_id) as dev: res = self._create_network_gateway( - self.fmt, 'xxx', name='yyy', + self.fmt, + tenant_id, + name='yyy', devices=[{'id': dev[self.dev_resource]['id']}]) self.assertEqual(409, res.status_int)