Merge "Using 31-Bit and 32-Bit prefixes for IPv4 reasonably"

This commit is contained in:
Zuul 2021-06-10 10:48:17 +00:00 committed by Gerrit Code Review
commit 1e9af8384b
7 changed files with 101 additions and 21 deletions

View File

@ -310,9 +310,17 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
Finally, verify that each range fall within the subnet's CIDR.
"""
subnet = netaddr.IPNetwork(subnet_cidr)
subnet_first_ip = netaddr.IPAddress(subnet.first + 1)
# last address is broadcast in v4
subnet_last_ip = netaddr.IPAddress(subnet.last - (subnet.version == 4))
if subnet.version == const.IP_VERSION_4:
if subnet.prefixlen <= 30:
subnet_first_ip = netaddr.IPAddress(subnet.first + 1)
# last address is broadcast in v4
subnet_last_ip = netaddr.IPAddress(subnet.last - 1)
else:
subnet_first_ip = netaddr.IPAddress(subnet.first)
subnet_last_ip = netaddr.IPAddress(subnet.last)
else: # IPv6 case
subnet_first_ip = netaddr.IPAddress(subnet.first + 1)
subnet_last_ip = netaddr.IPAddress(subnet.last)
LOG.debug("Performing IP validity checks on allocation pools")
ip_sets = []

View File

@ -62,12 +62,17 @@ def generate_pools(cidr, gateway_ip):
if first == last:
# handle single address subnet case
return [netaddr.IPRange(first, last)]
first_ip = first + 1
# last address is broadcast in v4
last_ip = last - (ip_version == 4)
if first_ip >= last_ip:
# /31 lands here
return []
if ip_version == constants.IP_VERSION_4:
if net.prefixlen <= 30:
first_ip = first + 1
# last address is broadcast in v4
last_ip = last - (ip_version == 4)
else:
first_ip = first
last_ip = last
else: # IPv6 case
first_ip = first + 1
last_ip = last
ipset = netaddr.IPSet(netaddr.IPRange(first_ip, last_ip))
if gateway_ip:
ipset.remove(netaddr.IPAddress(gateway_ip, ip_version))

View File

@ -129,7 +129,10 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
def _configure_static_ipaddress(self):
self.port.addr.add(self.ip_cidr)
if self.gateway_ip:
net_helpers.set_namespace_gateway(self.port, self.gateway_ip)
net = netaddr.IPNetwork(self.ip_cidr)
gateway_ip = netaddr.IPAddress(self.gateway_ip)
if gateway_ip in net:
net_helpers.set_namespace_gateway(self.port, self.gateway_ip)
def _configure_ipaddress_via_dhcp(self):
self._start_async_dhclient()

View File

@ -20,6 +20,7 @@ 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
from neutron.tests.fullstack.resources import machine
class TestSubnet(base.BaseFullStackTestCase):
@ -132,3 +133,32 @@ class TestSubnet(base.BaseFullStackTestCase):
subnet = self._show_subnet(subnet['id'])
self.assertEqual(subnet['subnet']['cidr'], str(subnets[2].cidr))
self.assertEqual(subnet['subnet']['gateway_ip'], str(gateway_ip))
def test_subnet_with_prefixlen_31_connectivity(self):
network = self._create_network(self._project_id)
self.safe_client.create_subnet(
self._project_id, network['id'],
cidr='10.14.0.20/31',
gateway_ip='10.14.0.19',
name='subnet-test',
enable_dhcp=False)
vms = self._prepare_vms_in_net(self._project_id, network, False)
vms.ping_all()
def test_subnet_with_prefixlen_32_vm_spawn(self):
network = self._create_network(self._project_id)
self.safe_client.create_subnet(
self._project_id, network['id'],
cidr='10.14.0.20/32',
gateway_ip='10.14.0.19',
name='subnet-test',
enable_dhcp=False)
vm = self.useFixture(
machine.FakeFullstackMachine(
self.environment.hosts[0],
network['id'],
self._project_id,
self.safe_client))
vm.block_until_boot()

View File

@ -1693,16 +1693,6 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
self.assertEqual(data['port']['fixed_ips'],
res['port']['fixed_ips'])
def test_no_more_port_exception(self):
with self.subnet(cidr='10.0.0.0/31', enable_dhcp=False,
gateway_ip=None) as subnet:
id = subnet['subnet']['network_id']
res = self._create_port(self.fmt, id)
data = self.deserialize(self.fmt, res)
msg = str(lib_exc.IpAddressGenerationFailure(net_id=id))
self.assertEqual(data['NeutronError']['message'], msg)
self.assertEqual(webob.exc.HTTPConflict.code, res.status_int)
def test_create_ports_native_quotas(self):
quota = 1
cfg.CONF.set_override('quota_port', quota, group='QUOTAS')
@ -5091,6 +5081,43 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPConflict.code, res.status_int)
def test_create_subnet_allocation_pools_with_prefixlen_31(self):
with self.network() as network:
with self.subnet(network=network,
enable_dhcp=False,
gateway_ip='10.14.0.19',
cidr='10.14.0.20/31'):
res = self._create_port(self.fmt, network['network']['id'])
self.assertEqual(webob.exc.HTTPCreated.code, res.status_int)
res = self._create_port(self.fmt, network['network']['id'])
self.assertEqual(webob.exc.HTTPCreated.code, res.status_int)
def test_update_subnet_allocation_pools_with_prefixlen_31(self):
with self.network() as network:
with self.subnet(network=network,
enable_dhcp=False,
gateway_ip='10.14.0.19',
cidr='10.14.0.20/31') as subnet:
data = {'subnet': {'allocation_pools': [
{'start': '10.14.0.20', 'end': '10.14.0.21'}]}}
req = self.new_update_request('subnets', data,
subnet['subnet']['id'])
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPOk.code, res.status_int)
res = self._create_port(self.fmt, network['network']['id'])
self.assertEqual(webob.exc.HTTPCreated.code, res.status_int)
res = self._create_port(self.fmt, network['network']['id'])
self.assertEqual(webob.exc.HTTPCreated.code, res.status_int)
def test_create_subnet_allocation_pools_with_prefixlen_32(self):
with self.network() as network:
with self.subnet(network=network,
enable_dhcp=False,
gateway_ip='10.14.0.19',
cidr='10.14.0.20/32'):
res = self._create_port(self.fmt, network['network']['id'])
self.assertEqual(webob.exc.HTTPCreated.code, res.status_int)
def test_create_subnets_native_quotas(self):
quota = 1
cfg.CONF.set_override('quota_subnet', quota, group='QUOTAS')

View File

@ -89,7 +89,7 @@ class TestIpamUtils(base.BaseTestCase):
def test_generate_pools_v4_31(self):
cidr = '192.168.0.0/31'
expected = []
expected = [netaddr.IPRange('192.168.0.0', '192.168.0.1')]
self.assertEqual(expected, utils.generate_pools(cidr, None))
def test_generate_pools_v4_gateway_middle(self):

View File

@ -0,0 +1,7 @@
---
features:
- |
Neutron supports creating IPv4 subnet with prefixlen /31 and /32,
via disabling dhcp on a subnet.
For more information, see bug
`1580927 <https://bugs.launchpad.net/neutron/+bug/1580927>`_.