Skip to content

Commit

Permalink
[config] Synchronously allocate virtual network ID
Browse files Browse the repository at this point in the history
To improve config node scalability, that patch move the virtual network
ID allocation from the schema to the vnc API server.
To prevent any issue during the upgrade from version where vni is
allocated by the schema to the version where it's allocated by the vnc
API, that patch also sets the vni if it not sets when the vnc API
received a virtual network create notification. That update will need to
be removed for the future release 3.2.

Change-Id: I53098c36315169035e1a9c2b8474b777eb7f362a
Closes-Bug: #1601919
  • Loading branch information
Édouard Thuleau committed Jul 12, 2016
1 parent 306e1cb commit 9df6229
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 31 deletions.
70 changes: 70 additions & 0 deletions src/config/api-server/tests/test_crud_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,76 @@ def test_resource_list_with_resource_type_underscored(self):
resources['%ss' % test_obj.get_type()]]
self.assertEqual([test_obj.uuid], resource_ids)

def test_allocate_vn_id(self):
mock_zk = self._api_server._db_conn._zk_db
vn_obj = VirtualNetwork('%s-vn' % self.id())

self._vnc_lib.virtual_network_create(vn_obj)

vn_obj = self._vnc_lib.virtual_network_read(id=vn_obj.uuid)
vn_id = vn_obj.virtual_network_network_id
self.assertEqual(vn_obj.get_fq_name_str(),
mock_zk.get_vn_from_id(vn_id))

def test_deallocate_vn_id(self):
mock_zk = self._api_server._db_conn._zk_db
vn_obj = VirtualNetwork('%s-vn' % self.id())
self._vnc_lib.virtual_network_create(vn_obj)
vn_obj = self._vnc_lib.virtual_network_read(id=vn_obj.uuid)
vn_id = vn_obj.virtual_network_network_id

self._vnc_lib.virtual_network_delete(id=vn_obj.uuid)

self.assertIsNone(mock_zk.get_vn_from_id(vn_id))

def test_cannot_update_vn_id(self):
vn_obj = VirtualNetwork('%s-vn' % self.id())
self._vnc_lib.virtual_network_create(vn_obj)
vn_obj = self._vnc_lib.virtual_network_read(id=vn_obj.uuid)

vn_obj.set_virtual_network_network_id(42)
with ExpectedException(PermissionDenied):
self._vnc_lib.virtual_network_update(vn_obj)

def test_allocate_vn_id_on_create_notification(self):
mock_zk = self._api_server._db_conn._zk_db
vn_obj = VirtualNetwork('%s-vn' % self.id())
create_vn_invoked = []
def dont_allocate_vn_id_on_creation(orig_method, *args, **kwargs):
if args[0] == vn_obj.get_fq_name_str() and not create_vn_invoked:
create_vn_invoked.append(True)
return
return orig_method(*args, **kwargs)

with test_common.patch(mock_zk, 'alloc_vn_id',
dont_allocate_vn_id_on_creation):
self._vnc_lib.virtual_network_create(vn_obj)
gevent.sleep(0.1)

vn_obj = self._vnc_lib.virtual_network_read(id=vn_obj.uuid)
vn_id = vn_obj.virtual_network_network_id
self.assertEqual(vn_obj.get_fq_name_str(),
mock_zk.get_vn_from_id(vn_id))

def test_deallocate_vn_id_on_delete_notification(self):
mock_zk = self._api_server._db_conn._zk_db
vn_obj = VirtualNetwork('%s-vn' % self.id())
self._vnc_lib.virtual_network_create(vn_obj)
vn_obj = self._vnc_lib.virtual_network_read(id=vn_obj.uuid)
vn_id = vn_obj.virtual_network_network_id
delete_vn_invoked = []
def dont_deallocate_vn_id_on_deletion(orig_method, *args, **kwargs):
if args[0] == vn_obj.get_fq_name_str() and not delete_vn_invoked:
delete_vn_invoked.append(True)
return
return orig_method(*args, **kwargs)

