From 53208ba8acc73bdaad0a74eb9e5a8f2b7ca54e73 Mon Sep 17 00:00:00 2001 From: Sachin Bansal Date: Thu, 23 Jun 2016 14:49:07 -0700 Subject: [PATCH] Alias ip support in api server Change-Id: I6332314f82907467cc7baa0de5b2e66068db31a6 Partial-Bug: 1572502 --- .../api-server/tests/test_crud_basic.py | 98 +++++++++++++++++++ src/config/api-server/tests/test_ip_alloc.py | 36 +++++++ src/config/api-server/vnc_addr_mgmt.py | 65 ++++++++++-- src/config/api-server/vnc_cfg_api_server.py | 3 + src/config/api-server/vnc_cfg_types.py | 86 ++++++++++++++++ src/config/schema-transformer/config_db.py | 43 ++++++++ src/config/schema-transformer/to_bgp.py | 5 + 7 files changed, 326 insertions(+), 10 deletions(-) diff --git a/src/config/api-server/tests/test_crud_basic.py b/src/config/api-server/tests/test_crud_basic.py index 73c3679f0d2..04628b1a6b9 100644 --- a/src/config/api-server/tests/test_crud_basic.py +++ b/src/config/api-server/tests/test_crud_basic.py @@ -107,6 +107,10 @@ def test_floating_ip_crud(self): pass # end test_floating_ip_crud + def test_alias_ip_crud(self): + pass + # end test_alias_ip_crud + def test_id_perms(self): # create object in enabled state # create object in disabled state @@ -298,6 +302,30 @@ def test_ip_alloc_on_net(self): self.assertThat(ip_allocated, Equals('1.1.1.3')) logger.info("...verified") + logger.info("Creating alias-ip-pool") + aip_pool_fixt = self.useFixture( + AliasIpPoolTestFixtureGen(self._vnc_lib, 'aip-pool', + parent_fixt=vn_fixt, + auto_prop_val=False)) + + logger.info("Creating auto-alloc alias-ip, expecting 1.1.1.251...") + aip_fixt = self.useFixture( + AliasIpTestFixtureGen( + self._vnc_lib, 'aip1', parent_fixt=aip_pool_fixt, + auto_prop_val=False)) + ip_allocated = aip_fixt.getObj().alias_ip_address + self.assertThat(ip_allocated, Equals('1.1.1.251')) + logger.info("...verified") + + logger.info("Creating specific alias-ip, expecting 1.1.1.4...") + aip_fixt = self.useFixture( + AliasIpTestFixtureGen( + self._vnc_lib, 'aip2', parent_fixt=aip_pool_fixt, + auto_prop_val=False, alias_ip_address='1.1.1.4')) + ip_allocated = aip_fixt.getObj().alias_ip_address + self.assertThat(ip_allocated, Equals('1.1.1.4')) + logger.info("...verified") + logger.info("Creating subnet 2.2.2.0/24, gateway 2.2.2.128") subnet_vnc = IpamSubnetType(subnet=SubnetType('2.2.2.0', 24), default_gateway='2.2.2.128') @@ -1127,6 +1155,42 @@ def test_floatingip_as_instanceip(self): virtual_network_refs=[vn_fixt.getObj()])) # end test_floatingip_as_instanceip + def test_aliasip_as_instanceip(self): + ipam_fixt = self.useFixture(NetworkIpamTestFixtureGen(self._vnc_lib)) + + project_fixt = self.useFixture(ProjectTestFixtureGen(self._vnc_lib, 'default-project')) + + subnet_vnc = IpamSubnetType(subnet=SubnetType('1.1.1.0', 24)) + vnsn_data = VnSubnetsType([subnet_vnc]) + logger.info("Creating a virtual network") + logger.info("Creating subnet 1.1.1.0/24") + vn_fixt = self.useFixture(VirtualNetworkTestFixtureGen(self._vnc_lib, + 'vn-%s' %(self.id()), + network_ipam_ref_infos=[(ipam_fixt.getObj(), vnsn_data)])) + vn_fixt.getObj().set_router_external(True) + self._vnc_lib.virtual_network_update(vn_fixt.getObj()) + + logger.info("Fetching alias-ip-pool") + aip_pool_fixt = self.useFixture( + AliasIpPoolTestFixtureGen(self._vnc_lib, 'alias-ip-pool', + parent_fixt=vn_fixt)) + + logger.info("Creating auto-alloc alias-ip") + aip_fixt = self.useFixture( + AliasIpTestFixtureGen( + self._vnc_lib, 'aip1', parent_fixt=aip_pool_fixt, + project_refs=[project_fixt.getObj()])) + ip_allocated = aip_fixt.getObj().alias_ip_address + + logger.info("Creating auto-alloc instance-ip, expecting an error") + with ExpectedException(PermissionDenied) as e: + iip_fixt = self.useFixture( + InstanceIpTestFixtureGen( + self._vnc_lib, 'iip1', auto_prop_val=False, + instance_ip_address=ip_allocated, + virtual_network_refs=[vn_fixt.getObj()])) + # end test_aliasip_as_instanceip + def test_name_with_reserved_xml_char(self): self.skipTest("Skipping test_name_with_reserved_xml_char") vn_name = self.id()+'-&vn<1>"2\'' @@ -1737,6 +1801,8 @@ def err_on_delete(orig_method, *args, **kwargs): def err_on_delete(orig_method, *args, **kwargs): if args[0] == 'floating_ip': raise Exception("Faking db delete for floating ip") + if args[0] == 'alias_ip': + raise Exception("Faking db delete for alias ip") return orig_method(*args, **kwargs) with test_common.patch( self._api_server._db_conn, 'dbe_delete', err_on_delete): @@ -1759,6 +1825,38 @@ def err_on_delete(orig_method, *args, **kwargs): self._vnc_lib.floating_ip_read( id=fip_obj.uuid).floating_ip_address, fip_obj.floating_ip_address) + + # alias-ip test + aip_pool_obj = AliasIpPool( + 'aip-pool-%s' %(self.id()), parent_obj=vn_obj) + self._vnc_lib.alias_ip_pool_create(aip_pool_obj) + aip_obj = AliasIp('aip-%s' %(self.id()), parent_obj=aip_pool_obj) + aip_obj.add_project(Project()) + self._vnc_lib.alias_ip_create(aip_obj) + # read back to get allocated alias-ip + aip_obj = self._vnc_lib.alias_ip_read(id=aip_obj.uuid) + + with test_common.patch( + self._api_server._db_conn, 'dbe_delete', err_on_delete): + try: + self._vnc_lib.alias_ip_delete(id=aip_obj.uuid) + self.assertTrue( + False, 'Alias IP delete worked unexpectedly') + except Exception as e: + self.assertThat(str(e), + Contains('"Faking db delete for alias ip"')) + # assert reservation present in zookeeper and value in iip + zk_node = "%(#)010d" % {'#': int(netaddr.IPAddress( + aip_obj.alias_ip_address))} + zk_path = '/api-server/subnets/%s:1.1.1.0/28/%s' %( + vn_obj.get_fq_name_str(), zk_node) + mock_zk = self._api_server._db_conn._zk_db._zk_client._zk_client + self.assertEqual( + mock_zk._values[zk_path][0], aip_obj.uuid) + self.assertEqual( + self._vnc_lib.alias_ip_read( + id=aip_obj.uuid).alias_ip_address, + aip_obj.alias_ip_address) # end test_ip_addr_not_released_on_delete_error def test_uve_trace_delete_name_from_msg(self): diff --git a/src/config/api-server/tests/test_ip_alloc.py b/src/config/api-server/tests/test_ip_alloc.py index 5eced49fff5..12c349aa538 100644 --- a/src/config/api-server/tests/test_ip_alloc.py +++ b/src/config/api-server/tests/test_ip_alloc.py @@ -967,6 +967,10 @@ def test_ip_alloc_clash(self): 'fip-pool-%s' %(self.id()), parent_obj=vn_obj) self._vnc_lib.floating_ip_pool_create(fip_pool_obj) + aip_pool_obj = AliasIpPool( + 'aip-pool-%s' %(self.id()), parent_obj=vn_obj) + self._vnc_lib.alias_ip_pool_create(aip_pool_obj) + iip_obj = InstanceIp('existing-iip-%s' %(self.id())) iip_obj.add_virtual_network(vn_obj) self._vnc_lib.instance_ip_create(iip_obj) @@ -979,6 +983,12 @@ def test_ip_alloc_clash(self): # read-in to find allocated address fip_obj = self._vnc_lib.floating_ip_read(id=fip_obj.uuid) + aip_obj = AliasIp('existing-aip-%s' %(self.id()), aip_pool_obj) + aip_obj.add_project(proj_obj) + self._vnc_lib.alias_ip_create(aip_obj) + # read-in to find allocated address + aip_obj = self._vnc_lib.alias_ip_read(id=aip_obj.uuid) + vm_obj = VirtualMachine('vm-%s' %(self.id())) self._vnc_lib.virtual_machine_create(vm_obj) @@ -1023,18 +1033,44 @@ def test_ip_alloc_clash(self): 'Ip address already in use') as e: self._vnc_lib.floating_ip_create(fip2_obj) + # allocate alias-ip clashing with existing alias-ip + aip2_obj = AliasIp('clashing-aip-%s' %(self.id()), aip_pool_obj, + alias_ip_address=aip_obj.alias_ip_address) + aip2_obj.add_project(proj_obj) + with ExpectedException(cfgm_common.exceptions.PermissionDenied, + 'Ip address already in use') as e: + self._vnc_lib.alias_ip_create(aip2_obj) + # allocate floating-ip clashing with existing instance-ip fip2_obj.set_floating_ip_address(iip_obj.instance_ip_address) with ExpectedException(cfgm_common.exceptions.PermissionDenied, 'Ip address already in use') as e: self._vnc_lib.floating_ip_create(fip2_obj) + # allocate alias-ip clashing with existing instance-ip + aip2_obj.set_alias_ip_address(iip_obj.instance_ip_address) + with ExpectedException(cfgm_common.exceptions.PermissionDenied, + 'Ip address already in use') as e: + self._vnc_lib.alias_ip_create(aip2_obj) + + # allocate alias-ip clashing with existing floating-ip + aip2_obj.set_alias_ip_address(fip_obj.floating_ip_address) + with ExpectedException(cfgm_common.exceptions.PermissionDenied, + 'Ip address already in use') as e: + self._vnc_lib.alias_ip_create(aip2_obj) + # allocate floating-ip with gateway ip and verify failure fip2_obj.set_floating_ip_address('11.1.1.254') with ExpectedException(cfgm_common.exceptions.PermissionDenied, 'Ip address already in use') as e: self._vnc_lib.floating_ip_create(fip2_obj) + # allocate alias-ip with gateway ip and verify failure + aip2_obj.set_alias_ip_address('11.1.1.254') + with ExpectedException(cfgm_common.exceptions.PermissionDenied, + 'Ip address already in use') as e: + self._vnc_lib.alias_ip_create(aip2_obj) + # allocate 2 instance-ip with gateway ip - should work # then verify iip cannot # ref to vm port (during iip-update # or vmi-update) diff --git a/src/config/api-server/vnc_addr_mgmt.py b/src/config/api-server/vnc_addr_mgmt.py index 66011af5237..2eb84b838af 100644 --- a/src/config/api-server/vnc_addr_mgmt.py +++ b/src/config/api-server/vnc_addr_mgmt.py @@ -834,10 +834,11 @@ def net_check_subnet(self, req_vn_dict): # end net_check_subnet # check subnets associated with a virtual network, return error if - # any subnet is being deleted and has backref to instance-ip/floating-ip + # any subnet is being deleted and has backref to + # instance-ip/floating-ip/alias-ip def net_check_subnet_delete(self, db_vn_dict, req_vn_dict): db_conn = self._get_db_conn() - # if all instance-ip/floating-ip are part of requested list + # if all ips are part of requested list # things are ok. # eg. existing [1.1.1.0/24, 2.2.2.0/24], # requested [1.1.1.0/24] OR @@ -856,18 +857,20 @@ def net_check_subnet_delete(self, db_vn_dict, req_vn_dict): # read the instance ip and floating ip pool only if subnet is being # deleted. Skip the port check if no subnet is being deleted vn_id = {'uuid': db_vn_dict['uuid']} - obj_fields = ['network_ipam_refs', 'instance_ip_back_refs', 'floating_ip_pools'] + obj_fields = ['network_ipam_refs', 'instance_ip_back_refs', 'floating_ip_pools', 'alias_ip_pools'] (read_ok, db_vn_dict) = db_conn.dbe_read('virtual_network', vn_id, obj_fields) if not read_ok: return (False, (500, db_vn_dict)) # if all subnets are being removed, check for any iip backrefs - # or floating pools still present in DB version of VN + # or floating/alias pools still present in DB version of VN if len(requested_subnets) == 0: if db_vn_dict.get('instance_ip_back_refs'): return False, "Cannot Delete IP Block, Instance IP(s) in use" if db_vn_dict.get('floating_ip_pools'): return False, "Cannot Delete IP Block, Floating Pool(s) in use" + if db_vn_dict.get('alias_ip_pools'): + return False, "Cannot Delete IP Block, Alias Pool(s) in use" else: return True, "" @@ -892,9 +895,9 @@ def net_check_subnet_delete(self, db_vn_dict, req_vn_dict): level=SandeshLevel.SYS_ERR) continue if not all_matching_cidrs(inst_ip, requested_subnets): - return False,\ - "Cannot Delete IP Block, IP(%s) is in use"\ - % (inst_ip) + return (False, + "Cannot Delete IP Block, IP(%s) is in use" + % (inst_ip)) fip_pool_refs = db_vn_dict.get('floating_ip_pools', []) for ref in fip_pool_refs: @@ -934,9 +937,51 @@ def net_check_subnet_delete(self, db_vn_dict, req_vn_dict): level=SandeshLevel.SYS_ERR) continue if not all_matching_cidrs(fip_addr, requested_subnets): - return False,\ - "Cannot Delete IP Block, Floating IP(%s) is in use"\ - % (fip_addr) + return (False, + "Cannot Delete IP Block, Floating IP(%s) is in use" + % (fip_addr)) + + aip_pool_refs = db_vn_dict.get('alias_ip_pools', []) + for ref in aip_pool_refs: + try: + (ok, result) = db_conn.dbe_read( + 'alias_ip_pool', {'uuid': ref['uuid']}) + except cfgm_common.exceptions.NoIdError: + continue + if not ok: + self.config_log( + "Error in subnet delete alias-ip-pool check: %s" + %(result), + level=SandeshLevel.SYS_ERR) + return False, result + + alias_ips = result.get('alias_ips', []) + for alias_ip in alias_ips: + # get alias_ip_address and this should be in + # new subnet_list + try: + (read_ok, read_result) = db_conn.dbe_read( + 'alias_ip', {'uuid': floating_ip['uuid']}) + except cfgm_common.exceptions.NoIdError: + continue + if not read_ok: + self.config_log( + "Error in subnet delete floating-ip check: %s" + %(read_result), + level=SandeshLevel.SYS_ERR) + return False, result + + aip_addr = read_result.get('alias_ip_address') + if not aip_addr: + self.config_log( + "Error in subnet delete aip null: %s" + %(alias_ip['uuid']), + level=SandeshLevel.SYS_ERR) + continue + if not all_matching_cidrs(aip_addr, requested_subnets): + return (False, + "Cannot Delete IP Block, Floating IP(%s) is in use" + % (aip_addr)) return True, "" # end net_check_subnet_delete diff --git a/src/config/api-server/vnc_cfg_api_server.py b/src/config/api-server/vnc_cfg_api_server.py index 35896fba2f2..784909f72ee 100644 --- a/src/config/api-server/vnc_cfg_api_server.py +++ b/src/config/api-server/vnc_cfg_api_server.py @@ -1281,6 +1281,8 @@ def __init__(self, args_str=None): 'access-control-list').generate_default_instance = False self.get_resource_class( 'floating-ip-pool').generate_default_instance = False + self.get_resource_class( + 'alias-ip-pool').generate_default_instance = False self.get_resource_class('instance-ip').generate_default_instance = False self.get_resource_class('logical-router').generate_default_instance = False self.get_resource_class('security-group').generate_default_instance = False @@ -1429,6 +1431,7 @@ def __init__(self, args_str=None): vnc_cfg_types.SecurityGroupServer.addr_mgmt = addr_mgmt vnc_cfg_types.VirtualMachineInterfaceServer.addr_mgmt = addr_mgmt vnc_cfg_types.FloatingIpServer.addr_mgmt = addr_mgmt + 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 diff --git a/src/config/api-server/vnc_cfg_types.py b/src/config/api-server/vnc_cfg_types.py index cace2df9923..749374fe71d 100644 --- a/src/config/api-server/vnc_cfg_types.py +++ b/src/config/api-server/vnc_cfg_types.py @@ -241,6 +241,92 @@ def dbe_delete_notification(cls, obj_ids, obj_dict): # end class FloatingIpServer +class AliasIpServer(Resource, AliasIp): + generate_default_instance = False + + @classmethod + def pre_dbe_create(cls, tenant_name, obj_dict, db_conn): + if 'project_refs' not in obj_dict: + return False, (400, 'Alias Ip should have project reference') + + proj_dict = obj_dict['project_refs'][0] + if 'uuid' in proj_dict: + proj_uuid = proj_dict['uuid'] + else: + proj_uuid = db_conn.fq_name_to_uuid('project', proj_dict['to']) + + user_visibility = obj_dict['id_perms'].get('user_visible', True) + verify_quota_kwargs = {'db_conn': db_conn, + 'fq_name': obj_dict['fq_name'], + 'resource': 'alias_ip_back_refs', + 'obj_type': 'alias_ip', + 'user_visibility': user_visibility, + 'proj_uuid': proj_uuid} + (ok, response) = QuotaHelper.verify_quota_for_resource( + **verify_quota_kwargs) + + if not ok: + return (ok, response) + + vn_fq_name = obj_dict['fq_name'][:-2] + req_ip = obj_dict.get("alias_ip_address") + if req_ip and cls.addr_mgmt.is_ip_allocated(req_ip, vn_fq_name): + return (False, (403, 'Ip address already in use')) + try: + aip_addr = cls.addr_mgmt.ip_alloc_req(vn_fq_name, + asked_ip_addr=req_ip, + alloc_id=obj_dict['uuid']) + def undo(): + db_conn.config_log( + 'AddrMgmt: free FIP %s for vn=%s tenant=%s, on undo' + % (fip_addr, vn_fq_name, tenant_name), + level=SandeshLevel.SYS_DEBUG) + cls.addr_mgmt.ip_free_req(aip_addr, vn_fq_name) + return True, "" + # end undo + get_context().push_undo(undo) + except Exception as e: + return (False, (500, str(e))) + + obj_dict['alias_ip_address'] = aip_addr + db_conn.config_log('AddrMgmt: alloc %s AIP for vn=%s, tenant=%s, askip=%s' \ + % (obj_dict['alias_ip_address'], vn_fq_name, tenant_name, + req_ip), level=SandeshLevel.SYS_DEBUG) + + return True, "" + # end pre_dbe_create + + + @classmethod + def post_dbe_delete(cls, id, obj_dict, db_conn): + vn_fq_name = obj_dict['fq_name'][:-2] + aip_addr = obj_dict['alias_ip_address'] + db_conn.config_log('AddrMgmt: free AIP %s for vn=%s' + % (aip_addr, vn_fq_name), + level=SandeshLevel.SYS_DEBUG) + cls.addr_mgmt.ip_free_req(aip_addr, vn_fq_name) + + return True, "" + # end post_dbe_delete + + + @classmethod + def dbe_create_notification(cls, obj_ids, obj_dict): + aip_addr = obj_dict['alias_ip_address'] + vn_fq_name = obj_dict['fq_name'][:-2] + cls.addr_mgmt.ip_alloc_notify(aip_addr, vn_fq_name) + # end dbe_create_notification + + @classmethod + def dbe_delete_notification(cls, obj_ids, obj_dict): + aip_addr = obj_dict['alias_ip_address'] + vn_fq_name = obj_dict['fq_name'][:-2] + cls.addr_mgmt.ip_free_notify(aip_addr, vn_fq_name) + # end dbe_delete_notification + +# end class AliasIpServer + + class InstanceIpServer(Resource, InstanceIp): generate_default_instance = False diff --git a/src/config/schema-transformer/config_db.py b/src/config/schema-transformer/config_db.py index b7f0c0a8e3b..b18d68cc923 100644 --- a/src/config/schema-transformer/config_db.py +++ b/src/config/schema-transformer/config_db.py @@ -3121,11 +3121,13 @@ def __init__(self, name, obj=None): self.uuid = None self.instance_ips = set() self.floating_ips = set() + self.alias_ips = set() self.routing_instances = {} self.obj = obj or self.read_vnc_obj(fq_name=name) self.uuid = self.obj.uuid self.update_multiple_refs('instance_ip', self.obj) self.update_multiple_refs('floating_ip', self.obj) + self.update_multiple_refs('alias_ip', self.obj) self.vrf_table = jsonpickle.encode(self.obj.get_vrf_assign_table()) self.update(self.obj) # end __init__ @@ -3151,6 +3153,7 @@ def delete_obj(self): self.update_single_ref('logical_router', {}) self.update_multiple_refs('instance_ip', {}) self.update_multiple_refs('floating_ip', {}) + self.update_multiple_refs('alias_ip', {}) self.update_single_ref('bgp_as_a_service', {}) self.update_routing_instances([]) # end delete_obj @@ -3326,6 +3329,10 @@ def recreate_vrf_assign_table(self): ip = FloatingIpST.get(ip_name) if ip and ip.address: ip_list.append(ip) + for ip_name in self.alias_ips: + ip = AliasIpST.get(ip_name) + if ip and ip.address: + ip_list.append(ip) for ip in ip_list: if ip.ip_version == 6: address = AddressType(subnet=SubnetType(ip.address, 128)) @@ -3389,6 +3396,7 @@ def handle_st_object_req(self): resp.obj_refs = [ self._get_sandesh_ref_list('instance_ip'), self._get_sandesh_ref_list('floating_ip'), + self._get_sandesh_ref_list('alias_ip'), self._get_sandesh_ref_list('virtual_network'), self._get_sandesh_ref_list('virtual_machine'), self._get_sandesh_ref_list('port_tuple'), @@ -3511,6 +3519,41 @@ def handle_st_object_req(self): # end FloatingIpST +class AliasIpST(DBBaseST): + _dict = {} + obj_type = 'alias_ip' + + def __init__(self, name, obj=None): + self.name = name + self.virtual_machine_interface = None + self.ip_version = None + self.update(obj) + # end __init + + def update(self, obj=None): + self.obj = obj or self.read_vnc_obj(fq_name=self.name) + self.address = self.obj.get_alias_ip_address() + if self.address: + self.ip_version = IPAddress(self.address).version + self.update_single_ref('virtual_machine_interface', self.obj) + # end update + + def delete_obj(self): + self.update_single_ref('virtual_machine_interface', {}) + + def handle_st_object_req(self): + resp = super(AliasIpST, self).handle_st_object_req() + resp.obj_refs = [ + self._get_sandesh_ref_list('virtual_machine_interface'), + ] + resp.properties = [ + sandesh.PropList('address', self.address), + ] + return resp + # end handle_st_object_req +# end AliasIpST + + class VirtualMachineST(DBBaseST): _dict = {} obj_type = 'virtual_machine' diff --git a/src/config/schema-transformer/to_bgp.py b/src/config/schema-transformer/to_bgp.py index b5cacfa7c2b..cc38bbef42b 100644 --- a/src/config/schema-transformer/to_bgp.py +++ b/src/config/schema-transformer/to_bgp.py @@ -68,6 +68,7 @@ class SchemaTransformer(object): 'logical_router': ['virtual_network'], 'instance_ip': ['virtual_machine', 'port_tuple', 'bgp_as_a_service', 'virtual_network'], 'floating_ip': ['virtual_machine', 'port_tuple'], + 'alias_ip': ['virtual_machine', 'port_tuple'], 'virtual_machine': ['virtual_network'], 'port_tuple': ['virtual_network'], 'bgp_as_a_service': [], @@ -122,6 +123,9 @@ class SchemaTransformer(object): 'floating_ip': { 'self': ['virtual_machine_interface'], }, + 'alias_ip': { + 'self': ['virtual_machine_interface'], + }, 'instance_ip': { 'self': ['virtual_machine_interface'], }, @@ -452,6 +456,7 @@ def reinit(self): InstanceIpST.reinit() gevent.sleep(0.001) FloatingIpST.reinit() + AliasIpST.reinit() gevent.sleep(0.001) for si in ServiceInstanceST.list_vnc_obj():