Preliminary version of Kloudbuster
Openstack resources creation Change-Id: I0d1dfe0c8591a551ebf739aa3e77f67a2f68cd67
This commit is contained in:
parent
e6d50a7ded
commit
e391ad79e3
8
scale/README
Normal file
8
scale/README
Normal file
@ -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)
|
188
scale/base_compute.py
Normal file
188
scale/base_compute.py
Normal file
@ -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)
|
289
scale/base_network.py
Normal file
289
scale/base_network.py
Normal file
@ -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)
|
31
scale/cfg.scale.yaml
Normal file
31
scale/cfg.scale.yaml
Normal file
@ -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'
|
110
scale/credentials.py
Normal file
110
scale/credentials.py
Normal file
@ -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 = ""
|
120
scale/kloudbuster.py
Normal file
120
scale/kloudbuster.py
Normal file
@ -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='<openrc_file>')
|
||||
parser.add_argument('-p', '--password', dest='pwd',
|
||||
action='store',
|
||||
help='OpenStack password',
|
||||
metavar='<password>')
|
||||
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()
|
72
scale/tenant.py
Normal file
72
scale/tenant.py
Normal file
@ -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)
|
108
scale/users.py
Normal file
108
scale/users.py
Normal file
@ -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)
|
Loading…
x
Reference in New Issue
Block a user