with test_common.patch(mock_zk, 'alloc_vn_id',
dont_deallocate_vn_id_on_deletion):
self._vnc_lib.virtual_network_delete(id=vn_obj.uuid)
gevent.sleep(0.1)

self.assertIsNone(mock_zk.get_vn_from_id(vn_id))
# end class TestVncCfgApiServer


Expand Down
14 changes: 13 additions & 1 deletion src/config/api-server/vnc_cfg_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,12 @@ def create_default_children(self, object_type, parent_obj):
return (ok, result)
obj_ids = result

# For virtual networks, allocate an ID
if child_obj_type == 'virtual_network':
child_dict['virtual_network_network_id'] =\
self._db_conn._zk_db.alloc_vn_id(
child_obj.get_fq_name_str())

(ok, result) = self._db_conn.dbe_create(child_obj_type, obj_ids,
child_dict)
if not ok:
Expand Down Expand Up @@ -1444,7 +1450,6 @@ def __init__(self, args_str=None):
vnc_cfg_types.AliasIpServer.addr_mgmt = addr_mgmt
vnc_cfg_types.InstanceIpServer.addr_mgmt = addr_mgmt
vnc_cfg_types.VirtualNetworkServer.addr_mgmt = addr_mgmt
vnc_cfg_types.InstanceIpServer.manager = self
self._addr_mgmt = addr_mgmt

# Authn/z interface
Expand Down Expand Up @@ -1481,6 +1486,8 @@ def __init__(self, args_str=None):
self.re_uuid = re.compile('^[0-9A-F]{8}-?[0-9A-F]{4}-?4[0-9A-F]{3}-?[89AB][0-9A-F]{3}-?[0-9A-F]{12}$',
re.IGNORECASE)

# VncZkClient client assignment
vnc_cfg_types.VirtualNetworkServer.vnc_zk_client = self._db_conn._zk_db
# end __init__

def sandesh_disc_client_subinfo_handle_request(self, req):
Expand Down Expand Up @@ -2809,6 +2816,11 @@ def _create_singleton_entry(self, singleton_obj):
obj_dict['perms2'] = self._get_default_perms2()
(ok, result) = self._db_conn.dbe_alloc(obj_type, obj_dict)
obj_ids = result
# For virtual networks, allocate an ID
if obj_type == 'virtual_network':
vn_id = self._db_conn._zk_db.alloc_vn_id(
s_obj.get_fq_name_str())
obj_dict['virtual_network_network_id'] = vn_id
self._db_conn.dbe_create(obj_type, obj_ids, obj_dict)
method = '_%s_create_default_children' %(obj_type)
def_children_method = getattr(self, method)
Expand Down
26 changes: 24 additions & 2 deletions src/config/api-server/vnc_cfg_ifmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,9 @@ class VncZkClient(object):
_FQ_NAME_TO_UUID_PATH = "/fq-name-to-uuid"
_MAX_SUBNET_ADDR_ALLOC = 65535

_VN_ID_ALLOC_PATH = "/id/virtual-networks/"
_VN_MAX_ID = 1 << 24

