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:
parent
b4ccac6773
commit
397eb2a2fe
|
@ -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.
|
||||
|
|
|
@ -1 +1 @@
|
|||
f4b9654dd40c
|
||||
a010322604bc
|
||||
|
|
|
@ -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))
|
||||
)
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue