From e391ad79e368af6d48815b8402cbe648ea8c767a Mon Sep 17 00:00:00 2001 From: AJAY KALAMBUR Date: Tue, 17 Mar 2015 13:01:02 -0700 Subject: [PATCH] Preliminary version of Kloudbuster Openstack resources creation Change-Id: I0d1dfe0c8591a551ebf739aa3e77f67a2f68cd67 --- scale/README | 8 ++ scale/base_compute.py | 188 +++++++++++++++++++++++++++ scale/base_network.py | 289 ++++++++++++++++++++++++++++++++++++++++++ scale/cfg.scale.yaml | 31 +++++ scale/credentials.py | 110 ++++++++++++++++ scale/kloudbuster.py | 120 ++++++++++++++++++ scale/tenant.py | 72 +++++++++++ scale/users.py | 108 ++++++++++++++++ 8 files changed, 926 insertions(+) create mode 100644 scale/README create mode 100644 scale/base_compute.py create mode 100644 scale/base_network.py create mode 100644 scale/cfg.scale.yaml create mode 100644 scale/credentials.py create mode 100644 scale/kloudbuster.py create mode 100644 scale/tenant.py create mode 100644 scale/users.py diff --git a/scale/README b/scale/README new file mode 100644 index 0000000..b829e2b --- /dev/null +++ b/scale/README @@ -0,0 +1,8 @@ +This is for VMTP scale testing +This is work in progress for now +The idea is to be able to +1. Create tenant and users to load the cloud +2. Create routers within a User in a tenant +3. Create N networks per router +4. Create N VMs per network +5. Clean up all resources by default (Provide ability to avoid cleanup) diff --git a/scale/base_compute.py b/scale/base_compute.py new file mode 100644 index 0000000..4974f40 --- /dev/null +++ b/scale/base_compute.py @@ -0,0 +1,188 @@ +# Copyright 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +import os +import time +class BaseCompute(object): + """ + The Base class for nova compute resources + 1. Creates virtual machines with specific configs + """ + + + def __init__(self, nova_client, user_name): + self.novaclient = nova_client + self.user_name = user_name + self.instance = None + self.fip = None + self.fip_ip = None + + + # Create a server instance with associated + # security group, keypair with a provided public key + def create_server(self, vmname, image_name, flavor_type, keyname, + nic, sec_group, public_key_file, + avail_zone=None, user_data=None, + config_drive=None, + retry_count=100): + """ + Create a VM instance given following parameters + 1. VM Name + 2. Image Name + 3. Flavor name + 4. key pair name + 5. Security group instance + 6. Optional parameters: availability zone, user data, config drive + """ + + # Get the image id and flavor id from their logical names + image = self.find_image(image_name) + flavor_type = self.find_flavor(flavor_type) + + # Also attach the created security group for the test + instance = self.novaclient.servers.create(name=vmname, + image=image, + flavor=flavor_type, + key_name=keyname, + nics=nic, + availability_zone=avail_zone, + userdata=user_data, + config_drive=config_drive, + security_groups=[sec_group.id]) + flag_exist = self.find_server(vmname, retry_count) + if flag_exist: + self.instance = instance + + + # Returns True if server is present false if not. + # Retry for a few seconds since after VM creation sometimes + # it takes a while to show up + def find_server(self, vmname, retry_count): + for _ in range(retry_count): + servers_list = self.get_server_list() + for server in servers_list: + if server.name == vmname and server.status == "ACTIVE": + return True + time.sleep(2) + print "[%s] VM not found, after %d attempts" % (vmname, retry_count) + return False + + + def get_server_list(self): + servers_list = self.novaclient.servers.list() + return servers_list + + + def delete_server(self): + # First delete the instance + self.novaclient.servers.delete(self.instance) + + + def find_image(self, image_name): + """ + Given a image name return the image id + """ + try: + image = self.novaclient.images.find(name=image_name) + return image + except Exception: + return None + + + def find_flavor(self, flavor_type): + """ + Given a named flavor return the flavor + """ + flavor = self.novaclient.flavors.find(name=flavor_type) + return flavor + + +class SecGroup(object): + + + def __init__(self, novaclient): + self.secgroup = None + self.secgroup_name = None + self.novaclient = novaclient + + + def create_secgroup_with_rules(self, group_name): + group = self.novaclient.security_groups.create(name=group_name, + description="Test sec group") + # Allow ping traffic + self.novaclient.security_group_rules.create(group.id, + ip_protocol="icmp", + from_port=-1, + to_port=-1) + # Allow SSH traffic + self.novaclient.security_group_rules.create(group.id, + ip_protocol="tcp", + from_port=22, + to_port=22) + # Allow HTTP traffic + self.novaclient.security_group_rules.create(group.id, + ip_protocol="tcp", + from_port=80, + to_port=80) + self.secgroup = group + self.secgroup_name = group_name + + + def delete_secgroup(self): + """ + Delete the security group + Sometimes this maybe in use if instance is just deleted + Add a retry mechanism + """ + print "Deleting secgroup %s" % (self.secgroup) + for retry_count in range(10): + try: + self.novaclient.security_groups.delete(self.secgroup) + break + except Exception: + print "Security group %s in use retry count:%d" % (self.secgroup_name, retry_count) + time.sleep(4) + + +class KeyPair(object): + + + def __init__(self, novaclient): + self.keypair = None + self.keypair_name = None + self.novaclient = novaclient + + + def add_public_key(self, name, public_key_file): + """ + Add the KloudBuster public key to openstack + """ + public_key = None + try: + with open(os.path.expanduser(public_key_file)) as pkf: + public_key = pkf.read() + except IOError as exc: + print 'ERROR: Cannot open public key file %s: %s' % \ + (public_key_file, exc) + print 'Adding public key %s' % (name) + keypair = self.novaclient.keypairs.create(name, public_key) + self.keypair = keypair + self.keypair_name = name + + + def remove_public_key(self): + """ + Remove the keypair created by KloudBuster + """ + self.novaclient.keypairs.delete(self.keypair) diff --git a/scale/base_network.py b/scale/base_network.py new file mode 100644 index 0000000..e1e3ac5 --- /dev/null +++ b/scale/base_network.py @@ -0,0 +1,289 @@ +# Copyright 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +import time + +import base_compute +import netaddr +from neutronclient.common.exceptions import NetworkInUseClient + +# Global CIDR shared by all objects of this class +# Enables each network to get a unique CIDR +START_CIDR = "1.0.0.0/16" +cidr = START_CIDR + +def create_floating_ip(neutron_client, ext_net): + """ + Function that creates a floating ip and returns it + Accepts the neutron client and ext_net + Module level function since this is not associated with a + specific network instance + """ + body = { + "floatingip": { + "floating_network_id": ext_net['id'] + } + } + fip = neutron_client.create_floatingip(body) + return fip + +def delete_floating_ip(neutron_client, fip): + """ + Deletes the floating ip + Module level function since this operation + is not associated with a network + """ + neutron_client.delete_floatingip(fip) + +def find_external_network(neutron_client): + """ + Find the external network + and return it + If no external network is found return None + """ + networks = neutron_client.list_networks()['networks'] + for network in networks: + if network['router:external']: + return network + + print "No external network found!!!" + return None + + +class BaseNetwork(object): + """ + The Base class for neutron network operations + 1. Creates networks with 1 subnet inside each network + 2. Increments a global CIDR for all network instances + 3. Deletes all networks on completion + 4. Also interacts with the compute class for instances + """ + + + + def __init__(self, neutron_client, nova_client, user_name): + """ + Store the neutron client + User name for this network + and network object + """ + self.neutron_client = neutron_client + self.nova_client = nova_client + self.user_name = user_name + self.network = None + self.instance_list = [] + self.secgroup_list = [] + self.keypair_list = [] + + def create_compute_resources(self, config_scale): + """ + Creates the compute resources includes the following resources + 1. VM instances + 2. Security groups + 3. Keypairs + """ + # Create the security groups first + for secgroup_count in range(config_scale.secgroups_per_network): + secgroup_instance = base_compute.SecGroup(self.nova_client) + self.secgroup_list.append(secgroup_instance) + secgroup_name = "kloudbuster_secgroup" + "_" + self.network['id'] + str(secgroup_count) + secgroup_instance.create_secgroup_with_rules(secgroup_name) + + # Create the keypair list + for keypair_count in range(config_scale.keypairs_per_network): + keypair_instance = base_compute.KeyPair(self.nova_client) + self.keypair_list.append(keypair_instance) + keypair_name = "kloudbuster_keypair" + "_" + self.network['id'] + str(keypair_count) + keypair_instance.add_public_key(keypair_name, config_scale.public_key_file) + + # Create the required number of VMs + # Create the VMs on specified network, first keypair, first secgroup + external_network = find_external_network(self.neutron_client) + print "Creating Virtual machines for user %s" % (self.user_name) + for instance_count in range(config_scale.vms_per_network): + nova_instance = base_compute.BaseCompute(self.nova_client, self.user_name) + self.instance_list.append(nova_instance) + vm_name = "kloudbuster_vm" + "_" + self.network['id'] + str(instance_count) + nic_used = [{'net-id': self.network['id']}] + nova_instance.create_server(vm_name, config_scale.image_name, + config_scale.flavor_type, + self.keypair_list[0].keypair_name, + nic_used, + self.secgroup_list[0].secgroup, + config_scale.public_key_file, + None, + None, + None) + # Create the floating ip for the instance store it and the ip address in instance object + nova_instance.fip = create_floating_ip(self.neutron_client, external_network) + nova_instance.fip_ip = nova_instance.fip['floatingip']['floating_ip_address'] + # Associate the floating ip with this instance + nova_instance.instance.add_floating_ip(nova_instance.fip_ip) + + def delete_compute_resources(self): + """ + Deletes the compute resources + Security groups,keypairs and instances + """ + # Delete the instances first + for instance in self.instance_list: + instance.delete_server() + delete_floating_ip(self.neutron_client, instance.fip['floatingip']['id']) + + # Delete all security groups + for secgroup_instance in self.secgroup_list: + secgroup_instance.delete_secgroup() + + # Delete all keypairs + for keypair_instance in self.keypair_list: + keypair_instance.remove_public_key() + + + def create_network_and_subnet(self, network_name): + """ + Create a network with 1 subnet inside it + """ + subnet_name = "kloudbuster_subnet" + network_name + body = { + 'network': { + 'name': network_name, + 'admin_state_up': True + } + } + self.network = self.neutron_client.create_network(body)['network'] + + # Now create the subnet inside this network support ipv6 in future + body = { + 'subnet': { + 'name': subnet_name, + 'cidr': self.generate_cidr(), + 'network_id': self.network['id'], + 'enable_dhcp': True, + 'ip_version': 4 + } + } + subnet = self.neutron_client.create_subnet(body)['subnet'] + # add subnet id to the network dict since it has just been added + self.network['subnets'] = [subnet['id']] + + def generate_cidr(self): + """Generate next CIDR for network or subnet, without IP overlapping. + """ + global cidr + cidr = str(netaddr.IPNetwork(cidr).next()) + return cidr + + def delete_network(self): + """ + Deletes the network and associated subnet + retry the deletion since network may be in use + """ + for _ in range(1, 5): + try: + self.neutron_client.delete_network(self.network['id']) + break + except NetworkInUseClient: + time.sleep(1) + + +class Router(object): + """ + Router class to create a new routers + Supports addition and deletion + of network interfaces to router + """ + + def __init__(self, neutron_client, nova_client, user_name): + self.neutron_client = neutron_client + self.nova_client = nova_client + self.router = None + self.user_name = user_name + # Stores the list of networks + self.network_list = [] + + def create_network_resources(self, config_scale): + """ + Creates the required number of networks per router + Also triggers the creation of compute resources inside each + network + """ + for network_count in range(config_scale.networks_per_router): + network_instance = BaseNetwork(self.neutron_client, self.nova_client, self.user_name) + self.network_list.append(network_instance) + # Create the network and subnet + network_name = "kloudbuster_network" + "_" + str(network_count) + network_instance.create_network_and_subnet(network_name) + # Attach the created network to router interface + self.attach_router_interface(network_instance) + # Create the compute resources in the network + network_instance.create_compute_resources(config_scale) + + def delete_network_resources(self): + """ + Delete all network and compute resources + associated with a router + """ + + for network in self.network_list: + # Now delete the compute resources and the network resources + network.delete_compute_resources() + self.remove_router_interface(network) + network.delete_network() + + + def create_router(self, router_name, ext_net): + """ + Create the router and attach it to + external network + """ + body = { + "router": { + "name": router_name, + "admin_state_up": True, + "external_gateway_info": { + "network_id": ext_net['id'] + } + } + } + self.router = self.neutron_client.create_router(body) + return self.router['router'] + + def delete_router(self): + """ + Delete the router + Also delete the networks attached to this router + """ + # Delete the network resources first and than delete the router itself + self.delete_network_resources() + self.neutron_client.delete_router(self.router['router']['id']) + + + def attach_router_interface(self, network_instance): + """ + Attach a network interface to the router + """ + body = { + 'subnet_id': network_instance.network['subnets'][0] + } + self.neutron_client.add_interface_router(self.router['router']['id'], body) + + + def remove_router_interface(self, network_instance): + """ + Remove the network interface from router + """ + body = { + 'subnet_id': network_instance.network['subnets'][0] + } + self.neutron_client.remove_interface_router(self.router['router']['id'], body) diff --git a/scale/cfg.scale.yaml b/scale/cfg.scale.yaml new file mode 100644 index 0000000..fbff2f5 --- /dev/null +++ b/scale/cfg.scale.yaml @@ -0,0 +1,31 @@ +# KloudBuster Default configuration file + +# Number of tenants to be created on the cloud +number_tenants: 1 + +# Number of Users to be created inside the tenant +users_per_tenant: 1 + +# Number of networks to be created within the context of each Router +# Assumes 1 subnet per network +networks_per_router: 2 + +# Number of routers to be created within the context of each User +# For now support only 1 router per user +routers_per_user: 1 + +# Number of VM instances to be created within the context of each User +vms_per_network: 1 + +# Number of security groups per user +secgroups_per_network: 1 + +# Number of keypairs per user +keypairs_per_network: 1 + +#Configs that remain constant +keystone_admin_role: "admin" +cleanup_resources : True +public_key_file : '../ssh/id_rsa.pub' +image_name : 'Ubuntu Server 14.04' +flavor_type: 'm1.small' diff --git a/scale/credentials.py b/scale/credentials.py new file mode 100644 index 0000000..f07287b --- /dev/null +++ b/scale/credentials.py @@ -0,0 +1,110 @@ +# Copyright 2014 Cisco Systems, Inc. All rights reserved. +# +# 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. +# + +# Module for credentials in Openstack +import getpass +import os +import re + + +class Credentials(object): + + def get_credentials(self): + dct = {} + dct['username'] = self.rc_username + dct['password'] = self.rc_password + dct['auth_url'] = self.rc_auth_url + dct['tenant_name'] = self.rc_tenant_name + return dct + + def get_nova_credentials(self): + dct = {} + dct['username'] = self.rc_username + dct['api_key'] = self.rc_password + dct['auth_url'] = self.rc_auth_url + dct['project_id'] = self.rc_tenant_name + return dct + + def get_nova_credentials_v2(self): + dct = self.get_nova_credentials() + dct['version'] = 2 + return dct + + # + # Read a openrc file and take care of the password + # The 2 args are passed from the command line and can be None + # + def __init__(self, openrc_file, pwd, no_env): + self.rc_password = None + self.rc_username = None + self.rc_tenant_name = None + self.rc_auth_url = None + success = True + + if openrc_file: + if os.path.exists(openrc_file): + export_re = re.compile('export OS_([A-Z_]*)="?(.*)') + for line in open(openrc_file): + line = line.strip() + mstr = export_re.match(line) + if mstr: + # get rif of posible trailing double quote + # the first one was removed by the re + name = mstr.group(1) + value = mstr.group(2) + if value.endswith('"'): + value = value[:-1] + # get rid of password assignment + # echo "Please enter your OpenStack Password: " + # read -sr OS_PASSWORD_INPUT + # export OS_PASSWORD=$OS_PASSWORD_INPUT + if value.startswith('$'): + continue + # now match against wanted variable names + if name == 'USERNAME': + self.rc_username = value + elif name == 'AUTH_URL': + self.rc_auth_url = value + elif name == 'TENANT_NAME': + self.rc_tenant_name = value + else: + print 'Error: rc file does not exist %s' % (openrc_file) + success = False + elif not no_env: + # no openrc file passed - we assume the variables have been + # sourced by the calling shell + # just check that they are present + for varname in ['OS_USERNAME', 'OS_AUTH_URL', 'OS_TENANT_NAME']: + if varname not in os.environ: + print 'Warning: %s is missing' % (varname) + success = False + if success: + self.rc_username = os.environ['OS_USERNAME'] + self.rc_auth_url = os.environ['OS_AUTH_URL'] + self.rc_tenant_name = os.environ['OS_TENANT_NAME'] + + # always override with CLI argument if provided + if pwd: + self.rc_password = pwd + # if password not know, check from env variable + elif self.rc_auth_url and not self.rc_password and success: + if 'OS_PASSWORD' in os.environ and not no_env: + self.rc_password = os.environ['OS_PASSWORD'] + else: + # interactively ask for password + self.rc_password = getpass.getpass( + 'Please enter your OpenStack Password: ') + if not self.rc_password: + self.rc_password = "" diff --git a/scale/kloudbuster.py b/scale/kloudbuster.py new file mode 100644 index 0000000..c175d91 --- /dev/null +++ b/scale/kloudbuster.py @@ -0,0 +1,120 @@ +# Copyright 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +import argparse + +import credentials + +import configure +from keystoneclient.v2_0 import client as keystoneclient +import tenant + +class KloudBuster(object): + """ + Creates resources on the cloud for loading up the cloud + 1. Tenants + 2. Users per tenant + 3. Routers per user + 4. Networks per router + 5. Instances per network + """ + def __init__(self): + # List of tenant objects to keep track of all tenants + self.tenant_list = [] + self.tenant = None + + def runner(self): + """ + The runner for KloudBuster Tests + Executes tests serially + Support concurrency in fututure + """ + # Create the keystone client for tenant and user creation operations + creds = cred.get_credentials() + keystone = keystoneclient.Client(**creds) + + # Store the auth url. Pass this around since + # this does not change for all tenants and users + auth_url = creds['auth_url'] + + # The main tenant creation loop which invokes user creations + # Create tenant resources and trigger User resource creations + for tenant_count in xrange(config_scale.number_tenants): + # For now have a serial naming convention for tenants + tenant_name = "kloudbuster_tenant_" + str(tenant_count) + # Create the tenant and append it to global list + print "Creating tenant %s" % (tenant_name) + self.tenant = tenant.Tenant(tenant_name, keystone, auth_url) + self.tenant_list.append(self.tenant) + + # Create the resources associated with the user + # the tenant class creates the user and offloads + # all resource creation inside a user to user class + self.tenant.create_user_elements(config_scale) + + # Clean up all resources by default unless specified otherwise + if config_scale.cleanup_resources: + self.teardown_resources() + + def teardown_resources(self): + """ + Responsible for cleanup + of all resources + """ + # Clean up all tenant resources + # Tenant class leverages the user class to clean up + # all user resources similar to the create resource flow + for tenant_current in self.tenant_list: + print "Deleting tenant resources for tenant %s" % (tenant_current) + tenant_current.delete_tenant_with_users() + + +if __name__ == '__main__': + # The default configuration file for CloudScale + default_cfg_file = "cfg.scale.yaml" + + # Read the command line arguments and parse them + parser = argparse.ArgumentParser(description="Openstack Scale Test Tool") + parser.add_argument('-r', '--rc', dest='rc', + action='store', + help='source OpenStack credentials from rc file', + metavar='') + parser.add_argument('-p', '--password', dest='pwd', + action='store', + help='OpenStack password', + metavar='') + parser.add_argument('-d', '--debug', dest='debug', + default=False, + action='store_true', + help='debug flag (very verbose)') + parser.add_argument('--no-env', dest='no_env', + default=False, + action='store_true', + help='do not read env variables') + + (opts, args) = parser.parse_known_args() + + + # Read the configuration file + config_scale = configure.Configuration.from_file(default_cfg_file).configure() + config_scale.debug = opts.debug + + # Now parse the openrc file and store credentials + cred = credentials.Credentials(opts.rc, opts.pwd, opts.no_env) + + # The KloudBuster class is just a wrapper class + # levarages tenant and user class for resource creations and + # deletion + kloud_buster = KloudBuster() + kloud_buster.runner() diff --git a/scale/tenant.py b/scale/tenant.py new file mode 100644 index 0000000..55de800 --- /dev/null +++ b/scale/tenant.py @@ -0,0 +1,72 @@ +# Copyright 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +import users +class Tenant(object): + """ + Holds the tenant resources + 1. Provides ability to create users in a tenant + 2. Uses the User class to perform all user resource creation and deletion + """ + + def __init__(self, tenant_name, keystone_client, auth_url): + """ + Holds the tenant name + tenant id and keystone client + Also stores the auth_url for constructing credentials + """ + self.tenant_name = tenant_name + self.keystone_client = keystone_client + self.tenant_object = self.keystone_client.tenants.create(tenant_name=tenant_name, + description="Test tenant", + enabled=True) + self.tenant_id = self.tenant_object.id + # Contains a list of user instance objects + self.tenant_user_list = [] + self.auth_url = auth_url + + + def create_user_elements(self, config_scale): + """ + Creates all the entities associated with + a user offloads tasks to user class + """ + + # Loop over the required number of users and create resources + for user_count in xrange(config_scale.users_per_tenant): + user_name = "kloudbuster_user_" + self.tenant_name + "_" + str(user_count) + print "Creating user %s" % (user_name) + user_instance = users.User(user_name, config_scale.keystone_admin_role, + self.tenant_id, self.tenant_name, + self.keystone_client, + self.auth_url) + # Global list with all user instances + self.tenant_user_list.append(user_instance) + + # Now create the user resources like routers which inturn trigger network and + # vm creation + user_instance.create_user_resources(config_scale) + + + def delete_tenant_with_users(self): + """ + Delete all user resources and than + deletes the tenant + """ + # Delete all the users in the tenant along with network and compute elements + for user in self.tenant_user_list: + user.delete_user() + + # Delete the tenant + self.keystone_client.tenants.delete(self.tenant_id) diff --git a/scale/users.py b/scale/users.py new file mode 100644 index 0000000..1cb1061 --- /dev/null +++ b/scale/users.py @@ -0,0 +1,108 @@ +# Copyright 2015 Cisco Systems, Inc. All rights reserved. +# +# 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. + +import base_network +from neutronclient.v2_0 import client as neutronclient +from novaclient.client import Client + +class User(object): + """ + User class that stores router list + Creates and deletes N routers based on num of routers + """ + + def __init__(self, user_name, user_role, tenant_id, tenant_name, keystone_client, + auth_url): + """ + Store all resources + 1. Keystone client object + 2. Tenant and User information + 3. nova and neutron clients + 4. router list + """ + self.user_name = user_name + self.keystone_client = keystone_client + self.tenant_id = tenant_id + self.tenant_name = tenant_name + self.user_id = None + self.router_list = [] + self.auth_url = auth_url + # Store the neutron and nova client + self.neutron = None + self.nova = None + + + # 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 + admin_user = self.keystone_client.users.create(name=user_name, + password=user_name, + email="test.com", + tenant_id=tenant_id) + current_role = None + for role in self.keystone_client.roles.list(): + if role.name == user_role: + current_role = role + break + self.keystone_client.roles.add_user_role(admin_user, current_role, tenant_id) + self.user_id = admin_user.id + + def delete_user(self): + print "Deleting all user resources for user %s" % (self.user_name) + + # Delete all user routers + for router in self.router_list: + router.delete_router() + + # Finally delete the user + self.keystone_client.users.delete(self.user_id) + + def create_user_resources(self, config_scale): + """ + Creates all the User elements associated with a User + 1. Creates the routers + 2. Creates the neutron and nova client objects + """ + + # Create a new neutron client for this User with correct credentials + creden = {} + creden['username'] = self.user_name + creden['password'] = self.user_name + creden['auth_url'] = self.auth_url + creden['tenant_name'] = self.tenant_name + + # Create the neutron client to be used for all operations + self.neutron = neutronclient.Client(**creden) + + # Create a new nova client for this User with correct credentials + creden_nova = {} + creden_nova['username'] = self.user_name + creden_nova['api_key'] = self.user_name + creden_nova['auth_url'] = self.auth_url + creden_nova['project_id'] = self.tenant_name + creden_nova['version'] = 2 + self.nova = Client(**creden_nova) + + # Find the external network that routers need to attach to + external_network = base_network.find_external_network(self.neutron) + # Create the required number of routers and append them to router list + print "Creating routers for user %s" % (self.user_name) + for router_count in range(config_scale.routers_per_user): + router_instance = base_network.Router(self.neutron, self.nova, self.user_name) + self.router_list.append(router_instance) + router_name = "kloudbuster_router_" + "_" + str(router_count) + # Create the router and also attach it to external network + router_instance.create_router(router_name, external_network) + # Now create the network resources inside the router + router_instance.create_network_resources(config_scale)