def __init__(self, instance_id, zk_server_ip, reset_config, db_prefix,
sandesh_hdl):
self._db_prefix = db_prefix
Expand All @@ -1222,6 +1225,7 @@ def __init__(self, instance_id, zk_server_ip, reset_config, db_prefix,
client_name = client_pfx + 'api-' + instance_id
self._subnet_path = zk_path_pfx + self._SUBNET_PATH
self._fq_name_to_uuid_path = zk_path_pfx + self._FQ_NAME_TO_UUID_PATH
_vn_id_alloc_path = zk_path_pfx + self._VN_ID_ALLOC_PATH
self._zk_path_pfx = zk_path_pfx

self._sandesh = sandesh_hdl
Expand All @@ -1237,9 +1241,16 @@ def __init__(self, instance_id, zk_server_ip, reset_config, db_prefix,
pass

if reset_config:
self._zk_client.delete_node(self._subnet_path, True);
self._zk_client.delete_node(self._fq_name_to_uuid_path, True);
self._zk_client.delete_node(self._subnet_path, True)
self._zk_client.delete_node(self._fq_name_to_uuid_path, True)
self._zk_client.delete_node(_vn_id_alloc_path, True)

self._subnet_allocators = {}

# Initialize the virtual network ID allocator
self._vn_id_allocator = IndexAllocator(self._zk_client,
_vn_id_alloc_path,
self._VN_MAX_ID)
# end __init__

def master_election(self, func, *args):
Expand Down Expand Up @@ -1350,6 +1361,17 @@ def is_connected(self):
return self._zk_client.is_connected()
# end is_connected

def alloc_vn_id(self, name):
if name is not None:
return self._vn_id_allocator.alloc(name)

def free_vn_id(self, vn_id):
if vn_id is not None:
self._vn_id_allocator.delete(vn_id)

def get_vn_from_id(self, vn_id):
if vn_id is not None:
return self._vn_id_allocator.read(vn_id)
# end VncZkClient


Expand Down
48 changes: 46 additions & 2 deletions src/config/api-server/vnc_cfg_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,14 @@ def pre_dbe_create(cls, tenant_name, obj_dict, db_conn):
if not ok:
return (ok, response)

# Allocate virtual network ID
vn_id = cls.vnc_zk_client.alloc_vn_id(':'.join(obj_dict['fq_name']))
def undo_vn_id():
cls.vnc_zk_client.free_vn_id(vn_id)
return True, ""
get_context().push_undo(undo_vn_id)
obj_dict['virtual_network_network_id'] = vn_id

db_conn.update_subnet_uuid(obj_dict)

(ok, result) = cls.addr_mgmt.net_check_subnet_overlap(obj_dict)
Expand Down Expand Up @@ -1056,7 +1064,9 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs):
if not ok:
return (False, (409, error))

if 'network_ipam_refs' not in obj_dict:
new_vn_id = obj_dict.get('virtual_network_network_id')

if 'network_ipam_refs' not in obj_dict and new_vn_id is None:
# NOP for addr-mgmt module
return True, ""

Expand All @@ -1065,6 +1075,12 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs):
if not ok:
return ok, read_result

# Does not authorize to update the virtual network ID as it's allocated
# by the vnc server
if (new_vn_id and
new_vn_id != read_result.get('virtual_network_network_id')):
return (False, (403, "Cannot update the virtual network ID"))

(ok, response) = cls._is_multi_policy_service_chain_supported(obj_dict,
read_result)
if not ok:
Expand Down Expand Up @@ -1149,6 +1165,10 @@ def drop_ref(obj_uuid):

cls.server.internal_request_delete('routing-instance', ri_uuid)

# Deallocate the virtual network ID
cls.vnc_zk_client.free_vn_id(
obj_dict.get('virtual_network_network_id'))

return True, ""
# end post_dbe_delete

Expand Down Expand Up @@ -1188,6 +1208,20 @@ def subnet_ip_count(cls, vn_fq_name, subnet_list):

@classmethod
def dbe_create_notification(cls, obj_ids, obj_dict):
# Allocate virtual network ID if not already set
# TODO(ethuleau): That check is just there for the upgrade from
# version where vni was allocated by the schema to the
# version where vni is allocated by the vnc api
# (so from 3.0 to 3.1, we can remove that in 3.2)
# Remove also unit test:
# - test_allocate_vn_id_on_create_notification
if obj_dict.get('virtual_network_network_id') is None:
vn_id = cls.vnc_zk_client.alloc_vn_id(':'.join(obj_dict['fq_name']))
db_conn = cls.server.get_db_connection()
db_conn.dbe_update('virtual_network',
{'uuid': obj_dict['uuid']},
{'virtual_network_network_id': vn_id})

cls.addr_mgmt.net_create_notify(obj_ids, obj_dict)
# end dbe_create_notification

