Skip to content

Commit 31decc6

Browse files
committed
Allow empty gateway IP in subnets from subnet pools
When a subnet is created from a subnet pool, now is possible to undefine the gateway IP. The new subnet created will have this value assigned to None. $ openstack subnet create --subnet-pool shared-default-subnetpool-v4 \ --network net14 snet14 --gateway None --format value \ --column gateway_ip None Closes-Bug: #2112453 Change-Id: I3bdd260f0f6b0259ff15cfe16a111bfe93b40749 (cherry picked from commit 5cccd21)
1 parent ecdb6ed commit 31decc6

File tree

4 files changed

+118
-12
lines changed

4 files changed

+118
-12
lines changed

neutron/ipam/requests.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class SubnetRequest(metaclass=abc.ABCMeta):
3636
instantiated on its own. Rather, a subclass of this class should be used.
3737
"""
3838
def __init__(self, tenant_id, subnet_id,
39-
gateway_ip=None, allocation_pools=None):
39+
gateway_ip=None, allocation_pools=None,
40+
set_gateway_ip=True):
4041
"""Initialize and validate
4142
4243
:param tenant_id: The tenant id who will own the subnet
@@ -50,10 +51,16 @@ def __init__(self, tenant_id, subnet_id,
5051
of this range if specifically requested.
5152
:type allocation_pools: A list of netaddr.IPRange. None if not
5253
specified.
54+
:param set_gateway_ip: in case the ``gateway_ip`` value is not defined
55+
(None), the IPAM module will set an IP address within the range of
56+
the subnet CIDR. If ``set_gateway_ip`` is unset, no IP address will
57+
be assigned.
58+
:type set_gateway_ip: boolean
5359
"""
5460
self._tenant_id = tenant_id
5561
self._subnet_id = subnet_id
5662
self._gateway_ip = None
63+
self._set_gateway_ip = set_gateway_ip
5764
self._allocation_pools = None
5865

5966
if gateway_ip is not None:
@@ -97,6 +104,10 @@ def subnet_id(self):
97104
def gateway_ip(self):
98105
return self._gateway_ip
99106

107+
@property
108+
def set_gateway_ip(self):
109+
return self._set_gateway_ip
110+
100111
@property
101112
def allocation_pools(self):
102113
return self._allocation_pools
@@ -144,7 +155,8 @@ class AnySubnetRequest(SubnetRequest):
144155
constants.IPv6: '::'}
145156

146157
def __init__(self, tenant_id, subnet_id, version, prefixlen,
147-
gateway_ip=None, allocation_pools=None):
158+
gateway_ip=None, allocation_pools=None,
159+
set_gateway_ip=True):
148160
"""Initialize AnySubnetRequest
149161
150162
:param version: Either constants.IPv4 or constants.IPv6
@@ -156,7 +168,9 @@ def __init__(self, tenant_id, subnet_id, version, prefixlen,
156168
tenant_id=tenant_id,
157169
subnet_id=subnet_id,
158170
gateway_ip=gateway_ip,
159-
allocation_pools=allocation_pools)
171+
allocation_pools=allocation_pools,
172+
set_gateway_ip=set_gateway_ip,
173+
)
160174

161175
net = netaddr.IPNetwork(self.WILDCARDS[version] + '/' + str(prefixlen))
162176
self._validate_with_subnet(net)
@@ -176,7 +190,8 @@ class SpecificSubnetRequest(SubnetRequest):
176190
blueprints.
177191
"""
178192
def __init__(self, tenant_id, subnet_id, subnet_cidr,
179-
gateway_ip=None, allocation_pools=None):
193+
gateway_ip=None, allocation_pools=None,
194+
set_gateway_ip=True):
180195
"""Initialize SpecificSubnetRequest
181196
182197
:param subnet: The subnet requested. Can be IPv4 or IPv6. However,
@@ -188,7 +203,9 @@ def __init__(self, tenant_id, subnet_id, subnet_cidr,
188203
tenant_id=tenant_id,
189204
subnet_id=subnet_id,
190205
gateway_ip=gateway_ip,
191-
allocation_pools=allocation_pools)
206+
allocation_pools=allocation_pools,
207+
set_gateway_ip=set_gateway_ip,
208+
)
192209

