Add bulk port creation of DB objects

Implement a new function called create_port_obj_bulk that optimizes
bulk port creation operations by streamlining ensuring network existence
and operates on an array of port data.

Change-Id: Ie819c215944514d0bb43c2ce87394825bda41e94
Partially-Implements: blueprint speed-up-neutron-bulk-creation
This commit is contained in:
Nate Johnston 2018-07-23 16:21:30 -04:00
parent dd14501c12
commit 80b48ebd4a
4 changed files with 131 additions and 1 deletions

View File

@ -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()

View File

@ -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 = []

View File

@ -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.")

View File

@ -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