Serialize subnet creating depending on the network ID

Add a new DB table "network_subnet_lock". The primary key will be the
network_id. When a subnet is created, inside the write context during
the "subnet" object creation, a register in the mentioned table is
created or updated. This will enforce the serialization of the "subnet"
registers belonging to the same network, due to the write lock in the
DB.

This will solve the problem of attending several "subnet" creation
requests, described in the related bug. If several subnets with the
same CIDR are processed in parallel, the implemented logic won't reject
them because any of them will not contain the information of each other.

This DB lock will also work in case of distributed servers because the
lock is not enforced in the server logic but in the DB backend.

Change-Id: Iecbb096e0b7e080a3e0299ea340f8b03e87ddfd2
Closes-Bug: #1852777
This commit is contained in:
Rodolfo Alonso Hernandez 2019-11-21 09:55:45 +00:00
parent b4ccac6773
commit 397eb2a2fe
7 changed files with 128 additions and 1 deletions

View File

@ -596,6 +596,8 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
subnet['dns_nameservers'],
subnet['host_routes'],
subnet_request)
obj_subnet.NetworkSubnetLock.lock_subnet(context, network.id,
subnet.id)
except Exception:
# Note(pbondar): Third-party ipam servers can't rely
# on transaction rollback, so explicit rollback call needed.

View File

@ -1 +1 @@
f4b9654dd40c
a010322604bc

View File

@ -0,0 +1,40 @@
# Copyright 2019 OpenStack Foundation
#
# 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.
#
from alembic import op
import sqlalchemy as sa
"""network subnet update lock
Revision ID: a010322604bc
Revises: f4b9654dd40c
Create Date: 2019-11-20 18:05:00.812058
"""
# revision identifiers, used by Alembic.
revision = 'a010322604bc'
down_revision = 'f4b9654dd40c'
def upgrade():
op.create_table(
'network_subnet_lock',
sa.Column('network_id', sa.String(length=36),
sa.ForeignKey('networks.id', ondelete='CASCADE'),
primary_key=True),
sa.Column('subnet_id', sa.String(length=36))
)

View File

@ -277,3 +277,20 @@ class Network(standard_attr.HasStandardAttributes, model_base.BASEV2,
api_collections = [net_def.COLLECTION_NAME]
collection_resource_map = {net_def.COLLECTION_NAME: net_def.RESOURCE_NAME}
tag_support = True
class NetworkSubnetLock(model_base.BASEV2):
"""Auxiliary table to lock each network subnet updates.
This table is used to synchronize the subnet creation per network. If
several requests to create subnets on a network are processed at the same
time (even in different servers), this database lock will prevent the
creation of several subnets with overlapping CIDRs by updating the network
register in the table each time a subnet is created.
"""
__tablename__ = 'network_subnet_lock'
network_id = sa.Column(sa.String(36),
sa.ForeignKey('networks.id', ondelete='CASCADE'),
primary_key=True)
subnet_id = sa.Column(sa.String(36))

View File

@ -411,3 +411,30 @@ class Subnet(base.NeutronDbObject):
# subnets. Happens on routed networks when host isn't known.
raise ipam_exceptions.DeferIpam()
return False
@base.NeutronObjectRegistry.register
class NetworkSubnetLock(base.NeutronDbObject):
# Version 1.0: Initial version
VERSION = '1.0'
db_model = models_v2.NetworkSubnetLock
new_facade = True
primary_keys = ['network_id']
fields = {
'network_id': common_types.UUIDField(),
'subnet_id': common_types.UUIDField(nullable=True)
}
@classmethod
def lock_subnet(cls, context, network_id, subnet_id):
subnet_lock = super(NetworkSubnetLock, cls).get_object(
context, network_id=network_id)
if subnet_lock:
subnet_lock.subnet_id = subnet_id
subnet_lock.update()
else:
subnet_lock = NetworkSubnetLock(context, network_id=network_id,
subnet_id=subnet_id)
subnet_lock.create()

View File

@ -64,6 +64,7 @@ object_data = {
'NetworkRBAC': '1.2-192845c5ed0718e1c54fac36936fcd7d',
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
'NetworkSegmentRange': '1.0-bdec1fffc9058ea676089b1f2f2b3cf3',
'NetworkSubnetLock': '1.0-140de39d4b86ae346dc3d70b885bea53',
'Port': '1.5-98f35183d876c9beb188f4bf44d4d886',
'PortBinding': '1.0-3306deeaa6deb01e33af06777d48d578',
'PortBindingLevel': '1.1-50d47f63218f87581b6cd9a62db574e5',

View File

@ -266,3 +266,43 @@ class SubnetDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
obj1 = self._make_object(self.obj_fields[0])
self.assertEqual([service_type_obj.service_type],
obj1.service_types)
class NetworkSubnetLockTestCase(obj_test_base.BaseObjectIfaceTestCase):
_test_class = subnet.NetworkSubnetLock
class NetworkSubnetLockDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase):
_test_class = subnet.NetworkSubnetLock
def setUp(self):
super(NetworkSubnetLockDbObjectTestCase, self).setUp()
self.update_obj_fields(
{'network_id': lambda: self._create_test_network_id()})
def test_lock_subnet_update(self):
obj = self._make_object(self.obj_fields[0])
obj.create()
subnet_id = self._create_test_subnet_id(network_id=obj.network_id)
subnet.NetworkSubnetLock.lock_subnet(self.context, obj.network_id,
subnet_id)
obj = subnet.NetworkSubnetLock.get_object(self.context,
network_id=obj.network_id)
self.assertEqual(subnet_id, obj.subnet_id)
def test_lock_subnet_create(self):
network_id = self._create_test_network_id()
subnet_id = self._create_test_subnet_id(network_id=network_id)
obj = subnet.NetworkSubnetLock.get_object(self.context,
network_id=network_id)
self.assertIsNone(obj)
subnet.NetworkSubnetLock.lock_subnet(self.context, network_id,
subnet_id)
obj = subnet.NetworkSubnetLock.get_object(self.context,
network_id=network_id)
self.assertEqual(network_id, obj.network_id)
self.assertEqual(subnet_id, obj.subnet_id)