Skip to content

Commit 22ddd65

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Fix bulk create without mac" into stable/yoga
2 parents deb0153 + 2ed8fa5 commit 22ddd65

File tree

2 files changed

+134
-33
lines changed

2 files changed

+134
-33
lines changed

neutron/plugins/ml2/plugin.py

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from netaddr.strategy import eui48
2121
from neutron_lib.agent import constants as agent_consts
2222
from neutron_lib.agent import topics
23+
from neutron_lib.api import converters
2324
from neutron_lib.api.definitions import address_group as addrgrp_def
2425
from neutron_lib.api.definitions import address_scope
2526
from neutron_lib.api.definitions import agent as agent_apidef
@@ -1563,11 +1564,36 @@ def _after_create_port(self, context, result, mech_context):
15631564

15641565
return bound_context.current
15651566

1566-
def allocate_ips_for_ports(self, context, ports):
1567+
def allocate_macs_and_ips_for_ports(self, context, ports):
1568+
macs = self._generate_macs(len(ports))
1569+
network_cache = dict()
15671570
for port in ports:
15681571
port['port']['id'] = (
15691572
port['port'].get('id') or uuidutils.generate_uuid())
15701573

1574+
network_id = port['port'].get('network_id')
1575+
if network_id not in network_cache:
1576+
network = self.get_network(context, network_id)
1577+
network_cache[network_id] = network
1578+
1579+
raw_mac_address = port['port'].get('mac_address',
1580+
const.ATTR_NOT_SPECIFIED)
1581+
if raw_mac_address is const.ATTR_NOT_SPECIFIED:
1582+
raw_mac_address = macs.pop()
1583+
elif self._is_mac_in_use(context, network_id, raw_mac_address):
1584+
raise exc.MacAddressInUse(net_id=network_id,
1585+
mac=raw_mac_address)
1586+
eui_mac_address = converters.convert_to_sanitized_mac_address(
1587+
raw_mac_address)
1588+
# Create the Port object
1589+
# Note: netaddr has an issue with using the correct dialect when
1590+
# the input for EUI is another EUI object, see:
1591+
# https://github.com/netaddr/netaddr/issues/250
1592+
# TODO(lajoskatona): remove this once
1593+
# https://review.opendev.org/c/openstack/neutron-lib/+/865517 is
1594+
# released.
1595+
port['port']['mac_address'] = str(eui_mac_address)
1596+
15711597
# Call IPAM to allocate IP addresses
15721598
try:
15731599
port['ipams'] = self.ipam.allocate_ips_for_port(context, port)
@@ -1577,18 +1603,19 @@ def allocate_ips_for_ports(self, context, ports):
15771603
except ipam_exc.DeferIpam:
15781604
port['ip_allocation'] = (ipalloc_apidef.
15791605
IP_ALLOCATION_DEFERRED)
1580-
return ports
1606+
return ports, network_cache
15811607

15821608
@utils.transaction_guard
15831609
def create_port_bulk(self, context, ports):
15841610
port_list = ports.get('ports')
15851611
for port in port_list:
15861612
self._before_create_port(context, port)
15871613

1588-
port_list = self.allocate_ips_for_ports(context, port_list)
1614+
port_list, net_cache = self.allocate_macs_and_ips_for_ports(
1615+
context, port_list)
15891616

15901617
try:
1591-
return self._create_port_bulk(context, port_list)
1618+
return self._create_port_bulk(context, port_list, net_cache)
15921619
except Exception:
15931620
with excutils.save_and_reraise_exception():
15941621
# If any issue happened allocated IP addresses needs to be
@@ -1598,17 +1625,16 @@ def create_port_bulk(self, context, ports):
15981625
context, port, port['ipams'])
15991626

