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…
Reference in New Issue
Block a user