Allow multiple IPv6 ports on router from same network ml2/ovs+dvr
On a recent change [1], some additional checks was added to avoid
ports overlapping cidrs on a router. On this change was also added
a check to do not attach more than one port IPv6 from same network,
but this check need to allow multiple ports when a deployment is
done using ml2/ovs+xvlan+dvr and the router has an external gateway
configured, because two ports are added:
- one with device_owner as network:router_interface_distributed
- another one with device_owner as network:router_centralized_snat
Also an small improvement is done for just run this check over the
existing ports on the router for the same network_id.
[1] https://review.opendev.org/c/openstack/neutron/+/859143
Closes-Bug: #2002800
Change-Id: I765a7b41e7e84f42a3180dfd15e3a41a8e085284
(cherry picked from commit e2097157bb
)
This commit is contained in:
parent
e59ce5568a
commit
6226fa9fd6
|
@ -846,8 +846,11 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||||
if self._port_has_ipv6_address(port):
|
if self._port_has_ipv6_address(port):
|
||||||
for existing_port in (rp.port for rp in router.attached_ports):
|
for existing_port in (rp.port for rp in router.attached_ports):
|
||||||
if (existing_port["id"] != port["id"] and
|
if (existing_port["id"] != port["id"] and
|
||||||
existing_port["network_id"] == port["network_id"] and
|
existing_port["network_id"] == port["network_id"] and
|
||||||
self._port_has_ipv6_address(existing_port)):
|
self._port_has_ipv6_address(existing_port) and
|
||||||
|
port["device_owner"] not in [
|
||||||
|
constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
constants.DEVICE_OWNER_DVR_INTERFACE]):
|
||||||
msg = _("Router already contains IPv6 port %(p)s "
|
msg = _("Router already contains IPv6 port %(p)s "
|
||||||
"belonging to network id %(nid)s. Only one IPv6 port "
|
"belonging to network id %(nid)s. Only one IPv6 port "
|
||||||
"from the same network subnet can be connected to a "
|
"from the same network subnet can be connected to a "
|
||||||
|
@ -974,7 +977,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||||
port=port,
|
port=port,
|
||||||
interface_info=interface_info)
|
interface_info=interface_info)
|
||||||
self._add_router_port(
|
self._add_router_port(
|
||||||
context, port['id'], router, device_owner)
|
context, port, router, device_owner)
|
||||||
|
|
||||||
gw_ips = []
|
gw_ips = []
|
||||||
gw_network_id = None
|
gw_network_id = None
|
||||||
|
@ -1002,10 +1005,10 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||||
subnets[-1]['id'], [subnet['id'] for subnet in subnets])
|
subnets[-1]['id'], [subnet['id'] for subnet in subnets])
|
||||||
|
|
||||||
@db_api.retry_if_session_inactive()
|
@db_api.retry_if_session_inactive()
|
||||||
def _add_router_port(self, context, port_id, router, device_owner):
|
def _add_router_port(self, context, port, router, device_owner):
|
||||||
l3_obj.RouterPort(
|
l3_obj.RouterPort(
|
||||||
context,
|
context,
|
||||||
port_id=port_id,
|
port_id=port['id'],
|
||||||
router_id=router.id,
|
router_id=router.id,
|
||||||
port_type=device_owner
|
port_type=device_owner
|
||||||
).create()
|
).create()
|
||||||
|
@ -1022,15 +1025,19 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||||
if len(router_ports) > 1:
|
if len(router_ports) > 1:
|
||||||
subnets_id = []
|
subnets_id = []
|
||||||
for rp in router_ports:
|
for rp in router_ports:
|
||||||
port = port_obj.Port.get_object(context.elevated(),
|
router_port = port_obj.Port.get_object(context.elevated(),
|
||||||
id=rp.port_id)
|
id=rp.port_id)
|
||||||
if port:
|
if router_port:
|
||||||
|
# NOTE(froyo): Just run the validation in case the new port
|
||||||
|
# added is on the same network than an existing one.
|
||||||
# Only allow one router port with IPv6 subnets per network
|
# Only allow one router port with IPv6 subnets per network
|
||||||
# id
|
# id.
|
||||||
self._validate_one_router_ipv6_port_per_network(
|
if router_port['network_id'] == port['network_id']:
|
||||||
router, port)
|
self._validate_one_router_ipv6_port_per_network(
|
||||||
subnets_id.extend([fixed_ip['subnet_id']
|
router, router_port)
|
||||||
for fixed_ip in port['fixed_ips']])
|
subnets_id.extend(
|
||||||
|
[fixed_ip["subnet_id"]
|
||||||
|
for fixed_ip in router_port["fixed_ips"]])
|
||||||
else:
|
else:
|
||||||
# due to race conditions maybe the port under analysis is
|
# due to race conditions maybe the port under analysis is
|
||||||
# deleted, so instead returning a RouterInterfaceNotFound
|
# deleted, so instead returning a RouterInterfaceNotFound
|
||||||
|
@ -1058,8 +1065,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||||
# make sure the records in routerports table and ports
|
# make sure the records in routerports table and ports
|
||||||
# table are consistent.
|
# table are consistent.
|
||||||
self._core_plugin.update_port(
|
self._core_plugin.update_port(
|
||||||
context, port_id, {'port': {'device_id': router.id,
|
context, port['id'], {'port': {'device_id': router.id,
|
||||||
'device_owner': device_owner}})
|
'device_owner': device_owner}})
|
||||||
|
|
||||||
def _check_router_interface_not_in_use(self, router_id, subnet):
|
def _check_router_interface_not_in_use(self, router_id, subnet):
|
||||||
context = n_ctx.get_admin_context()
|
context = n_ctx.get_admin_context()
|
||||||
|
|
|
@ -577,6 +577,53 @@ class TestL3_NAT_dbonly_mixin(
|
||||||
self.db._validate_one_router_ipv6_port_per_network(
|
self.db._validate_one_router_ipv6_port_per_network(
|
||||||
router, new_port)
|
router, new_port)
|
||||||
|
|
||||||
|
def test__validate_one_router_ipv6_port_per_network_distributed_port(self):
|
||||||
|
port = models_v2.Port(
|
||||||
|
id=uuidutils.generate_uuid(),
|
||||||
|
network_id='foo_network',
|
||||||
|
device_owner=n_const.DEVICE_OWNER_DVR_INTERFACE,
|
||||||
|
fixed_ips=[models_v2.IPAllocation(
|
||||||
|
ip_address=str(netaddr.IPNetwork(
|
||||||
|
'2001:db8::/32').ip + 1),
|
||||||
|
subnet_id='foo_subnet')])
|
||||||
|
rports = [l3_models.RouterPort(router_id='foo_router', port=port)]
|
||||||
|
router = l3_models.Router(
|
||||||
|
id='foo_router', attached_ports=rports, route_list=[],
|
||||||
|
gw_port_id=None)
|
||||||
|
new_port = models_v2.Port(
|
||||||
|
id=uuidutils.generate_uuid(),
|
||||||
|
network_id='foo_network',
|
||||||
|
device_owner=n_const.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
fixed_ips=[models_v2.IPAllocation(
|
||||||
|
ip_address=str(netaddr.IPNetwork(
|
||||||
|
'2001:db8::/32').ip + 2),
|
||||||
|
subnet_id='foo_subnet')])
|
||||||
|
self.db._validate_one_router_ipv6_port_per_network(router, new_port)
|
||||||
|
|
||||||
|
def test__validate_one_router_ipv6_port_per_network_centralized_snat_port(
|
||||||
|
self):
|
||||||
|
port = models_v2.Port(
|
||||||
|
id=uuidutils.generate_uuid(),
|
||||||
|
network_id='foo_network',
|
||||||
|
device_owner=n_const.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
fixed_ips=[models_v2.IPAllocation(
|
||||||
|
ip_address=str(netaddr.IPNetwork(
|
||||||
|
'2001:db8::/32').ip + 1),
|
||||||
|
subnet_id='foo_subnet')])
|
||||||
|
rports = [l3_models.RouterPort(router_id='foo_router', port=port)]
|
||||||
|
router = l3_models.Router(
|
||||||
|
id='foo_router', attached_ports=rports, route_list=[],
|
||||||
|
gw_port_id=None)
|
||||||
|
new_port = models_v2.Port(
|
||||||
|
id=uuidutils.generate_uuid(),
|
||||||
|
network_id='foo_network',
|
||||||
|
device_owner=n_const.DEVICE_OWNER_DVR_INTERFACE,
|
||||||
|
fixed_ips=[models_v2.IPAllocation(
|
||||||
|
ip_address=str(netaddr.IPNetwork(
|
||||||
|
'2001:db8::/32').ip + 2),
|
||||||
|
subnet_id='foo_subnet')])
|
||||||
|
self.db._validate_one_router_ipv6_port_per_network(router, new_port)
|
||||||
|
|
||||||
def test__validate_one_router_ipv6_port_per_network_failed(self):
|
def test__validate_one_router_ipv6_port_per_network_failed(self):
|
||||||
port = models_v2.Port(
|
port = models_v2.Port(
|
||||||
id=uuidutils.generate_uuid(),
|
id=uuidutils.generate_uuid(),
|
||||||
|
|
Loading…
Reference in New Issue