16001627
@db_api.retry_if_session_inactive()
1601-
def _create_port_bulk(self, context, port_list):
1628+
def _create_port_bulk(self, context, port_list, network_cache):
16021629
# TODO(njohnston): Break this up into smaller functions.
16031630
port_data = []
1604-
network_cache = dict()
1605-
macs = self._generate_macs(len(port_list))
16061631
with db_api.CONTEXT_WRITER.using(context):
16071632
for port in port_list:
16081633
# Set up the port request dict
16091634
pdata = port.get('port')
16101635
project_id = pdata.get('project_id') or pdata.get('tenant_id')
16111636
security_group_ids = pdata.get('security_groups')
1637+
network_id = pdata.get('network_id')
16121638
if security_group_ids is const.ATTR_NOT_SPECIFIED:
16131639
security_group_ids = None
16141640
else:
@@ -1620,39 +1646,21 @@ def _create_port_bulk(self, context, port_list):
16201646
bulk_port_data = dict(
16211647
project_id=project_id,
16221648
name=pdata.get('name'),
1623-
network_id=pdata.get('network_id'),
1649+
network_id=network_id,
16241650
admin_state_up=pdata.get('admin_state_up'),
16251651
status=pdata.get('status',
16261652
const.PORT_STATUS_ACTIVE),
16271653
device_id=pdata.get('device_id'),
16281654
device_owner=pdata.get('device_owner'),
16291655
description=pdata.get('description'))
16301656

1631-
# Ensure that the networks exist.
1632-
network_id = pdata.get('network_id')
1633-
if network_id not in network_cache:
1634-
network = self.get_network(context, network_id)
1635-
network_cache[network_id] = network
1636-
else:
1637-
network = network_cache[network_id]
1638-
1639-
# Determine the MAC address
1640-
raw_mac_address = pdata.get('mac_address',
1641-
const.ATTR_NOT_SPECIFIED)
1642-
if raw_mac_address is const.ATTR_NOT_SPECIFIED:
1643-
raw_mac_address = macs.pop()
1644-
elif self._is_mac_in_use(context, network_id, raw_mac_address):
1645-
raise exc.MacAddressInUse(net_id=network_id,
1646-
mac=raw_mac_address)
1647-
eui_mac_address = netaddr.EUI(raw_mac_address,
1648-
dialect=eui48.mac_unix_expanded)
1649-
port['port']['mac_address'] = str(eui_mac_address)
1650-
1651-
# Create the Port object
1652-
db_port_obj = ports_obj.Port(context,
1653-
mac_address=eui_mac_address,
1654-
id=port['port']['id'],
1655-
**bulk_port_data)
1657+
network = network_cache[network_id]
1658+
1659+
db_port_obj = ports_obj.Port(
1660+
context,
1661+
mac_address=netaddr.EUI(port['port']['mac_address'],
1662+
dialect=eui48.mac_unix_expanded),
1663+
id=port['port']['id'], **bulk_port_data)
16561664
db_port_obj.create()
16571665

16581666
# Call IPAM to store allocated IP addresses

neutron/tests/unit/plugins/ml2/test_plugin.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from neutron_lib.plugins import utils as p_utils
4343
from oslo_config import cfg
4444
from oslo_db import exception as db_exc
45+
from oslo_utils import netutils
4546
from oslo_utils import uuidutils
4647
import testtools
4748
import webob
@@ -50,6 +51,7 @@
5051
from neutron.agent import rpc as agent_rpc
5152
from neutron.common import utils
5253
from neutron.db import agents_db
54+
from neutron.db import ipam_pluggable_backend
5355
from neutron.db import provisioning_blocks
5456
from neutron.db import securitygroups_db as sg_db
5557
from neutron.db import segments_db
@@ -1595,6 +1597,97 @@ def test_create_ports_bulk_ip_allocation_reverted_in_case_of_error(self):
15951597

15961598
self.assertEqual(2, deallocate_mock.call_count)
15971599