Expand All @@ -1199,7 +1233,17 @@ def dbe_update_notification(cls, obj_ids):
@classmethod
def dbe_delete_notification(cls, obj_ids, obj_dict):
cls.addr_mgmt.net_delete_notify(obj_ids, obj_dict)
# end dbe_update_notification

# Deallocate virtual network ID if it's still there
# TODO(ethuleau): That check is just there for the upgrade from
# version where vni was allocated by the schema to the
# version where vni is allocated by the vnc api
# (so from 3.0 to 3.1, we can remove that in 3.2)
# Remove also unit test:
# - test_deallocate_vn_id_on_delete_notification
cls.vnc_zk_client.free_vn_id(
obj_dict.get('virtual_network_network_id'))
# end dbe_delete_notification

# end class VirtualNetworkServer

Expand Down
12 changes: 0 additions & 12 deletions src/config/schema-transformer/config_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,6 @@ def __init__(self, name, obj=None, acl_dict=None):
prop = self.obj.get_virtual_network_properties(
) or VirtualNetworkType()
self.allow_transit = prop.allow_transit
nid = self.obj.get_virtual_network_network_id()
if nid is None:
nid = prop.network_id or self._cassandra.alloc_vn_id(name) + 1
self.obj.set_virtual_network_network_id(nid)
self._vnc_lib.virtual_network_update(self.obj)
if self.obj.get_fq_name() == common.IP_FABRIC_VN_FQ_NAME:
default_ri_fq_name = common.IP_FABRIC_RI_FQ_NAME
elif self.obj.get_fq_name() == common.LINK_LOCAL_VN_FQ_NAME:
Expand Down Expand Up @@ -415,13 +410,6 @@ def delete_obj(self):
self._vnc_lib.access_control_list_delete(id=self.acl.uuid)
if self.dynamic_acl:
self._vnc_lib.access_control_list_delete(id=self.dynamic_acl.uuid)
nid = self.obj.get_virtual_network_network_id()
if nid is None:
props = self.obj.get_virtual_network_properties()
if props:
nid = props.network_id
if nid:
self._cassandra.free_vn_id(nid - 1)

self.update_multiple_refs('route_table', {})
self.uve_send(deleted=True)
Expand Down
14 changes: 0 additions & 14 deletions src/config/schema-transformer/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ class SchemaTransformerDB(VncCassandraClient):
_BGP_RTGT_MAX_ID = 1 << 24
_BGP_RTGT_ALLOC_PATH = "/id/bgp/route-targets/"

_VN_MAX_ID = 1 << 24
_VN_ID_ALLOC_PATH = "/id/virtual-networks/"

_SECURITY_GROUP_MAX_ID = 1 << 32
_SECURITY_GROUP_ID_ALLOC_PATH = "/id/security-groups/id/"

Expand Down Expand Up @@ -85,8 +82,6 @@ def __init__(self, manager, zkclient):
if self._args.reset_config:
zkclient.delete_node(self._zk_path_pfx + "/id", True)

self._vn_id_allocator = IndexAllocator(
zkclient, self._zk_path_pfx+self._VN_ID_ALLOC_PATH, self._VN_MAX_ID)
self._sg_id_allocator = IndexAllocator(
zkclient, self._zk_path_pfx+self._SECURITY_GROUP_ID_ALLOC_PATH,
self._SECURITY_GROUP_MAX_ID)
Expand Down Expand Up @@ -269,15 +264,6 @@ def alloc_sg_id(self, name):
def free_sg_id(self, sg_id):
self._sg_id_allocator.delete(sg_id)

def get_vn_from_id(self, vn_id):
return self._vn_id_allocator.read(vn_id)

def alloc_vn_id(self, name):
return self._vn_id_allocator.alloc(name)

def free_vn_id(self, vn_id):
self._vn_id_allocator.delete(vn_id)

def get_bgpaas_port(self, port):
return self._bgpaas_allocator.read(port)

Expand Down

0 comments on commit 9df6229

Please sign in to comment.