diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 1136bdd96e4..7880ddb1754 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -61,6 +61,7 @@ from neutron.objects import network as network_obj from neutron.objects import ports as port_obj from neutron.objects import subnet as subnet_obj from neutron.objects import subnetpool as subnetpool_obj +from neutron.plugins.ml2.common import exceptions as ml2_exceptions LOG = logging.getLogger(__name__) @@ -1266,6 +1267,24 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, def create_port_bulk(self, context, ports): return self._create_bulk('port', context, ports) + def _create_db_port_obj_bulk(self, context, port_data): + db_ports = [] + macs = self._generate_macs(len(port_data)) + with db_api.CONTEXT_WRITER.using(context): + for port in port_data: + raw_mac_address = port.pop('mac_address', + constants.ATTR_NOT_SPECIFIED) + if raw_mac_address is constants.ATTR_NOT_SPECIFIED: + raw_mac_address = macs.pop() + eui_mac_address = netaddr.EUI(raw_mac_address, 48) + db_port_obj = port_obj.Port(context, + mac_address=eui_mac_address, + id=uuidutils.generate_uuid(), + **port) + db_port_obj.create() + db_ports.append(db_port_obj) + return db_ports + def _create_db_port_obj(self, context, port_data): mac_address = port_data.pop('mac_address', None) if mac_address: @@ -1284,6 +1303,50 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, db_port = self.create_port_db(context, port) return self._make_port_dict(db_port, process_extensions=False) + def create_port_obj_bulk(self, context, ports): + bulk_port_data = [] + network_ids = set() + with db_api.CONTEXT_WRITER.using(context): + for port in ports: + fixed_ips = port['port'].get('fixed_ips') + if fixed_ips is not constants.ATTR_NOT_SPECIFIED: + raise ml2_exceptions.BulkPortCannotHaveFixedIpError() + pdata = port['port'] + if pdata.get('device_owner'): + self._enforce_device_owner_not_router_intf_or_device_id( + context, pdata.get('device_owner'), + pdata.get('device_id'), pdata.get('tenant_id')) + bulk_port_data.append(dict(project_id=pdata.get('project_id'), + name=pdata.get('name'), + network_id=pdata.get('network_id'), + admin_state_up=pdata.get('admin_state_up'), + status=pdata.get('status', + constants.PORT_STATUS_ACTIVE), + mac_address=pdata.get('mac_address'), + device_id=pdata.get('device_id'), + device_owner=pdata.get('device_owner'), + description=pdata.get('description'))) + + # Ensure that the networks exist. + network_id = pdata.get('network_id') + if network_id not in network_ids: + self._get_network(context, network_id) + network_ids.add(network_id) + + db_ports = self._create_db_port_obj_bulk(context, bulk_port_data) + + for db_port in db_ports: + try: + self.ipam.allocate_ips_for_port_and_store( + context, db_port, db_port['id']) + db_port['ip_allocation'] = (ipalloc_apidef. + IP_ALLOCATION_IMMEDIATE) + except ipam_exc.DeferIpam: + db_port['ip_allocation'] = (ipalloc_apidef. + IP_ALLOCATION_DEFERRED) + + return db_ports + def create_port_db(self, context, port): p = port['port'] port_id = p.get('id') or uuidutils.generate_uuid() diff --git a/neutron/db/ipam_pluggable_backend.py b/neutron/db/ipam_pluggable_backend.py index 6ed84df98ab..68b88cbcfa1 100644 --- a/neutron/db/ipam_pluggable_backend.py +++ b/neutron/db/ipam_pluggable_backend.py @@ -165,7 +165,14 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin): # Deepcopy doesn't work correctly in this case, because copy of # ATTR_NOT_SPECIFIED object happens. Address of copied object doesn't # match original object, so 'is' check fails - port_copy = {'port': port['port'].copy()} + # TODO(njohnston): Different behavior is required depending on whether + # a Port object is used or not; once conversion to OVO is complete only + # the 'else' will be needed. + if isinstance(port, dict): + port_copy = {'port': port['port'].copy()} + else: + port_copy = {'port': port.to_dict()} + port_copy['port']['id'] = port_id network_id = port_copy['port']['network_id'] ips = [] diff --git a/neutron/plugins/ml2/common/exceptions.py b/neutron/plugins/ml2/common/exceptions.py index 106d4275fa5..f0a58a49d55 100644 --- a/neutron/plugins/ml2/common/exceptions.py +++ b/neutron/plugins/ml2/common/exceptions.py @@ -42,6 +42,12 @@ class ExtensionDriverNotFound(exceptions.InvalidConfigurationOption): "service plugin %(service_plugin)s not found.") +class BulkPortCannotHaveFixedIpError(exceptions.InvalidInput): + """You cannot request fixed IP addresses in a bulk port request.""" + message = _("Fixed IP addresses cannot be requested from a bulk port " + "allocation.") + + class UnknownNetworkType(exceptions.NeutronException): """Network with unknown type.""" message = _("Unknown network type %(network_type)s.") diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index c8c0bf8f53f..670f853a395 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -21,6 +21,7 @@ import itertools import eventlet import mock import netaddr +from neutron_lib.api import validators from neutron_lib.callbacks import exceptions from neutron_lib.callbacks import registry from neutron_lib import constants @@ -59,6 +60,7 @@ from neutron.ipam.drivers.neutrondb_ipam import driver as ipam_driver from neutron.ipam import exceptions as ipam_exc from neutron.objects import network as network_obj from neutron.objects import router as l3_obj +from neutron.plugins.ml2.common import exceptions as ml2_exceptions from neutron.tests import base from neutron.tests import tools from neutron.tests.unit.api import test_extensions @@ -2579,6 +2581,58 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s plugin = directory.get_plugin() self._test_delete_ports_ignores_port_not_found(plugin) + def test_create_port_obj_bulk(self): + cfg.CONF.set_override('base_mac', "12:34:56:00") + test_mac = "00-12-34-56-78-90" + num_ports = 4 + plugin = directory.get_plugin() + tenant_id = 'some_tenant' + device_owner = "me" + ctx = context.Context('', tenant_id) + with self.network(tenant_id=tenant_id) as network_to_use: + net_id = network_to_use['network']['id'] + port = {'port': {'name': 'port', + 'network_id': net_id, + 'mac_address': constants.ATTR_NOT_SPECIFIED, + 'fixed_ips': constants.ATTR_NOT_SPECIFIED, + 'admin_state_up': True, + 'device_id': 'device_id', + 'device_owner': device_owner, + 'tenant_id': tenant_id}} + ports = [copy.deepcopy(port) for x in range(num_ports)] + ports[1]['port']['mac_address'] = test_mac + port_data = plugin.create_port_obj_bulk(ctx, ports) + self.assertEqual(num_ports, len(port_data)) + result_macs = [] + for port in port_data: + port_mac = str(port.get('mac_address')) + self.assertIsNone(validators.validate_mac_address(port_mac)) + result_macs.append(port_mac) + for ip_addr in port.get('fixed_ips'): + self.assertIsNone(validators.validate_ip_address(ip_addr)) + self.assertTrue(test_mac in result_macs) + + def test_create_port_obj_bulk_with_fixed_ips(self): + num_ports = 4 + plugin = directory.get_plugin() + tenant_id = 'some_tenant' + device_owner = "me" + ctx = context.Context('', tenant_id) + with self.network(tenant_id=tenant_id) as network_to_use: + net_id = network_to_use['network']['id'] + fixed_ip = [dict(ip_address='10.0.0.5')] + port = {'port': {'name': 'port', + 'network_id': net_id, + 'mac_address': constants.ATTR_NOT_SPECIFIED, + 'fixed_ips': fixed_ip, + 'admin_state_up': True, + 'device_id': 'device_id', + 'device_owner': device_owner, + 'tenant_id': tenant_id}} + ports = [port for x in range(num_ports)] + self.assertRaises(ml2_exceptions.BulkPortCannotHaveFixedIpError, + plugin.create_port_obj_bulk, ctx, ports) + class TestNetworksV2(NeutronDbPluginV2TestCase): # NOTE(cerberus): successful network update and delete are