193210
self._subnet_cidr = netaddr.IPNetwork(subnet_cidr)
194211
self._validate_with_subnet(self._subnet_cidr)
@@ -322,6 +339,7 @@ def get_request(cls, context, subnet, subnetpool):
322339
cidr = subnet.get('cidr')
323340
cidr = cidr if validators.is_attr_set(cidr) else None
324341
gateway_ip = subnet.get('gateway_ip')
342+
set_gateway_ip = gateway_ip is not None
325343
gateway_ip = gateway_ip if validators.is_attr_set(gateway_ip) else None
326344
subnet_id = subnet.get('id', uuidutils.generate_uuid())
327345

@@ -335,7 +353,9 @@ def get_request(cls, context, subnet, subnetpool):
335353
subnet['tenant_id'],
336354
subnet_id,
337355
common_utils.ip_version_from_int(subnetpool['ip_version']),
338-
prefixlen)
356+
prefixlen,
357+
set_gateway_ip=set_gateway_ip,
358+
)
339359
alloc_pools = subnet.get('allocation_pools')
340360
alloc_pools = (
341361
alloc_pools if validators.is_attr_set(alloc_pools) else None)
@@ -352,4 +372,6 @@ def get_request(cls, context, subnet, subnetpool):
352372
subnet_id,
353373
cidr,
354374
gateway_ip=gateway_ip,
355-
allocation_pools=alloc_pools)
375+
allocation_pools=alloc_pools,
376+
set_gateway_ip=set_gateway_ip,
377+
)

neutron/ipam/subnet_alloc.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def _allocate_any_subnet(self, request):
129129
if request.prefixlen >= prefix.prefixlen:
130130
subnet = next(prefix.subnet(request.prefixlen))
131131
gateway_ip = request.gateway_ip
132-
if not gateway_ip:
132+
if not gateway_ip and request.set_gateway_ip:
133133
gateway_ip = subnet.network + 1
134134
pools = ipam_utils.generate_pools(subnet.cidr,
135135
gateway_ip)
@@ -138,7 +138,9 @@ def _allocate_any_subnet(self, request):
138138
request.subnet_id,
139139
subnet.cidr,
140140
gateway_ip=gateway_ip,
141-
allocation_pools=pools)
141+
allocation_pools=pools,
142+
set_gateway_ip=request.set_gateway_ip,
143+
)
142144
msg = _("Insufficient prefix space to allocate subnet size /%s")
143145
raise exceptions.SubnetAllocationError(
144146
reason=msg % str(request.prefixlen))
@@ -156,7 +158,9 @@ def _allocate_specific_subnet(self, request):
156158
request.subnet_id,
157159
cidr,
158160
gateway_ip=request.gateway_ip,
159-
allocation_pools=request.allocation_pools)
161+
allocation_pools=request.allocation_pools,
162+
set_gateway_ip=request.set_gateway_ip,
163+
)
160164
msg = _("Cannot allocate requested subnet from the available "
161165
"set of prefixes")
162166
raise exceptions.SubnetAllocationError(reason=msg)
@@ -200,13 +204,17 @@ def __init__(self,
200204
subnet_id,
201205
cidr,
202206
gateway_ip=None,
203-
allocation_pools=None):
207+
allocation_pools=None,
208+
set_gateway_ip=True,
209+
):
204210
self._req = ipam_req.SpecificSubnetRequest(
205211
tenant_id,
206212
subnet_id,
207213
cidr,
208214
gateway_ip=gateway_ip,
209-
allocation_pools=allocation_pools)
215+
allocation_pools=allocation_pools,
216+
set_gateway_ip=set_gateway_ip,
217+
)
210218

211219
def allocate(self, address_request):
212220
raise NotImplementedError()

neutron/tests/common/test_db_base_plugin_v2.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6455,6 +6455,28 @@ def test_allocate_any_subnet_with_default_prefixlen(self):
64556455
self.assertEqual(subnet.prefixlen,
64566456
int(sp['subnetpool']['default_prefixlen']))
64576457

