Merge "Disallow subnet cidr of :: without PD"

This commit is contained in:
Zuul 2024-02-13 21:48:00 +00:00 committed by Gerrit Code Review
commit 2d74a93d68
4 changed files with 59 additions and 28 deletions

@ -723,18 +723,19 @@ First, create a network and IPv6 subnet:
+---------------------------+--------------------------------------+ +---------------------------+--------------------------------------+
$ openstack subnet create --ip-version 6 --ipv6-ra-mode slaac \ $ openstack subnet create --ip-version 6 --ipv6-ra-mode slaac \
--ipv6-address-mode slaac --use-default-subnet-pool \ --ipv6-address-mode slaac --use-prefix-delegation \
--network ipv6-pd ipv6-pd-1 --network ipv6-pd ipv6-pd-1
+------------------------+--------------------------------------+ +------------------------+--------------------------------------+
| Field | Value | | Field | Value |
+------------------------+--------------------------------------+ +------------------------+--------------------------------------+
| allocation_pools | ::2-::ffff:ffff:ffff:ffff | | allocation_pools | ::1-::ffff:ffff:ffff:ffff |
| cidr | ::/64 | | cidr | ::/64 |
| created_at | 2017-01-25T19:31:53Z | | created_at | 2017-01-25T19:31:53Z |
| description | | | description | |
| dns_nameservers | | | dns_nameservers | |
| dns_publish_fixed_ip | None |
| enable_dhcp | True | | enable_dhcp | True |
| gateway_ip | ::1 | | gateway_ip | :: |
| headers | | | headers | |
| host_routes | | | host_routes | |
| id | 1319510d-c92c-4532-bf5d-8bcf3da761a1 | | id | 1319510d-c92c-4532-bf5d-8bcf3da761a1 |
@ -747,9 +748,8 @@ First, create a network and IPv6 subnet:
| revision_number | 2 | | revision_number | 2 |
| service_types | | | service_types | |
| subnetpool_id | prefix_delegation | | subnetpool_id | prefix_delegation |
| tags | [] | | tags | |
| updated_at | 2017-01-25T19:31:53Z | | updated_at | 2017-01-25T19:31:53Z |
| use_default_subnetpool | True |
+------------------------+--------------------------------------+ +------------------------+--------------------------------------+
The subnet is initially created with a temporary CIDR before one can be The subnet is initially created with a temporary CIDR before one can be

