diff --git a/neutron/db/ipam_backend_mixin.py b/neutron/db/ipam_backend_mixin.py index 15656d1880c..177a5252a3f 100644 --- a/neutron/db/ipam_backend_mixin.py +++ b/neutron/db/ipam_backend_mixin.py @@ -56,10 +56,14 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): @staticmethod def _gateway_ip_str(subnet, cidr_net): if subnet.get('gateway_ip') is const.ATTR_NOT_SPECIFIED: - if subnet.get('version') == const.IP_VERSION_6: - return str(netaddr.IPNetwork(cidr_net).network) + if subnet.get('ip_version') == const.IP_VERSION_6: + gateway_ip = netaddr.IPNetwork(cidr_net).network + pd_net = netaddr.IPNetwork(const.PROVISIONAL_IPV6_PD_PREFIX) + if gateway_ip == pd_net.network: + return else: - return str(netaddr.IPNetwork(cidr_net).network + 1) + gateway_ip = netaddr.IPNetwork(cidr_net).network + 1 + return str(gateway_ip) return subnet.get('gateway_ip') @staticmethod diff --git a/neutron/ipam/utils.py b/neutron/ipam/utils.py index 968e8f64b6d..3c20c2f2102 100644 --- a/neutron/ipam/utils.py +++ b/neutron/ipam/utils.py @@ -17,7 +17,7 @@ import netaddr from neutron_lib import constants -def check_subnet_ip(cidr, ip_address, port_owner=None): +def check_subnet_ip(cidr, ip_address, port_owner=''): """Validate that the IP address is on the subnet.""" ip = netaddr.IPAddress(ip_address) net = netaddr.IPNetwork(cidr) @@ -25,16 +25,12 @@ def check_subnet_ip(cidr, ip_address, port_owner=None): # network or the broadcast address if net.version == constants.IP_VERSION_6: # NOTE(njohnston): In some cases the code cannot know the owner of the - # port. In these cases port_owner should be None, and we pass it - # through here. - return ((port_owner in constants.ROUTER_PORT_OWNERS or - port_owner is None or - ip != net.network) and - net.netmask & ip == net.network) + # port. In these cases port_owner should an empty string, and we pass + # it through here. + return (port_owner in (constants.ROUTER_PORT_OWNERS + ('', )) and + ip in net) else: - return (ip != net.network and - ip != net[-1] and - net.netmask & ip == net.network) + return ip != net.network and ip != net.broadcast and ip in net def check_gateway_invalid_in_subnet(cidr, gateway): diff --git a/neutron/tests/fullstack/resources/client.py b/neutron/tests/fullstack/resources/client.py index 5ca657a552a..686d29031f0 100644 --- a/neutron/tests/fullstack/resources/client.py +++ b/neutron/tests/fullstack/resources/client.py @@ -107,20 +107,25 @@ class ClientFixture(fixtures.Fixture): def create_subnet(self, tenant_id, network_id, cidr, gateway_ip=None, name=None, enable_dhcp=True, - ipv6_address_mode='slaac', ipv6_ra_mode='slaac'): + ipv6_address_mode='slaac', ipv6_ra_mode='slaac', + subnetpool_id=None, ip_version=None): resource_type = 'subnet' name = name or utils.get_rand_name(prefix=resource_type) - ip_version = netaddr.IPNetwork(cidr).version + if cidr and not ip_version: + ip_version = netaddr.IPNetwork(cidr).version spec = {'tenant_id': tenant_id, 'network_id': network_id, 'name': name, - 'cidr': cidr, 'enable_dhcp': enable_dhcp, - 'ip_version': ip_version} + 'enable_dhcp': enable_dhcp, 'ip_version': ip_version} if ip_version == constants.IP_VERSION_6: spec['ipv6_address_mode'] = ipv6_address_mode spec['ipv6_ra_mode'] = ipv6_ra_mode if gateway_ip: spec['gateway_ip'] = gateway_ip + if subnetpool_id: + spec['subnetpool_id'] = subnetpool_id + if cidr: + spec['cidr'] = cidr return self._create_resource(resource_type, spec) diff --git a/neutron/tests/fullstack/test_subnet.py b/neutron/tests/fullstack/test_subnet.py new file mode 100644 index 00000000000..74bcb78cbbd --- /dev/null +++ b/neutron/tests/fullstack/test_subnet.py @@ -0,0 +1,82 @@ +# Copyright 2019 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import netaddr +from neutron_lib import constants +from oslo_utils import uuidutils + +from neutron.tests.common.exclusive_resources import ip_network +from neutron.tests.fullstack import base +from neutron.tests.fullstack.resources import environment + + +class TestSubnet(base.BaseFullStackTestCase): + + def setUp(self): + host_descriptions = [ + environment.HostDescription(l3_agent=True, dhcp_agent=True), + environment.HostDescription()] + env = environment.Environment( + environment.EnvironmentDescription(network_type='vlan', + l2_pop=False), + host_descriptions) + super(TestSubnet, self).setUp(env) + self._project_id = uuidutils.generate_uuid() + self._network = self._create_network(self._project_id) + + def _create_network(self, project_id, name='test_network'): + return self.safe_client.create_network(project_id, name=name) + + def _create_subnet(self, project_id, network_id, cidr, + ipv6_address_mode=None, ipv6_ra_mode=None, + subnetpool_id=None): + ip_version = None + if ipv6_address_mode or ipv6_ra_mode: + ip_version = constants.IP_VERSION_6 + return self.safe_client.create_subnet( + project_id, network_id, cidr, enable_dhcp=True, + ipv6_address_mode=ipv6_address_mode, ipv6_ra_mode=ipv6_ra_mode, + subnetpool_id=subnetpool_id, ip_version=ip_version) + + def _show_subnet(self, subnet_id): + return self.client.show_subnet(subnet_id) + + def test_create_subnet_ipv4(self): + cidr = self.useFixture( + ip_network.ExclusiveIPNetwork( + '240.0.0.0', '240.255.255.255', '24')).network + subnet = self._create_subnet(self._project_id, self._network['id'], + cidr) + subnet = self._show_subnet(subnet['id']) + self.assertEqual(subnet['subnet']['gateway_ip'], + str(netaddr.IPNetwork(cidr).network + 1)) + + def test_create_subnet_ipv6_slaac(self): + cidr = self.useFixture( + ip_network.ExclusiveIPNetwork( + '2001:db8::', '2001:db8::ffff', '64')).network + subnet = self._create_subnet(self._project_id, self._network['id'], + cidr, ipv6_address_mode='slaac', + ipv6_ra_mode='slaac') + subnet = self._show_subnet(subnet['id']) + self.assertEqual(subnet['subnet']['gateway_ip'], + str(netaddr.IPNetwork(cidr).network)) + + def test_create_subnet_ipv6_prefix_delegation(self): + subnet = self._create_subnet(self._project_id, self._network['id'], + None, ipv6_address_mode='slaac', + ipv6_ra_mode='slaac', + subnetpool_id='prefix_delegation') + subnet = self._show_subnet(subnet['id']) + self.assertIsNone(subnet['subnet']['gateway_ip']) 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 ea3cdd288a6..ca5aeca5ceb 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -3934,7 +3934,7 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): ipv6_ra_mode=constants.DHCPV6_STATEFUL, ipv6_address_mode=constants.DHCPV6_STATEFUL) # If gateway_ip is not specified, allocate first IP from the subnet - expected = {'gateway_ip': gateway, + expected = {'gateway_ip': str(netaddr.IPNetwork(cidr).network), 'cidr': cidr} self._test_create_subnet(expected=expected, cidr=cidr, ip_version=constants.IP_VERSION_6, @@ -3966,8 +3966,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): cidr=cidr, ip_version=constants.IP_VERSION_6, ipv6_ra_mode=constants.DHCPV6_STATELESS, ipv6_address_mode=constants.DHCPV6_STATELESS) - # If gateway_ip is not specified, allocate first IP from the subnet - expected = {'gateway_ip': gateway, + # If gateway_ip is not specified and the subnet is using prefix + # delegation, until the CIDR is assigned, this value should be "None" + expected = {'gateway_ip': None, 'cidr': cidr} self._test_create_subnet(expected=expected, cidr=cidr, ip_version=constants.IP_VERSION_6, diff --git a/neutron/tests/unit/db/test_ipam_pluggable_backend.py b/neutron/tests/unit/db/test_ipam_pluggable_backend.py index c013176e471..3ec4677dc52 100644 --- a/neutron/tests/unit/db/test_ipam_pluggable_backend.py +++ b/neutron/tests/unit/db/test_ipam_pluggable_backend.py @@ -406,7 +406,9 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase): mocks = self._prepare_mocks_with_pool_mock(pool_mock) cfg.CONF.set_override('ipv6_pd_enabled', True) cidr = constants.PROVISIONAL_IPV6_PD_PREFIX - allocation_pools = [netaddr.IPRange('::2', '::ffff:ffff:ffff:ffff')] + cidr_network = netaddr.IPNetwork(cidr) + allocation_pools = [netaddr.IPRange(cidr_network.ip + 1, + cidr_network.last)] with self.subnet(cidr=None, ip_version=constants.IP_VERSION_6, subnetpool_id=constants.IPV6_PD_POOL_ID, ipv6_ra_mode=constants.IPV6_SLAAC, diff --git a/neutron/tests/unit/extensions/test_network_ip_availability.py b/neutron/tests/unit/extensions/test_network_ip_availability.py index cb6e3a003de..231fbf46064 100644 --- a/neutron/tests/unit/extensions/test_network_ip_availability.py +++ b/neutron/tests/unit/extensions/test_network_ip_availability.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import netaddr from neutron_lib import constants import neutron.api.extensions as api_ext @@ -215,9 +216,11 @@ class TestNetworkIPAvailabilityAPI( self.assertEqual(0, len(response[IP_AVAILS_KEY])) def test_usages_query_ip_version_v6(self): + cidr_ipv6 = '2001:db8:1002:51::/64' + cidr_ipv6_net = netaddr.IPNetwork(cidr_ipv6) with self.network() as net: with self.subnet( - network=net, cidr='2607:f0d0:1002:51::/64', + network=net, cidr=cidr_ipv6, ip_version=constants.IP_VERSION_6, ipv6_address_mode=constants.DHCPV6_STATELESS): # Get IPv6 @@ -227,7 +230,7 @@ class TestNetworkIPAvailabilityAPI( request.get_response(self.ext_api)) self.assertEqual(1, len(response[IP_AVAILS_KEY])) self._validate_from_availabilities( - response[IP_AVAILS_KEY], net, 0, 18446744073709551614) + response[IP_AVAILS_KEY], net, 0, cidr_ipv6_net.size - 1) # Get IPv4 should return empty array params = 'ip_version=%s' % constants.IP_VERSION_4 @@ -237,9 +240,11 @@ class TestNetworkIPAvailabilityAPI( self.assertEqual(0, len(response[IP_AVAILS_KEY])) def test_usages_ports_consumed_v6(self): + cidr_ipv6 = '2001:db8:1002:51::/64' + cidr_ipv6_net = netaddr.IPNetwork(cidr_ipv6) with self.network() as net: with self.subnet( - network=net, cidr='2607:f0d0:1002:51::/64', + network=net, cidr=cidr_ipv6, ip_version=constants.IP_VERSION_6, ipv6_address_mode=constants.DHCPV6_STATELESS) as subnet: request = self.new_list_request(API_RESOURCE) @@ -252,7 +257,7 @@ class TestNetworkIPAvailabilityAPI( self._validate_from_availabilities(response[IP_AVAILS_KEY], net, 3, - 18446744073709551614) + cidr_ipv6_net.size - 1) def test_usages_query_network_id(self): with self.network() as net: @@ -345,14 +350,16 @@ class TestNetworkIPAvailabilityAPI( def test_usages_multi_net_multi_subnet_46(self): # Setup mixed v4/v6 networks with IPs consumed on each + cidr_ipv6 = '2001:db8:1003:52::/64' + cidr_ipv6_net = netaddr.IPNetwork(cidr_ipv6) with self.network(name='net-v6-1') as net_v6_1, \ self.network(name='net-v6-2') as net_v6_2, \ self.network(name='net-v4-1') as net_v4_1, \ self.network(name='net-v4-2') as net_v4_2: - with self.subnet(network=net_v6_1, cidr='2607:f0d0:1002:51::/64', + with self.subnet(network=net_v6_1, cidr='2001:db8:1002:51::/64', ip_version=constants.IP_VERSION_6) as s61, \ self.subnet(network=net_v6_2, - cidr='2607:f0d0:1003:52::/64', + cidr=cidr_ipv6, ip_version=constants.IP_VERSION_6) as s62, \ self.subnet(network=net_v4_1, cidr='10.0.0.0/24') as s41, \ self.subnet(network=net_v4_2, cidr='10.0.1.0/24') as s42: @@ -367,9 +374,9 @@ class TestNetworkIPAvailabilityAPI( self.fmt, request.get_response(self.ext_api)) avails_list = response[IP_AVAILS_KEY] self._validate_from_availabilities( - avails_list, net_v6_1, 1, 18446744073709551614) + avails_list, net_v6_1, 1, cidr_ipv6_net.size - 1) self._validate_from_availabilities( - avails_list, net_v6_2, 2, 18446744073709551614) + avails_list, net_v6_2, 2, cidr_ipv6_net.size - 1) self._validate_from_availabilities( avails_list, net_v4_1, 1, 253) self._validate_from_availabilities( @@ -397,6 +404,6 @@ class TestNetworkIPAvailabilityAPI( self.fmt, request.get_response(self.ext_api)) avails_list = response[IP_AVAILS_KEY] self._validate_from_availabilities( - avails_list, net_v6_2, 2, 18446744073709551614) + avails_list, net_v6_2, 2, cidr_ipv6_net.size - 1) self._validate_from_availabilities( avails_list, net_v4_2, 2, 253) diff --git a/neutron/tests/unit/services/ovn_l3/test_plugin.py b/neutron/tests/unit/services/ovn_l3/test_plugin.py index 682aed77818..daa402b2c76 100644 --- a/neutron/tests/unit/services/ovn_l3/test_plugin.py +++ b/neutron/tests/unit/services/ovn_l3/test_plugin.py @@ -1452,6 +1452,6 @@ class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase, test_router_update_gateway_upon_subnet_create_max_ips_ipv6() add_static_route_calls = [ mock.call(mock.ANY, ip_prefix='0.0.0.0/0', nexthop='10.0.0.1'), - mock.call(mock.ANY, ip_prefix='::/0', nexthop='2001:db8::1')] + mock.call(mock.ANY, ip_prefix='::/0', nexthop='2001:db8::')] self.l3_inst._ovn.add_static_route.assert_has_calls( add_static_route_calls, any_order=True)