Add support for resuing tenants/users

1. Add support for resuing tenants/users;
2. Add the version checks;
3. KNOWN ISSUE: Single cloud mode is not supported under reusing mode.
(WIP on the solution)

Change-Id: I0607312d341e53df4ad6d869ac4265f074c23cf5
This commit is contained in:
Yichen Wang 2015-07-16 16:32:21 -07:00
parent 522e6c1167
commit 91487faa20
11 changed files with 231 additions and 92 deletions

View File

@ -79,7 +79,10 @@ class BaseCompute(object):
instance = self.novaclient.servers.get(instance.id)
if instance.status == 'ACTIVE':
self.instance = instance
self.host = instance.__dict__['OS-EXT-SRV-ATTR:hypervisor_hostname']
if 'OS-EXT-SRV-ATTR:hypervisor_hostname' in instance.__dict__:
self.host = instance.__dict__['OS-EXT-SRV-ATTR:hypervisor_hostname']
else:
self.host = "Unknown"
return instance
if instance.status == 'ERROR':
LOG.error('Instance creation error:' + instance.fault['message'])

View File

@ -158,7 +158,7 @@ class BaseNetwork(object):
"""
Create a network with 1 subnet inside it
"""
subnet_name = "kloudbuster_subnet_" + network_name
subnet_name = "KB_subnet_" + network_name
body = {
'network': {
'name': network_name,

28
scale/cfg.tenants.yaml Normal file
View File

@ -0,0 +1,28 @@
# KloudBuster tenant and users files
# Usage: Pass to KloudBuster using "-l"
#
# This file contains the a list of tenants and users that will be reused by
# KloudBuster instead of creating them. This is useful when running
# KloudBuster on clouds without admin permissions.
#
# Settings in this file has higher priority than user configs. It determines
# the final count of tenants and useres that KloudBuster will use.
# Sections for listing the tenant and user lists for server cloud.
# Note: For now we only support 1 user per tenant
server:
- name: ts_1
user:
- username: tsu_1
password: tsu_1
- name: ts_2
user:
- username: tsu_2
password: tsu_2
client:
- name: tc_1
user:
- username: tcu_1
password: tcu_1

View File

@ -1,4 +1,5 @@
# Compute host topology file for running KloudBuster
# Usage: Pass to KloudBuster using -t
# The compute host name must be exactly the same as shown from NOVA:
# i.e. "nova hypervisor-list"

View File

@ -13,7 +13,6 @@
# under the License.
#
import socket
import subprocess
import sys
import threading
@ -21,6 +20,16 @@ import time
import redis
# Define the version of the KloudBuster agent.
#
# When VM is up running, the agent will send the READY message to the
# KloudBuster main program, along with its version. The main program
# will check the version to see whether the image meets the minimum
# requirements to run, and stopped with an error if not.
#
# Note: All majors are compatible regardless of minor.
__version__ = '1.0'
class KB_Instance(object):
# Check whether the HTTP Service is up running
@ -96,10 +105,7 @@ class KB_VM_Agent(object):
self.pubsub = self.redis_obj.pubsub(ignore_subscribe_messages=True)
self.hello_thread = None
self.stop_hello = threading.Event()
# Assumption:
# Here we assume the vm_name is the same as the host name (lower case),
# which is true if the VM is spawned by Kloud Buster.
self.vm_name = socket.gethostname().lower()
self.vm_name = user_data['vm_name']
self.orches_chan_name = "kloudbuster_orches"
self.report_chan_name = "kloudbuster_report"
self.last_cmd = None
@ -125,7 +131,7 @@ class KB_VM_Agent(object):
def send_hello(self):
# Sending "hello" message to master node every 2 seconds
while not self.stop_hello.is_set():
self.report('READY', None, None)
self.report('READY', None, __version__)
time.sleep(2)
def exec_command(self, cmd):

View File

@ -24,6 +24,9 @@ import credentials
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class KBConfigParseException(Exception):
pass
# Some hardcoded client side options we do not want users to change
hardcoded_client_cfg = {
# Number of tenants to be created on the cloud
@ -74,6 +77,7 @@ class KBConfig(object):
self.server_cfg = None
self.client_cfg = None
self.topo_cfg = None
self.tenants_list = None
def update_configs(self):
# Initialize the key pair name
@ -109,12 +113,14 @@ class KBConfig(object):
self.get_credentials()
self.get_configs()
self.get_topo_cfg()
self.get_tenants_list()
self.update_configs()
def init_with_rest_api(self, **kwargs):
self.cred_tested = kwargs['cred_tested']
self.cred_testing = kwargs['cred_testing']
self.topo_cfg = kwargs['topo_cfg']
self.tenants_list = kwargs['tenants_list']
self.update_configs()
def get_total_vm_count(self, config):
@ -125,11 +131,11 @@ class KBConfig(object):
def get_credentials(self):
# Retrieve the credentials
self.cred_tested = credentials.Credentials(openrc_file=CONF.tested_rc,
pwd=CONF.passwd_tested,
pwd=CONF.tested_passwd,
no_env=CONF.no_env)
if CONF.testing_rc and CONF.testing_rc != CONF.tested_rc:
self.cred_testing = credentials.Credentials(openrc_file=CONF.testing_rc,
pwd=CONF.passwd_testing,
pwd=CONF.testing_passwd,
no_env=CONF.no_env)
else:
# Use the same openrc file for both cases
@ -143,3 +149,13 @@ class KBConfig(object):
def get_topo_cfg(self):
if CONF.topology:
self.topo_cfg = configure.Configuration.from_file(CONF.topology).configure()
def get_tenants_list(self):
if CONF.tenants_list:
self.tenants_list = configure.Configuration.from_file(CONF.tenants_list).configure()
try:
self.config_scale['number_tenants'] = len(self.tenants_list['server'])
self.config_scale['users_per_tenant'] = len(self.tenants_list['server'][0]['user'])
except Exception as e:
LOG.error('Cannot parse the count of tenant/user from the config file.')
raise KBConfigParseException(e.message)

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from distutils.version import LooseVersion
import time
import log as logging
@ -39,13 +40,15 @@ class KBRunner(object):
Control the testing VMs on the testing cloud
"""
def __init__(self, client_list, config, single_cloud=True):
self.client_dict = dict(zip([x.vm_name.lower() for x in client_list], client_list))
def __init__(self, client_list, config, required_agent_version, single_cloud=True):
self.client_dict = dict(zip([x.vm_name for x in client_list], client_list))
self.config = config
self.single_cloud = single_cloud
self.result = {}
self.host_stats = {}
self.tool_result = {}
self.required_agent_version = str(required_agent_version)
self.agent_version = None
# Redis
self.redis_obj = None
@ -129,6 +132,7 @@ class KBRunner(object):
clist[vm_name].up_flag = True
clist.pop(vm_name)
cnt_succ = cnt_succ + 1
self.agent_version = payload['data']
elif cmd == 'REPORT':
sample_count = sample_count + 1
# Parse the results from HTTP Tools
@ -212,7 +216,13 @@ class KBRunner(object):
try:
LOG.info("Waiting for agents on VMs to come up...")
self.wait_for_vm_up()
if not self.agent_version:
self.agent_version = "0.0"
if (LooseVersion(self.agent_version) < LooseVersion(self.required_agent_version)):
LOG.error("The VM image you are running is too old (%s), the minimum version "
"required is %s.x. Please build the image from latest repository." %
(self.agent_version, self.required_agent_version))
return
if self.single_cloud:
LOG.info("Setting up static route to reach tested cloud...")
self.setup_static_route()
@ -224,7 +234,7 @@ class KBRunner(object):
print "Press enter to start running benchmarking tools..."
raw_input()
LOG.info("Starting HTTP Benchmarking...")
LOG.info("Running HTTP Benchmarking...")
self.run_http_test()
# Call the method in corresponding tools to consolidate results

View File

@ -48,6 +48,7 @@ class ConfigController(object):
# 'testing_rc': '<STRING>', 'passwd_testing': '<STRING>'},
# 'kb_cfg': {<USER_OVERRIDED_CONFIGS>},
# 'topo_cfg': {<TOPOLOGY_CONFIGS>}
# 'tenants_cfg': {<TENANT_AND_USER_LISTS_FOR_REUSING>}
# }
user_config = eval(args)
@ -80,6 +81,12 @@ class ConfigController(object):
topo_cfg = Configuration.from_string(user_config['topo_cfg']).configure()
else:
topo_cfg = None
# Parsing tenants configs from application input
if 'tenants_list' in user_config:
tenants_list = Configuration.from_string(user_config['tenants_list']).configure()
else:
tenants_list = None
except Exception as e:
response.status = 403
response.text = "Error while parsing configurations: %s" % e.message
@ -87,7 +94,8 @@ class ConfigController(object):
self.kb_config.init_with_rest_api(cred_tested=cred_tested,
cred_testing=cred_testing,
topo_cfg=topo_cfg)
topo_cfg=topo_cfg,
tenants_list=tenants_list)
return str(self.kb_config.config_scale)
@expose(generic=True)

View File

@ -37,6 +37,9 @@ import sshutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
__version__ = '1.0.0'
KB_IMAGE_MAJOR_VERSION = 1
class KBVMCreationException(Exception):
pass
@ -62,6 +65,10 @@ def check_and_upload_images(cred, cred_testing, server_img_name, client_img_name
if img['name'] == img_name_dict[kloud]:
img_found = True
break
if img.visibility != 'public' and CONF.tenants_list:
LOG.error("Image must be public when running in reusing mode.")
sys.exit(1)
if not img_found:
# Trying upload images
LOG.info("Image is not found in %s, trying to upload..." % (kloud))
@ -72,17 +79,19 @@ def check_and_upload_images(cred, cred_testing, server_img_name, client_img_name
with open('dib/kloudbuster.qcow2') as fimage:
image = glance_client.images.create(name=img_name_dict[kloud],
disk_format="qcow2",
container_format="bare")
container_format="bare",
visibility='public')
glance_client.images.upload(image['id'], fimage)
return True
class Kloud(object):
def __init__(self, scale_cfg, admin_creds, testing_side=False):
def __init__(self, scale_cfg, admin_creds, reusing_tenants, testing_side=False):
self.cred = admin_creds
self.tenant_list = []
self.testing_side = testing_side
self.scale_cfg = scale_cfg
self.reusing_tenants = reusing_tenants
self.keystone, self.auth_url = create_keystone_client(self.cred)
if testing_side:
self.prefix = 'KBc'
@ -102,21 +111,32 @@ class Kloud(object):
LOG.info('%s Availability Zone: %s' % (self.name, self.placement_az))
def create_resources(self, tenant_quota):
for tenant_count in xrange(self.scale_cfg['number_tenants']):
tenant_name = self.prefix + "-T" + str(tenant_count)
new_tenant = tenant.Tenant(tenant_name, self, tenant_quota)
self.tenant_list.append(new_tenant)
new_tenant.create_resources()
# Create flavors for servers, clients, and kb-proxy nodes
nova_client = self.tenant_list[0].user_list[0].nova_client
flavor_manager = base_compute.Flavor(nova_client)
flavor_dict = self.scale_cfg.flavor
if self.testing_side:
flavor_manager.create_flavor('kb.client', override=True, **flavor_dict)
flavor_manager.create_flavor('kb.proxy', override=True, ram=2048, vcpus=1, disk=20)
if self.reusing_tenants:
for tenant_info in self.reusing_tenants:
tenant_name = tenant_info['name']
user_list = tenant_info['user']
tenant_instance = tenant.Tenant(tenant_name, self, tenant_quota,
reusing_users=user_list)
self.tenant_list.append(tenant_instance)
else:
flavor_manager.create_flavor('kb.server', override=True, **flavor_dict)
for tenant_count in xrange(self.scale_cfg['number_tenants']):
tenant_name = self.prefix + "-T" + str(tenant_count)
tenant_instance = tenant.Tenant(tenant_name, self, tenant_quota)
self.tenant_list.append(tenant_instance)
for tenant_instance in self.tenant_list:
tenant_instance.create_resources()
if not self.reusing_tenants:
# Create flavors for servers, clients, and kb-proxy nodes
nova_client = self.tenant_list[0].user_list[0].nova_client
flavor_manager = base_compute.Flavor(nova_client)
flavor_dict = self.scale_cfg.flavor
if self.testing_side:
flavor_manager.create_flavor('kb.client', override=True, **flavor_dict)
flavor_manager.create_flavor('kb.proxy', override=True, ram=2048, vcpus=1, disk=20)
else:
flavor_manager.create_flavor('kb.server', override=True, **flavor_dict)
def delete_resources(self):
# Deleting flavors created by KloudBuster
@ -126,12 +146,13 @@ class Kloud(object):
# NOVA Client is not yet initialized, so skip cleaning up...
return
flavor_manager = base_compute.Flavor(nova_client)
if self.testing_side:
flavor_manager.delete_flavor('kb.client')
flavor_manager.delete_flavor('kb.proxy')
else:
flavor_manager.delete_flavor('kb.server')
if not self.reusing_tenants:
flavor_manager = base_compute.Flavor(nova_client)
if self.testing_side:
flavor_manager.delete_flavor('kb.client')
flavor_manager.delete_flavor('kb.proxy')
else:
flavor_manager.delete_flavor('kb.server')
for tnt in self.tenant_list:
tnt.delete_resources()
@ -205,22 +226,30 @@ class KloudBuster(object):
4. Networks per router
5. Instances per network
"""
def __init__(self, server_cred, client_cred, server_cfg, client_cfg, topology):
def __init__(self, server_cred, client_cred, server_cfg, client_cfg, topology, tenants_list):
# List of tenant objects to keep track of all tenants
self.tenant_list = []
self.tenant = None
self.tenant_list_testing = []
self.tenant_testing = None
self.server_cfg = server_cfg
self.client_cfg = client_cfg
self.topology = topology
if topology and tenants_list:
self.topology = None
LOG.warn("REUSING MODE: Topology configs will be ignored.")
else:
self.topology = topology
if tenants_list:
self.tenants_list = tenants_list
LOG.warn("REUSING MODE: The quota will not adjust automatically.")
LOG.warn("REUSING MODE: The flavor configs will be ignored, and m1.small is used.")
else:
self.tenants_list = {'server': None, 'client': None}
# TODO(check on same auth_url instead)
if server_cred == client_cred:
self.single_cloud = True
else:
self.single_cloud = False
self.kloud = Kloud(server_cfg, server_cred)
self.testing_kloud = Kloud(client_cfg, client_cred, testing_side=True)
self.kloud = Kloud(server_cfg, server_cred, self.tenants_list['server'])
self.testing_kloud = Kloud(client_cfg, client_cred,
self.tenants_list['client'],
testing_side=True)
self.kb_proxy = None
self.final_result = None
self.server_vm_create_thread = None
@ -256,8 +285,8 @@ class KloudBuster(object):
KBScheduler.setup_vm_placement(role, svr_list, self.topology,
self.kloud.placement_az, "Round-robin")
for ins in svr_list:
ins.user_data['role'] = "Server"
ins.boot_info['flavor_type'] = "kb.server"
ins.user_data['role'] = 'Server'
ins.boot_info['flavor_type'] = 'm1.small' if self.tenants_list else 'kb.server'
ins.boot_info['user_data'] = str(ins.user_data)
elif role == "Client":
client_list = self.testing_kloud.get_all_instances()
@ -266,14 +295,15 @@ class KloudBuster(object):
KBScheduler.setup_vm_placement(role, client_list, self.topology,
self.testing_kloud.placement_az, "Round-robin")
for idx, ins in enumerate(client_list):
ins.user_data['role'] = "Client"
ins.user_data['role'] = 'Client'
ins.user_data['vm_name'] = ins.vm_name
ins.user_data['redis_server'] = self.kb_proxy.fixed_ip
ins.user_data['redis_server_port'] = 6379
ins.user_data['target_subnet_ip'] = svr_list[idx].subnet_ip
ins.user_data['target_shared_interface_ip'] = svr_list[idx].shared_interface_ip
ins.user_data['http_tool'] = ins.config['http_tool']
ins.user_data['http_tool_configs'] = ins.config['http_tool_configs']
ins.boot_info['flavor_type'] = "kb.client"
ins.boot_info['flavor_type'] = 'm1.small' if self.tenants_list else 'kb.client'
ins.boot_info['user_data'] = str(ins.user_data)
def run(self):
@ -297,16 +327,18 @@ class KloudBuster(object):
self.kb_proxy = client_list[-1]
client_list.pop()
self.kb_proxy.vm_name = "KB-PROXY"
self.kb_proxy.vm_name = 'KB-PROXY'
self.kb_proxy.user_data['role'] = 'KB-PROXY'
self.kb_proxy.boot_info['flavor_type'] = 'kb.proxy'
self.kb_proxy.boot_info['flavor_type'] = 'm1.small' if self.tenants_list else 'kb.proxy'
if self.testing_kloud.placement_az:
self.kb_proxy.boot_info['avail_zone'] = "%s:%s" %\
(self.testing_kloud.placement_az, self.topology.clients_rack.split()[0])
self.kb_proxy.boot_info['user_data'] = str(self.kb_proxy.user_data)
self.testing_kloud.create_vm(self.kb_proxy)
kbrunner = KBRunner(client_list, self.client_cfg, self.single_cloud)
kbrunner = KBRunner(client_list, self.client_cfg,
KB_IMAGE_MAJOR_VERSION,
self.single_cloud)
kbrunner.setup_redis(self.kb_proxy.fip_ip)
if self.single_cloud:
@ -499,18 +531,24 @@ if __name__ == '__main__':
short="t",
default=None,
help="Topology files for compute hosts"),
cfg.StrOpt("tenants-list",
short="l",
default=None,
help="Existing tenant and user lists for reusing"),
cfg.StrOpt("tested-rc",
default=None,
help="Tested cloud openrc credentials file"),
cfg.StrOpt("testing-rc",
default=None,
help="Testing cloud openrc credentials file"),
cfg.StrOpt("passwd_tested",
cfg.StrOpt("tested-passwd",
default=None,
secret=True,
help="Tested cloud password"),
cfg.StrOpt("passwd_testing",
cfg.StrOpt("testing-passwd",
default=None,
help="OpenStack password testing cloud"),
secret=True,
help="Testing cloud password"),
cfg.StrOpt("json",
default=None,
help='store results in JSON format file'),
@ -540,7 +578,7 @@ if __name__ == '__main__':
kloudbuster = KloudBuster(
kb_config.cred_tested, kb_config.cred_testing,
kb_config.server_cfg, kb_config.client_cfg,
kb_config.topo_cfg)
kb_config.topo_cfg, kb_config.tenants_list)
kloudbuster.run()
if CONF.json:

View File

@ -26,7 +26,7 @@ class Tenant(object):
2. Uses the User class to perform all user resource creation and deletion
"""
def __init__(self, tenant_name, kloud, tenant_quota):
def __init__(self, tenant_name, kloud, tenant_quota, reusing_users=None):
"""
Holds the tenant name
tenant id and keystone client
@ -39,32 +39,42 @@ class Tenant(object):
self.tenant_object = self._get_tenant()
self.tenant_id = self.tenant_object.id
self.tenant_quota = tenant_quota
self.reusing_users = reusing_users
# Contains a list of user instance objects
self.user_list = []
def _get_tenant(self):
if self.kloud.reusing_tenants:
LOG.info("Using tenant: " + self.tenant_name)
tenant_list = self.kloud.keystone.tenants.list()
for tenant in tenant_list:
if tenant.name == self.tenant_name:
return tenant
raise Exception("Tenant not found")
'''
Create or reuse a tenant object of a given name
'''
try:
LOG.info("Creating tenant: " + self.tenant_name)
self.tenant_object = \
tenant_object = \
self.kloud.keystone.tenants.create(tenant_name=self.tenant_name,
description="KloudBuster tenant",
enabled=True)
return tenant_object
except keystone_exception.Conflict as exc:
# ost likely the entry already exists:
# Conflict: Conflict occurred attempting to store project - Duplicate Entry (HTTP 409)
if exc.http_status != 409:
raise exc
LOG.info("Tenant %s already present, reusing it" % self.tenant_name)
# It is a hassle to find a tenant by name as the only way seems to retrieve
# the list of all tenants which can be very large
tenant_list = self.kloud.keystone.tenants.list()
for tenant in tenant_list:
if tenant.name == self.tenant_name:
return tenant
# It is a hassle to find a tenant by name as the only way seems to retrieve
# the list of all tenants which can be very large
tenant_list = self.kloud.keystone.tenants.list()
for tenant in tenant_list:
if tenant.name == self.tenant_name:
return tenant
# Should never come here
raise Exception("Tenant not found")
@ -73,16 +83,22 @@ class Tenant(object):
Creates all the entities associated with
a user offloads tasks to user class
"""
if self.reusing_users:
for user_info in self.reusing_users:
user_name = user_info['username']
password = user_info['password']
user_instance = users.User(user_name, password, self, None)
self.user_list.append(user_instance)
else:
# Loop over the required number of users and create resources
for user_count in xrange(self.kloud.scale_cfg['users_per_tenant']):
user_name = self.tenant_name + "-U" + str(user_count)
user_instance = users.User(user_name, user_name, self,
self.kloud.scale_cfg['keystone_admin_role'])
# Global list with all user instances
self.user_list.append(user_instance)
# Loop over the required number of users and create resources
for user_count in xrange(self.kloud.scale_cfg['users_per_tenant']):
user_name = self.tenant_name + "-U" + str(user_count)
user_instance = users.User(user_name,
self,
self.kloud.scale_cfg['keystone_admin_role'])
# Global list with all user instances
self.user_list.append(user_instance)
for user_instance in self.user_list:
# Now create the user resources like routers which inturn trigger network and
# vm creation
user_instance.create_resources()
@ -110,5 +126,6 @@ class Tenant(object):
for user in self.user_list:
user.delete_resources()
# Delete the tenant (self)
self.kloud.keystone.tenants.delete(self.tenant_id)
if not self.reusing_users:
# Delete the tenant (self)
self.kloud.keystone.tenants.delete(self.tenant_id)

View File

@ -29,7 +29,7 @@ class User(object):
Creates and deletes N routers based on num of routers
"""
def __init__(self, user_name, tenant, user_role):
def __init__(self, user_name, password, tenant, user_role):
"""
Store all resources
1. Keystone client object
@ -38,14 +38,14 @@ class User(object):
4. router list
"""
self.user_name = user_name
self.password = password
self.tenant = tenant
self.user_id = None
self.router_list = []
# Store the nova, neutron and cinder client
self.nova_client = None
self.neutron_client = None
self.cinder_client = None
self.admin_user = self._get_user()
self.user = self._get_user()
# Each user is associated to 1 key pair at most
self.key_pair = None
self.key_name = None
@ -53,25 +53,35 @@ class User(object):
# Create the user within the given tenant associate
# admin role with user. We need admin role for user
# since we perform VM placement in future
current_role = None
for role in self.tenant.kloud.keystone.roles.list():
if role.name == user_role:
current_role = role
break
self.tenant.kloud.keystone.roles.add_user_role(self.admin_user,
current_role,
tenant.tenant_id)
self.user_id = self.admin_user.id
#
# If running on top of existing tenants/users, skip
# the step for admin role association.
if not self.tenant.kloud.reusing_tenants:
current_role = None
for role in self.tenant.kloud.keystone.roles.list():
if role.name == user_role:
current_role = role
break
self.tenant.kloud.keystone.roles.add_user_role(self.user,
current_role,
tenant.tenant_id)
def _create_user(self):
LOG.info("Creating user: " + self.user_name)
return self.tenant.kloud.keystone.users.create(name=self.user_name,
password=self.user_name,
password=self.password,
email="kloudbuster@localhost",
tenant_id=self.tenant.tenant_id)
def _get_user(self):
if self.tenant.reusing_users:
LOG.info("Using user: " + self.user_name)
users_list = self.tenant.kloud.keystone.users.list()
for user in users_list:
if user.name == self.user_name:
return user
raise Exception('Cannot find stale user:' + self.user_name)
'''
Create a new user or reuse if it already exists (on a different tenant)
delete the user and create a new one
@ -112,8 +122,9 @@ class User(object):
for router in self.router_list:
router.delete_router()
# Finally delete the user
self.tenant.kloud.keystone.users.delete(self.user_id)
if not self.tenant.reusing_users:
# Finally delete the user
self.tenant.kloud.keystone.users.delete(self.user.id)
def update_tenant_quota(self, tenant_quota):
nova_quota = base_compute.NovaQuota(self.nova_client, self.tenant.tenant_id)
@ -152,7 +163,8 @@ class User(object):
self.nova_client = Client(**creden_nova)
self.cinder_client = cinderclient.Client(**creden_nova)
self.update_tenant_quota(self.tenant.tenant_quota)
if not self.tenant.kloud.reusing_tenants:
self.update_tenant_quota(self.tenant.tenant_quota)
config_scale = self.tenant.kloud.scale_cfg