@ -640,7 +640,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
"the ip_version '%(ip_version)s'") % data "the ip_version '%(ip_version)s'") % data
raise exc.InvalidInput(error_message=msg) raise exc.InvalidInput(error_message=msg)
def _validate_subnet(self, context, s, cur_subnet=None): def _validate_subnet(self, context, s, cur_subnet=None, is_pd=False):
"""Validate a subnet spec.""" """Validate a subnet spec."""
# This method will validate attributes which may change during # This method will validate attributes which may change during
@ -658,6 +658,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
has_cidr = False has_cidr = False
if validators.is_attr_set(s.get('cidr')): if validators.is_attr_set(s.get('cidr')):
self._validate_ip_version(ip_ver, s['cidr'], 'cidr') self._validate_ip_version(ip_ver, s['cidr'], 'cidr')
net = netaddr.IPNetwork(s['cidr'])
has_cidr = True has_cidr = True
# TODO(watanabe.isao): After we found a way to avoid the re-sync # TODO(watanabe.isao): After we found a way to avoid the re-sync
@ -666,15 +667,21 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
dhcp_was_enabled = cur_subnet.enable_dhcp dhcp_was_enabled = cur_subnet.enable_dhcp
else: else:
dhcp_was_enabled = False dhcp_was_enabled = False
# A subnet cidr of '::' is invalid, unless the caller has
# indicated they are doing Prefix Delegation,
# see https://bugs.launchpad.net/neutron/+bug/2028159
if (has_cidr and ip_ver == constants.IP_VERSION_6 and
net.network == netaddr.IPAddress('::') and not
is_pd):
error_message = _("IPv6 subnet '::' is not supported")
raise exc.InvalidInput(error_message=error_message)
if has_cidr and s.get('enable_dhcp') and not dhcp_was_enabled: if has_cidr and s.get('enable_dhcp') and not dhcp_was_enabled:
subnet_prefixlen = netaddr.IPNetwork(s['cidr']).prefixlen
error_message = _("Subnet has a prefix length that is " error_message = _("Subnet has a prefix length that is "
"incompatible with DHCP service enabled") "incompatible with DHCP service enabled")
if ((ip_ver == 4 and subnet_prefixlen > 30) or if ((ip_ver == 4 and net.prefixlen > 30) or
(ip_ver == 6 and subnet_prefixlen > 126)): (ip_ver == 6 and net.prefixlen > 126)):
raise exc.InvalidInput(error_message=error_message) raise exc.InvalidInput(error_message=error_message)
net = netaddr.IPNetwork(s['cidr'])
if net.is_multicast(): if net.is_multicast():
error_message = _("Multicast IP subnet is not supported " error_message = _("Multicast IP subnet is not supported "
"if enable_dhcp is True") "if enable_dhcp is True")
@ -936,9 +943,11 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
raise exc.BadRequest(resource='subnets', msg=msg) raise exc.BadRequest(resource='subnets', msg=msg)
validate = True validate = True
is_pd = False
if subnetpool_id: if subnetpool_id:
self.ipam.validate_pools_with_subnetpool(s) self.ipam.validate_pools_with_subnetpool(s)
if subnetpool_id == constants.IPV6_PD_POOL_ID: if subnetpool_id == constants.IPV6_PD_POOL_ID:
is_pd = True
if has_cidr: if has_cidr:
# We do not currently support requesting a specific # We do not currently support requesting a specific
# cidr with IPv6 prefix delegation. Set the subnetpool_id # cidr with IPv6 prefix delegation. Set the subnetpool_id
@ -956,7 +965,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
raise exc.BadRequest(resource='subnets', msg=msg) raise exc.BadRequest(resource='subnets', msg=msg)
if validate: if validate:
self._validate_subnet(context, s) self._validate_subnet(context, s, is_pd=is_pd)
with db_api.CONTEXT_WRITER.using(context): with db_api.CONTEXT_WRITER.using(context):
network = self._get_network(context, network = self._get_network(context,
@ -1013,7 +1022,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# Fill 'network_id' field with the current value since this is expected # Fill 'network_id' field with the current value since this is expected
# by _validate_segment() in ipam_pluggable_backend. # by _validate_segment() in ipam_pluggable_backend.
s['network_id'] = subnet_obj.network_id s['network_id'] = subnet_obj.network_id
self._validate_subnet(context, s, cur_subnet=subnet_obj) is_pd = s['subnetpool_id'] == constants.IPV6_PD_POOL_ID
self._validate_subnet(context, s, cur_subnet=subnet_obj, is_pd=is_pd)
db_pools = [netaddr.IPRange(p.start, p.end) db_pools = [netaddr.IPRange(p.start, p.end)
for p in subnet_obj.allocation_pools] for p in subnet_obj.allocation_pools]

@ -455,7 +455,7 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
data = {'subnet': {'network_id': net_id, data = {'subnet': {'network_id': net_id,
'ip_version': constants.IP_VERSION_4, 'ip_version': constants.IP_VERSION_4,
'tenant_id': tenant_id}} 'tenant_id': tenant_id}}
if cidr: if cidr and cidr is not constants.ATTR_NOT_SPECIFIED:
data['subnet']['cidr'] = cidr data['subnet']['cidr'] = cidr
for arg in ('ip_version', 'tenant_id', 'subnetpool_id', 'prefixlen', for arg in ('ip_version', 'tenant_id', 'subnetpool_id', 'prefixlen',
'enable_dhcp', 'allocation_pools', 'segment_id', 'enable_dhcp', 'allocation_pools', 'segment_id',
@ -824,7 +824,9 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
as_admin=False): as_admin=False):
if project_id: if project_id:
tenant_id = project_id tenant_id = project_id
cidr = netaddr.IPNetwork(cidr) if cidr else None if (cidr is not None and
cidr != constants.ATTR_NOT_SPECIFIED):
cidr = netaddr.IPNetwork(cidr)
if (gateway_ip is not None and if (gateway_ip is not None and
gateway_ip != constants.ATTR_NOT_SPECIFIED): gateway_ip != constants.ATTR_NOT_SPECIFIED):
gateway_ip = netaddr.IPAddress(gateway_ip) gateway_ip = netaddr.IPAddress(gateway_ip)
@ -1008,7 +1010,7 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
if (k == 'gateway_ip' and ipv6_zero_gateway and if (k == 'gateway_ip' and ipv6_zero_gateway and
keys[k][-3:] == "::0"): keys[k][-3:] == "::0"):
self.assertEqual(keys[k][:-1], resource[res_name][k]) self.assertEqual(keys[k][:-1], resource[res_name][k])
else: elif keys[k] != constants.ATTR_NOT_SPECIFIED:
self.assertEqual(keys[k], resource[res_name][k]) self.assertEqual(keys[k], resource[res_name][k])
@ -3321,7 +3323,6 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
def _test_create_subnet(self, network=None, expected=None, **kwargs): def _test_create_subnet(self, network=None, expected=None, **kwargs):
keys = kwargs.copy() keys = kwargs.copy()
keys.setdefault('cidr', '10.0.0.0/24')
keys.setdefault('ip_version', constants.IP_VERSION_4) keys.setdefault('ip_version', constants.IP_VERSION_4)
keys.setdefault('enable_dhcp', True) keys.setdefault('enable_dhcp', True)
with self.subnet(network=network, **keys) as subnet: with self.subnet(network=network, **keys) as subnet:
@ -4062,6 +4063,7 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
@testtools.skipIf(tools.is_bsd(), 'bug/1484837') @testtools.skipIf(tools.is_bsd(), 'bug/1484837')
def test_create_subnet_ipv6_pd_gw_values(self): def test_create_subnet_ipv6_pd_gw_values(self):
cidr = constants.PROVISIONAL_IPV6_PD_PREFIX cidr = constants.PROVISIONAL_IPV6_PD_PREFIX
subnetpool_id = constants.IPV6_PD_POOL_ID
# Gateway is last IP in IPv6 DHCPv6 Stateless subnet # Gateway is last IP in IPv6 DHCPv6 Stateless subnet
gateway = '::ffff:ffff:ffff:ffff' gateway = '::ffff:ffff:ffff:ffff'
allocation_pools = [{'start': '::1', allocation_pools = [{'start': '::1',
@ -4069,10 +4071,17 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
expected = {'gateway_ip': gateway, expected = {'gateway_ip': gateway,
'cidr': cidr, 'cidr': cidr,
'allocation_pools': allocation_pools} 'allocation_pools': allocation_pools}
# We do not specify a CIDR in the API call for a PD subnet, as it
# is unsupported. Instead we specify the subnetpool_id as
# "prefix_delegation" which is what happens via OSC's
# --use-prefix-delegation argument. But the expected result is a
# subnet object with the "::/64" PD prefix. Same comment applies below.
self._test_create_subnet(expected=expected, gateway_ip=gateway, self._test_create_subnet(expected=expected, gateway_ip=gateway,
cidr=cidr, ip_version=constants.IP_VERSION_6, cidr=constants.ATTR_NOT_SPECIFIED,
ip_version=constants.IP_VERSION_6,
ipv6_ra_mode=constants.DHCPV6_STATELESS, ipv6_ra_mode=constants.DHCPV6_STATELESS,
ipv6_address_mode=constants.DHCPV6_STATELESS) ipv6_address_mode=constants.DHCPV6_STATELESS,
subnetpool_id=subnetpool_id)
# Gateway is first IP in IPv6 DHCPv6 Stateless subnet # Gateway is first IP in IPv6 DHCPv6 Stateless subnet
gateway = '::1' gateway = '::1'
allocation_pools = [{'start': '::2', allocation_pools = [{'start': '::2',
@ -4081,18 +4090,22 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
'cidr': cidr, 'cidr': cidr,
'allocation_pools': allocation_pools} 'allocation_pools': allocation_pools}
self._test_create_subnet(expected=expected, gateway_ip=gateway, self._test_create_subnet(expected=expected, gateway_ip=gateway,
cidr=cidr, ip_version=constants.IP_VERSION_6, cidr=constants.ATTR_NOT_SPECIFIED,
ip_version=constants.IP_VERSION_6,
ipv6_ra_mode=constants.DHCPV6_STATELESS, ipv6_ra_mode=constants.DHCPV6_STATELESS,
ipv6_address_mode=constants.DHCPV6_STATELESS) ipv6_address_mode=constants.DHCPV6_STATELESS,
subnetpool_id=subnetpool_id)
# If gateway_ip is not specified and the subnet is using prefix # If gateway_ip is not specified and the subnet is using prefix
# delegation, until the CIDR is assigned, this value should be first # delegation, until the CIDR is assigned, this value should be first
# IP from the subnet # IP from the subnet
expected = {'gateway_ip': str(netaddr.IPNetwork(cidr).network), expected = {'gateway_ip': str(netaddr.IPNetwork(cidr).network),
'cidr': cidr} 'cidr': cidr}
self._test_create_subnet(expected=expected, self._test_create_subnet(expected=expected,
cidr=cidr, ip_version=constants.IP_VERSION_6, cidr=constants.ATTR_NOT_SPECIFIED,
ip_version=constants.IP_VERSION_6,
ipv6_ra_mode=constants.IPV6_SLAAC, ipv6_ra_mode=constants.IPV6_SLAAC,
ipv6_address_mode=constants.IPV6_SLAAC) ipv6_address_mode=constants.IPV6_SLAAC,
subnetpool_id=subnetpool_id)
def test_create_subnet_gw_outside_cidr_returns_201(self): def test_create_subnet_gw_outside_cidr_returns_201(self):
with self.network() as network: with self.network() as network:
@ -4189,14 +4202,21 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
allocation_pools=allocation_pools) allocation_pools=allocation_pools)
@testtools.skipIf(tools.is_bsd(), 'bug/1484837') @testtools.skipIf(tools.is_bsd(), 'bug/1484837')
def test_create_subnet_with_v6_pd_allocation_pool(self): def test_create_subnet_with_v6_pd_allocation_pool_returns_400(self):
gateway_ip = '::1' gateway_ip = '::1'
cidr = constants.PROVISIONAL_IPV6_PD_PREFIX cidr = constants.PROVISIONAL_IPV6_PD_PREFIX
allocation_pools = [{'start': '::2', allocation_pools = [{'start': '::2',
'end': '::ffff:ffff:ffff:fffe'}] 'end': '::ffff:ffff:ffff:fffe'}]
self._test_create_subnet(gateway_ip=gateway_ip, # Creating a subnet object with the "::/64" PD prefix is invalid
cidr=cidr, ip_version=constants.IP_VERSION_6, # unless the subnetpool_id is also passed as "prefix_delegation"
allocation_pools=allocation_pools) with testlib_api.ExpectedException(
webob.exc.HTTPClientError) as ctx_manager:
self._test_create_subnet(gateway_ip=gateway_ip,
cidr=cidr,
ip_version=constants.IP_VERSION_6,
allocation_pools=allocation_pools)
self.assertEqual(webob.exc.HTTPClientError.code,
ctx_manager.exception.code)
def test_create_subnet_with_large_allocation_pool(self): def test_create_subnet_with_large_allocation_pool(self):
gateway_ip = '10.0.0.1' gateway_ip = '10.0.0.1'
@ -4407,10 +4427,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
for mode, value in modes.items(): for mode, value in modes.items():
new_subnet[mode] = value new_subnet[mode] = value
if expect_success: if expect_success:
plugin._validate_subnet(ctx, new_subnet, cur_subnet) plugin._validate_subnet(ctx, new_subnet, cur_subnet, True)
else: else:
self.assertRaises(lib_exc.InvalidInput, plugin._validate_subnet, self.assertRaises(lib_exc.InvalidInput, plugin._validate_subnet,
ctx, new_subnet, cur_subnet) ctx, new_subnet, cur_subnet, True)
def test_create_subnet_ipv6_ra_modes(self): def test_create_subnet_ipv6_ra_modes(self):
# Test all RA modes with no address mode specified # Test all RA modes with no address mode specified

@ -375,6 +375,7 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
context = mock.Mock() context = mock.Mock()
pluggable_backend = ipam_pluggable_backend.IpamPluggableBackend() pluggable_backend = ipam_pluggable_backend.IpamPluggableBackend()
with self.subnet(cidr=constants.PROVISIONAL_IPV6_PD_PREFIX, with self.subnet(cidr=constants.PROVISIONAL_IPV6_PD_PREFIX,
subnetpool_id=constants.IPV6_PD_POOL_ID,
ip_version=constants.IP_VERSION_6) as subnet: ip_version=constants.IP_VERSION_6) as subnet:
subnet = subnet['subnet'] subnet = subnet['subnet']
fixed_ips = [{'subnet_id': subnet['id'], fixed_ips = [{'subnet_id': subnet['id'],