6458+
def test_allocate_any_subnet_with_default_prefixlen_no_gateway_ip(self):
6459+
with self.network() as network:
6460+
sp = self._test_create_subnetpool(['10.10.0.0/16'],
6461+
tenant_id=self._tenant_id,
6462+
name=self._POOL_NAME,
6463+
min_prefixlen='21')
6464+
6465+
# Request any subnet allocation using default prefix
6466+
data = {'subnet': {'network_id': network['network']['id'],
6467+
'subnetpool_id': sp['subnetpool']['id'],
6468+
'ip_version': constants.IP_VERSION_4,
6469+
'tenant_id': network['network']['tenant_id'],
6470+
'gateway_ip': None,
6471+
}}
6472+
req = self.new_create_request('subnets', data)
6473+
res = self.deserialize(self.fmt, req.get_response(self.api))
6474+
6475+
subnet = netaddr.IPNetwork(res['subnet']['cidr'])
6476+
self.assertEqual(subnet.prefixlen,
6477+
int(sp['subnetpool']['default_prefixlen']))
6478+
self.assertIsNone(res['subnet']['gateway_ip'])
6479+
64586480
def test_allocate_specific_subnet_with_mismatch_prefixlen(self):
64596481
with self.network() as network:
64606482
sp = self._test_create_subnetpool(['10.10.0.0/16'],
@@ -6645,6 +6667,29 @@ def test_allocate_specific_subnet_prefix_too_large(self):
66456667
res = req.get_response(self.api)
66466668
self._check_http_response(res, 400)
66476669

6670+
def test_allocate_specific_subnet_no_gateway_ip(self):
6671+
with self.network() as network:
6672+
sp = self._test_create_subnetpool(['10.10.0.0/16'],
6673+
tenant_id=self._tenant_id,
6674+
name=self._POOL_NAME,
6675+
min_prefixlen='21')
6676+
6677+
# Request a specific subnet allocation
6678+
data = {'subnet': {'network_id': network['network']['id'],
6679+
'subnetpool_id': sp['subnetpool']['id'],
6680+
'cidr': '10.10.1.0/24',
6681+
'ip_version': constants.IP_VERSION_4,
6682+
'tenant_id': network['network']['tenant_id'],
6683+
'gateway_ip': None,
6684+
}}
6685+
req = self.new_create_request('subnets', data)
6686+
res = self.deserialize(self.fmt, req.get_response(self.api))
6687+
6688+
# Assert the allocated subnet CIDR is what we expect
6689+
subnet = netaddr.IPNetwork(res['subnet']['cidr'])
6690+
self.assertEqual(netaddr.IPNetwork('10.10.1.0/24'), subnet)
6691+
self.assertIsNone(res['subnet']['gateway_ip'])
6692+
66486693
def test_delete_subnetpool_existing_allocations(self):
66496694
with self.network() as network:
66506695
sp = self._test_create_subnetpool(['10.10.0.0/16'],

neutron/tests/unit/ipam/test_subnet_alloc.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,34 @@ def test_subnetpool_concurrent_allocation_exception(self):
197197
'fe80::/63')
198198
with mock.patch("sqlalchemy.orm.query.Query.update", return_value=0):
199199
self.assertRaises(db_exc.RetryRequest, sa.allocate_subnet, req)
200+
201+
def test_subnetpool_any_request_no_gateway_ip_set(self):
202+
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
203+
['10.1.0.0/16', '192.168.1.0/24'],
204+
21, 4)
205+
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
206+
with db_api.CONTEXT_WRITER.using(self.ctx):
207+
sa = subnet_alloc.SubnetAllocator(sp, self.ctx)
208+
req = ipam_req.AnySubnetRequest(self._tenant_id,
209+
uuidutils.generate_uuid(),
210+
constants.IPv4, 21,
211+
set_gateway_ip=False)
212+
res = sa.allocate_subnet(req)
213+
detail = res.get_details()
214+
self.assertIsNone(detail.gateway_ip)
215+
self.assertFalse(detail.set_gateway_ip)
216+
217+
def test_subnetpool_specific_request_no_gateway_ip_set(self):
218+
sp = self._create_subnet_pool(self.plugin, self.ctx, 'test-sp',
219+
['10.1.0.0/16', '192.168.1.0/24'],
220+
21, 4)
221+
sp = self.plugin._get_subnetpool(self.ctx, sp['id'])
222+
with db_api.CONTEXT_WRITER.using(self.ctx):
223+
sa = subnet_alloc.SubnetAllocator(sp, self.ctx)
224+
req = ipam_req.SpecificSubnetRequest(
225+
self._tenant_id, uuidutils.generate_uuid(),
226+
'10.1.2.0/27', set_gateway_ip=False)
227+
res = sa.allocate_subnet(req)
228+
detail = res.get_details()
229+
self.assertIsNone(detail.gateway_ip)
230+
self.assertFalse(detail.set_gateway_ip)

0 commit comments

Comments
 (0)