1600+
def test_create_ports_bulk_ip_allocation_without_mac(self):
1601+
ctx = context.get_admin_context()
1602+
plugin = directory.get_plugin()
1603+
fake_prefix = '2001:db8::/64'
1604+
fake_gateway = 'fe80::1'
1605+
with self.network() as net:
1606+
with self.subnet(net,
1607+
gateway_ip=fake_gateway,
1608+
cidr=fake_prefix,
1609+
ip_version=constants.IP_VERSION_6,
1610+
ipv6_ra_mode='slaac',
1611+
ipv6_address_mode='slaac') as snet_v6,\
1612+
mock.patch.object(plugin, '_before_create_port'),\
1613+
mock.patch.object(plugin, 'get_network') as mock_getnet,\
1614+
mock.patch.object(
1615+
ipam_pluggable_backend.IpamPluggableBackend,
1616+
'_ipam_get_subnets') as fcs:
1617+
fcs.return_value = [snet_v6['subnet']]
1618+
mock_getnet.return_value = net['network']
1619+
net_id = net['network']['id']
1620+
ports = {
1621+
'ports': [{'port': {'network_id': net_id,
1622+
'admin_state_up': True,
1623+
'name': 'test_0',
1624+
'mac_address':
1625+
constants.ATTR_NOT_SPECIFIED,
1626+
'fixed_ips':
1627+
constants.ATTR_NOT_SPECIFIED,
1628+
'device_owner': '',
1629+
'device_id': '',
1630+
'security_groups':
1631+
constants.ATTR_NOT_SPECIFIED,
1632+
'project_id':
1633+
snet_v6['subnet']['project_id'],
1634+
'tenant_id':
1635+
snet_v6['subnet']['tenant_id']}},
1636+
{'port': {'network_id': net_id,
1637+
'admin_state_up': True,
1638+
'name': 'test_1',
1639+
'mac_address':
1640+
constants.ATTR_NOT_SPECIFIED,
1641+
'fixed_ips':
1642+
constants.ATTR_NOT_SPECIFIED,
1643+
'device_owner': '',
1644+
'device_id': '',
1645+
'security_groups':
1646+
constants.ATTR_NOT_SPECIFIED,
1647+
'project_id':
1648+
snet_v6['subnet']['project_id'],
1649+
'tenant_id':
1650+
snet_v6['subnet']['tenant_id']}}
1651+
]}
1652+
b_ports = self.plugin.create_port_bulk(
1653+
ctx, ports)
1654+
self.assertEqual(len(ports['ports']), len(b_ports))
1655+
for port in b_ports:
1656+
self.assertIsNotNone(port['fixed_ips'][0]['ip_address'])
1657+
self.assertTrue(
1658+
netutils.is_valid_ipv6(
1659+
port['fixed_ips'][0]['ip_address']))
1660+
self.assertEqual(1, mock_getnet.call_count)
1661+
1662+
def test_create_ports_bulk_ip_allocation_without_mac_no_net(self):
1663+
ctx = context.get_admin_context()
1664+
plugin = directory.get_plugin()
1665+
project_id = uuidutils.generate_uuid()
1666+
net_id = uuidutils.generate_uuid()
1667+
with mock.patch.object(plugin, '_before_create_port'),\
1668+
mock.patch.object(ipam_pluggable_backend.IpamPluggableBackend,
1669+
'_ipam_get_subnets'):
1670+
ports = {
1671+
'ports': [{'port': {'network_id': net_id,
1672+
'admin_state_up': True,
1673+
'name': 'test_0',
1674+
'mac_address':
1675+
constants.ATTR_NOT_SPECIFIED,
1676+
'fixed_ips':
1677+
constants.ATTR_NOT_SPECIFIED,
1678+
'device_owner': '',
1679+
'device_id': '',
1680+
'security_groups':
1681+
constants.ATTR_NOT_SPECIFIED,
1682+
'project_id': project_id,
1683+
'tenant_id': project_id}}
1684+
]}
1685+
self.assertRaises(
1686+
exc.NetworkNotFound,
1687+
self.plugin.create_port_bulk,
1688+
ctx, ports
1689+
)
1690+
15981691
def test_delete_port_no_notify_in_disassociate_floatingips(self):
15991692
ctx = context.get_admin_context()
16001693
plugin = directory.get_plugin()

0 commit comments

Comments
 (0)