Don't fail subnet validation if gw_ip is actually not changed

In subnet update API call Neutron checks if gateway_ip was send to be
updated and if so, it checkes if old gateway_ip isn't already allocated
to some router port. If it's already used, Neutron returns 409 response.
This is valid behaviour but sometimes, some automation tools may do
subnet update request and pass the same gateway ip as already used by
the subnet. In such case, as gateway_ip is actually not changed Neutron
should not raise exception in that validation.

Closes-Bug: #1955121
Change-Id: Iba90b44331fdc63273fd3d19c583a24b5295c0ac
(cherry picked from commit 6809bed632)
(cherry picked from commit 7ab37e92b8)
This commit is contained in:
Slawek Kaplonski 2021-12-17 12:34:07 +01:00
parent b84630ad0f
commit faee38f328
2 changed files with 71 additions and 4 deletions

View File

@ -626,17 +626,20 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# NOTE(salv-orlando): There is slight chance of a race, when
# a subnet-update and a router-interface-add operation are
# executed concurrently
if cur_subnet and not ipv6_utils.is_ipv6_pd_enabled(s):
s_gateway_ip = netaddr.IPAddress(s['gateway_ip'])
if (cur_subnet and
s_gateway_ip != cur_subnet['gateway_ip'] and
not ipv6_utils.is_ipv6_pd_enabled(s)):
gateway_ip = str(cur_subnet['gateway_ip'])
with db_api.CONTEXT_READER.using(context):
allocated = port_obj.IPAllocation.get_alloc_routerports(
alloc = port_obj.IPAllocation.get_alloc_routerports(
context, cur_subnet['id'], gateway_ip=gateway_ip,
first=True)
if allocated and allocated.port_id:
if alloc and alloc.port_id:
raise exc.GatewayIpInUse(
ip_address=gateway_ip,
port_id=allocated.port_id)
port_id=alloc.port_id)
if validators.is_attr_set(s.get('dns_nameservers')):
if len(s['dns_nameservers']) > cfg.CONF.max_dns_nameservers:

View File

@ -4821,6 +4821,70 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
res = req.get_response(self.api)
self.assertEqual(res.status_int, 200)
def test_update_subnet_the_same_gw_as_in_use_by_router(self):
with self.network() as network:
with self.subnet(network=network,
allocation_pools=[{'start': '10.0.0.2',
'end': '10.0.0.8'}]) as subnet:
s = subnet['subnet']
with self.port(
subnet=subnet, fixed_ips=[{'subnet_id': s['id'],
'ip_address': s['gateway_ip']}]
) as port:
# this protection only applies to router ports so we need
# to make this port belong to a router
ctx = context.get_admin_context()
with db_api.CONTEXT_WRITER.using(ctx):
router = l3_models.Router()
ctx.session.add(router)
rp = l3_obj.RouterPort(ctx, router_id=router.id,
port_id=port['port']['id'])
rp.create()
# update subnet will be with the same gateway_ip as was
# used before, thus it should be fine
data = {'subnet': {
'gateway_ip': s['gateway_ip'],
'description': 'test update subnet'}}
req = self.new_update_request('subnets', data,
s['id'])
res = req.get_response(self.api)
self.assertEqual(200, res.status_int)
def test_update_subnet_the_same_gw_as_in_use_by_router_ipv6(self):
with self.network() as network:
with self.subnet(network=network,
ip_version=constants.IP_VERSION_6,
cidr="fe80::/48") as subnet:
s = subnet['subnet']
with self.port(
subnet=subnet, fixed_ips=[{'subnet_id': s['id'],
'ip_address': s['gateway_ip']}]
) as port:
# this protection only applies to router ports so we need
# to make this port belong to a router
ctx = context.get_admin_context()
with db_api.CONTEXT_WRITER.using(ctx):
router = l3_models.Router()
ctx.session.add(router)
rp = l3_obj.RouterPort(ctx, router_id=router.id,
port_id=port['port']['id'])
rp.create()
# It's the same IP address but with all zeros now so string
# is different
new_gw_ip = netaddr.IPAddress(s['gateway_ip']).format(
dialect=netaddr.ipv6_verbose)
# update subnet will be with the same gateway_ip as was
# used before, thus it should be fine
data = {'subnet': {
'gateway_ip': new_gw_ip,
'description': 'test update subnet'}}
req = self.new_update_request('subnets', data,
s['id'])
res = req.get_response(self.api)
self.assertEqual(200, res.status_int)
def test_update_subnet_invalid_gw_V4_cidr(self):
with self.network() as network:
with self.subnet(network=network) as subnet: