From f49642de75c1942df47d95400657fd2a454118b5 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 29 Apr 2014 01:55:03 -0700 Subject: [PATCH] WIP simplified tester TODO's are in the code. Change-Id: I5fc098802ec0482370b70ab42236bf4dc74fe6f4 --- refstack/tools/execute_test/setup.cfg | 1 - refstack/tools/tester/README.rst | 45 ++ .../tester/etc/havana-tempest.conf.sample | 438 ++++++++++++++++++ refstack/tools/tester/havana_requirements.txt | 1 + refstack/tools/tester/scripts/prep_cloud.py | 395 ++++++++++++++++ refstack/tools/tester/setup.cfg | 28 ++ refstack/tools/tester/setup.py | 24 + refstack/tools/tester/setup_ubuntu_env.sh | 21 + refstack/tools/tester/tester | 188 ++++++++ 9 files changed, 1140 insertions(+), 1 deletion(-) create mode 100755 refstack/tools/tester/README.rst create mode 100644 refstack/tools/tester/etc/havana-tempest.conf.sample create mode 100755 refstack/tools/tester/havana_requirements.txt create mode 100644 refstack/tools/tester/scripts/prep_cloud.py create mode 100755 refstack/tools/tester/setup.cfg create mode 100755 refstack/tools/tester/setup.py create mode 100755 refstack/tools/tester/setup_ubuntu_env.sh create mode 100755 refstack/tools/tester/tester diff --git a/refstack/tools/execute_test/setup.cfg b/refstack/tools/execute_test/setup.cfg index c2b62a04..1b50bb4e 100755 --- a/refstack/tools/execute_test/setup.cfg +++ b/refstack/tools/execute_test/setup.cfg @@ -14,7 +14,6 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python - Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3.3 diff --git a/refstack/tools/tester/README.rst b/refstack/tools/tester/README.rst new file mode 100755 index 00000000..bf869c10 --- /dev/null +++ b/refstack/tools/tester/README.rst @@ -0,0 +1,45 @@ +Execute Test +============ + +Execute test is a command line utility that allows you to execute tempest runs with generated configs. When finished running tempest it sends the raw subunit data back to an api. + +**Usage** + +First make sure you have some stuff installed + +`apt-get update` + + +`apt-get install -y git python-pip` + +`apt-get install -y libxml2-dev libxslt-dev lib32z1-dev python2.7-dev libssl-dev libxml2-python` + +`apt-get install -y python-dev libxslt1-dev libsasl2-dev libsqlite3-dev libldap2-dev libffi-dev` + +`pip install --upgrade pip>=1.4` +`pip install virtualenv` + +Then you'll need to setup the tempest env.. from the refstack dir. + +`cd refstack/tools/execute_test/` + +the following command installs stable havana tempest in a virtual env named 'test_runner'. putting tempest in `./test_runner/src/tempest` + +`./setup_env` + +From here you have two options.. + +a. if you are triggering this test from the web gui you can use the `/get-miniconf` method .. + +i.e. `./execute_test --url refstack.org --test-id 235 --tempest-dir ./test_runner/src/tempest --conf-json {section:{option:value}}` + +or + +b. my recomendation which is to source an openstack rc file you download from the cloud you want to test. + +i.e. + +`source openstackrc.sh` + +`./execute_test --env --url refstack.org --test-id 235 --tempest-dir ./test_runner/src/tempest` + diff --git a/refstack/tools/tester/etc/havana-tempest.conf.sample b/refstack/tools/tester/etc/havana-tempest.conf.sample new file mode 100644 index 00000000..115a2b5c --- /dev/null +++ b/refstack/tools/tester/etc/havana-tempest.conf.sample @@ -0,0 +1,438 @@ +[DEFAULT] +#log_config = /opt/stack/tempest/etc/logging.conf.sample + +# disable logging to the stderr +use_stderr = False + +# log file +log_file = tempest.log + +# lock/semaphore base directory +lock_path=/tmp + +default_log_levels=tempest.stress=INFO,amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,eventlet.wsgi.server=WARN + +[identity] +# This section contains configuration options that a variety of Tempest +# test clients use when authenticating with different user/tenant +# combinations + +# The type of endpoint for a Identity service. Unless you have a +# custom Keystone service catalog implementation, you probably want to leave +# this value as "identity" +catalog_type = identity +# Ignore SSL certificate validation failures? Use when in testing +# environments that have self-signed SSL certs. +disable_ssl_certificate_validation = False +# URL for where to find the OpenStack Identity API endpoint (Keystone) +uri = http://127.0.0.1:5000/v2.0/ +# URL for where to find the OpenStack V3 Identity API endpoint (Keystone) +uri_v3 = http://127.0.0.1:5000/v3/ +# The identity region. Also used as the other services' region name unless +# they are set explicitly. +region = RegionOne + +# This should be the username of a user WITHOUT administrative privileges +username = demo +# The above non-administrative user's password +password = secret +# The above non-administrative user's tenant name +tenant_name = demo + +# This should be the username of an alternate user WITHOUT +# administrative privileges +alt_username = alt_demo +# The above non-administrative user's password +alt_password = secret +# The above non-administrative user's tenant name +alt_tenant_name = alt_demo + +# This should be the username of a user WITH administrative privileges +admin_username = admin +# The above administrative user's password +admin_password = secret +# The above administrative user's tenant name +admin_tenant_name = admin + +# The role that is required to administrate keystone. +admin_role = admin + +[compute] +# This section contains configuration options used when executing tests +# against the OpenStack Compute API. + +# Allows test cases to create/destroy tenants and users. This option +# enables isolated test cases and better parallel execution, +# but also requires that OpenStack Identity API admin credentials +# are known. +allow_tenant_isolation = true + +# Allows test cases to create/destroy tenants and users. This option +# enables isolated test cases and better parallel execution, +# but also requires that OpenStack Identity API admin credentials +# are known. +allow_tenant_reuse = true + +# Reference data for tests. The ref and ref_alt should be +# distinct images/flavors. +image_ref = {$IMAGE_ID} +image_ref_alt = {$IMAGE_ID_ALT} +flavor_ref = 1 +flavor_ref_alt = 2 + +# User name used to authenticate to an instance +image_ssh_user = root + +# Password used to authenticate to an instance +image_ssh_password = password + +# User name used to authenticate to an instance using the alternate image +image_alt_ssh_user = root + +# Password used to authenticate to an instance using the alternate image +image_alt_ssh_password = password + +# Number of seconds to wait while looping to check the status of an +# instance that is building. +build_interval = 10 + +# Number of seconds to time out on waiting for an instance +# to build or reach an expected status +build_timeout = 600 + +# Run additional tests that use SSH for instance validation? +# This requires the instances be routable from the host +# executing the tests +run_ssh = false + +# Name of a user used to authenticate to an instance. +ssh_user = cirros + +# Visible fixed network name +fixed_network_name = private + +# Network id used for SSH (public, private, etc) +network_for_ssh = public + +# IP version of the address used for SSH +ip_version_for_ssh = 4 + +# Number of seconds to wait to ping to an instance +ping_timeout = 60 + +# Number of seconds to wait to authenticate to an instance +ssh_timeout = 300 + +# Additinal wait time for clean state, when there is +# no OS-EXT-STS extension availiable +ready_wait = 0 + +# Number of seconds to wait for output from ssh channel +ssh_channel_timeout = 60 + +# Dose the SSH uses Floating IP? +use_floatingip_for_ssh = True + +# The type of endpoint for a Compute API service. Unless you have a +# custom Keystone service catalog implementation, you probably want to leave +# this value as "compute" +catalog_type = compute + +# The name of a region for compute. If empty or commented-out, the value of +# identity.region is used instead. If no such region is found in the service +# catalog, the first found one is used. +#region = RegionOne + +# Does the Compute API support creation of images? +create_image_enabled = true + +# For resize to work with libvirt/kvm, one of the following must be true: +# Single node: allow_resize_to_same_host=True must be set in nova.conf +# Cluster: the 'nova' user must have scp access between cluster nodes +resize_available = true + +# Does the compute API support changing the admin password? +change_password_available=true + +# Run live migration tests (requires 2 hosts) +live_migration_available = false + +# Use block live migration (Otherwise, non-block migration will be +# performed, which requires XenServer pools in case of using XS) +use_block_migration_for_live_migration = false + +# Supports iSCSI block migration - depends on a XAPI supporting +# relax-xsm-sr-check +block_migrate_supports_cinder_iscsi = false + +# When set to false, disk config tests are forced to skip +disk_config_enabled = true + +# When set to false, flavor extra data tests are forced to skip +flavor_extra_enabled = true + +# Expected first device name when a volume is attached to an instance +volume_device_name = vdb + +[compute-admin] +# This should be the username of a user WITH administrative privileges +# If not defined the admin user from the identity section will be used +username = +# The above administrative user's password +password = +# The above administrative user's tenant name +tenant_name = + +[image] +# This section contains configuration options used when executing tests +# against the OpenStack Images API + +# The type of endpoint for an Image API service. Unless you have a +# custom Keystone service catalog implementation, you probably want to leave +# this value as "image" +catalog_type = image + +# The name of a region for image. If empty or commented-out, the value of +# identity.region is used instead. If no such region is found in the service +# catalog, the first found one is used. +#region = RegionOne + +# The version of the OpenStack Images API to use +api_version = 1 + +# HTTP image to use for glance http image testing +http_image = http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz + +[network] +# This section contains configuration options used when executing tests +# against the OpenStack Network API. + +# Version of the Neutron API +api_version = v1.1 +# Catalog type of the Neutron Service +catalog_type = network + +# The name of a region for network. If empty or commented-out, the value of +# identity.region is used instead. If no such region is found in the service +# catalog, the first found one is used. +#region = RegionOne + +# A large private cidr block from which to allocate smaller blocks for +# tenant networks. +tenant_network_cidr = 10.100.0.0/16 + +# The mask bits used to partition the tenant block. +tenant_network_mask_bits = 24 + +# If tenant networks are reachable, connectivity checks will be +# performed directly against addresses on those networks. +tenant_networks_reachable = false + +# Id of the public network that provides external connectivity. +public_network_id = {$PUBLIC_NETWORK_ID} + +# Id of a shared public router that provides external connectivity. +# A shared public router would commonly be used where IP namespaces +# were disabled. If namespaces are enabled, it would be preferable +# for each tenant to have their own router. +public_router_id = {$PUBLIC_ROUTER_ID} + + +[volume] +# This section contains the configuration options used when executing tests +# against the OpenStack Block Storage API service + +# The type of endpoint for a Cinder or Block Storage API service. +# Unless you have a custom Keystone service catalog implementation, you +# probably want to leave this value as "volume" +catalog_type = volume +# The name of a region for volume. If empty or commented-out, the value of +# identity.region is used instead. If no such region is found in the service +# catalog, the first found one is used. +#region = RegionOne +# The disk format to use when copying a volume to image +disk_format = raw +# Number of seconds to wait while looping to check the status of a +# volume that is being made available +build_interval = 10 +# Number of seconds to time out on waiting for a volume +# to be available or reach an expected status +build_timeout = 300 +# Runs Cinder multi-backend tests (requires 2 backends declared in cinder.conf) +# They must have different volume_backend_name (backend1_name and backend2_name +# have to be different) +multi_backend_enabled = false +backend1_name = BACKEND_1 +backend2_name = BACKEND_2 +# Protocol and vendor of volume backend to target when testing volume-types. +# You should update to reflect those exported by configured backend driver. +storage_protocol = iSCSI +vendor_name = Open Source + +[object-storage] +# This section contains configuration options used when executing tests +# against the OpenStack Object Storage API. + +# You can configure the credentials in the compute section + +# The type of endpoint for an Object Storage API service. Unless you have a +# custom Keystone service catalog implementation, you probably want to leave +# this value as "object-store" +catalog_type = object-store + +# The name of a region for object storage. If empty or commented-out, the +# value of identity.region is used instead. If no such region is found in +# the service catalog, the first found one is used. +#region = RegionOne + +# Number of seconds to time on waiting for a container to container +# synchronization complete +container_sync_timeout = 120 +# Number of seconds to wait while looping to check the status of a +# container to container synchronization +container_sync_interval = 5 +# Set to True if the Account Quota middleware is enabled +accounts_quotas_available = True +# Set to True if the Container Quota middleware is enabled +container_quotas_available = True + +# Set operator role for tests that require creating a container +operator_role = Member + +[boto] +# This section contains configuration options used when executing tests +# with boto. + +# EC2 URL +ec2_url = http://localhost:8773/services/Cloud +# S3 URL +s3_url = http://localhost:3333 + +# Use keystone ec2-* command to get those values for your test user and tenant +aws_access = +aws_secret = + +# Image materials for S3 upload +# ALL content of the specified directory will be uploaded to S3 +s3_materials_path = /opt/stack/devstack/files/images/s3-materials/cirros-0.3.1 + +# The manifest.xml files, must be in the s3_materials_path directory +# Subdirectories not allowed! +# The filenames will be used as a Keys in the S3 Buckets + +# ARI Ramdisk manifest. Must be in the above s3_materials_path +ari_manifest = cirros-0.3.1-x86_64-initrd.manifest.xml + +# AMI Machine Image manifest. Must be in the above s3_materials_path +ami_manifest = cirros-0.3.1-x86_64-blank.img.manifest.xml + +# AKI Kernel Image manifest, Must be in the above s3_materials_path +aki_manifest = cirros-0.3.1-x86_64-vmlinuz.manifest.xml + +# Instance type +instance_type = m1.tiny + +# TCP/IP connection timeout +http_socket_timeout = 5 + +# Number of retries actions on connection or 5xx error +num_retries = 1 + +# Status change wait timout +build_timeout = 120 + +# Status change wait interval +build_interval = 1 + +[orchestration] +# The type of endpoint for an Orchestration API service. Unless you have a +# custom Keystone service catalog implementation, you probably want to leave +# this value as "orchestration" +catalog_type = orchestration + +# The name of a region for orchestration. If empty or commented-out, the value +# of identity.region is used instead. If no such region is found in the service +# catalog, the first found one is used. +#region = RegionOne + +# Status change wait interval +build_interval = 1 + +# Status change wait timout. This may vary across environments as some some +# tests spawn full VMs, which could be slow if the test is already in a VM. +build_timeout = 300 + +# Instance type for tests. Needs to be big enough for a +# full OS plus the test workload +instance_type = m1.micro + +# Name of heat-cfntools enabled image to use when launching test instances +# If not specified, tests that spawn instances will not run +#image_ref = ubuntu-vm-heat-cfntools + +# Name of existing keypair to launch servers with. The default is not to specify +# any key, which will generate a keypair for each test class +#keypair_name = heat_key + +[dashboard] +# URL where to find the dashboard home page +dashboard_url = 'http://localhost/' + +# URL where to submit the login form +login_url = 'http://localhost/auth/login/' + +[scenario] +# Directory containing image files +img_dir = /opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec + +# AMI image file name +ami_img_file = cirros-0.3.1-x86_64-blank.img + +# ARI image file name +ari_img_file = cirros-0.3.1-x86_64-initrd + +# AKI image file name +aki_img_file = cirros-0.3.1-x86_64-vmlinuz + +# ssh username for the image file +ssh_user = cirros + +# specifies how many resources to request at once. Used for large operations +# testing." +large_ops_number = 0 + +[cli] +# Enable cli tests +enabled = True +# directory where python client binaries are located +cli_dir = /usr/local/bin +# Number of seconds to wait on a CLI timeout +timeout = 15 + +[service_available] +# Whether or not cinder is expected to be available +cinder = True +# Whether or not neutron is expected to be available +neutron = false +# Whether or not glance is expected to be available +glance = True +# Whether or not swift is expected to be available +swift = True +# Whether or not nova is expected to be available +nova = True +# Whether or not Heat is expected to be available +heat = false +# Whether or not horizon is expected to be available +horizon = True + +[stress] +# Maximum number of instances to create during test +max_instances = 32 +# Time (in seconds) between log file error checks +log_check_interval = 60 +# The default number of threads created while stress test +default_thread_number_per_action=4 + +[debug] +# Enable diagnostic commands +enable = True diff --git a/refstack/tools/tester/havana_requirements.txt b/refstack/tools/tester/havana_requirements.txt new file mode 100755 index 00000000..727f9623 --- /dev/null +++ b/refstack/tools/tester/havana_requirements.txt @@ -0,0 +1 @@ +-e git+https://github.com/openstack/tempest.git@stable/havana#egg=tempest diff --git a/refstack/tools/tester/scripts/prep_cloud.py b/refstack/tools/tester/scripts/prep_cloud.py new file mode 100644 index 00000000..5b8d05be --- /dev/null +++ b/refstack/tools/tester/scripts/prep_cloud.py @@ -0,0 +1,395 @@ +# Copyright 2012 OpenStack Foundation +# 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. +# +# This script aims to configure an initial OpenStack environment with all the +# necessary configurations for tempest's run using nothing but OpenStack's +# native API. +# That includes, creating users, tenants, registering images (cirros), +# configuring neutron and so on. +# +# ASSUMPTION: this script is run by an admin user as it is meant to configure +# the OpenStack environment prior to actual use. + +# Config +import ConfigParser +import os +import tarfile +import urllib2 + +# Default client libs +import glanceclient as glance_client +import keystoneclient.v2_0.client as keystone_client + +# Import OpenStack exceptions +import glanceclient.exc as glance_exception +import keystoneclient.exceptions as keystone_exception + + +TEMPEST_TEMP_DIR = os.getenv("TEMPEST_TEMP_DIR", "/tmp").rstrip('/') +TEMPEST_ROOT_DIR = os.getenv("TEMPEST_ROOT_DIR", os.getenv("HOME")).rstrip('/') + +# Environment variables override defaults +TEMPEST_CONFIG_DIR = os.getenv("TEMPEST_CONFIG_DIR", + "%s%s" % (TEMPEST_ROOT_DIR, "/etc")).rstrip('/') +TEMPEST_CONFIG_FILE = os.getenv("TEMPEST_CONFIG_FILE", + "%s%s" % (TEMPEST_CONFIG_DIR, "/tempest.conf")) +TEMPEST_CONFIG_SAMPLE = os.getenv("TEMPEST_CONFIG_SAMPLE", + "%s%s" % (TEMPEST_CONFIG_DIR, + "/tempest.conf.sample")) +# Image references +IMAGE_DOWNLOAD_CHUNK_SIZE = 8 * 1024 +IMAGE_UEC_SOURCE_URL = os.getenv("IMAGE_UEC_SOURCE_URL", + "http://download.cirros-cloud.net/0.3.1/" + "cirros-0.3.1-x86_64-uec.tar.gz") +TEMPEST_IMAGE_ID = os.getenv('IMAGE_ID') +TEMPEST_IMAGE_ID_ALT = os.getenv('IMAGE_ID_ALT') +IMAGE_STATUS_ACTIVE = 'active' + + +class ClientManager(object): + """ + Manager that provides access to the official python clients for + calling various OpenStack APIs. + """ + def __init__(self): + self.identity_client = None + self.image_client = None + self.network_client = None + self.compute_client = None + self.volume_client = None + + def get_identity_client(self, **kwargs): + """ + Returns the openstack identity python client + :param username: a string representing the username + :param password: a string representing the user's password + :param tenant_name: a string representing the tenant name of the user + :param auth_url: a string representing the auth url of the identity + :param insecure: True if we wish to disable ssl certificate validation, + False otherwise + :returns an instance of openstack identity python client + """ + if not self.identity_client: + self.identity_client = keystone_client.Client(**kwargs) + + return self.identity_client + + def get_image_client(self, version="1", *args, **kwargs): + """ + This method returns OpenStack glance python client + :param version: a string representing the version of the glance client + to use. + :param string endpoint: A user-supplied endpoint URL for the glance + service. + :param string token: Token for authentication. + :param integer timeout: Allows customization of the timeout for client + http requests. (optional) + :return: a Client object representing the glance client + """ + if not self.image_client: + self.image_client = glance_client.Client(version, *args, **kwargs) + + return self.image_client + + +def get_tempest_config(path_to_config): + """ + Gets the tempest configuration file as a ConfigParser object + :param path_to_config: path to the config file + :return: a ConfigParser object representing the tempest configuration file + """ + # get the sample config file from the sample + config = ConfigParser.ConfigParser() + config.readfp(open(path_to_config)) + + return config + + +def update_config_admin_credentials(config, config_section): + """ + Updates the tempest config with the admin credentials + :param config: a ConfigParser object representing the tempest config file + :param config_section: the section name where the admin credentials are + """ + # Check if credentials are present, default uses the config credentials + OS_USERNAME = os.getenv('OS_USERNAME', + config.get(config_section, "admin_username")) + OS_PASSWORD = os.getenv('OS_PASSWORD', + config.get(config_section, "admin_password")) + OS_TENANT_NAME = os.getenv('OS_TENANT_NAME', + config.get(config_section, "admin_tenant_name")) + OS_AUTH_URL = os.getenv('OS_AUTH_URL', config.get(config_section, "uri")) + + if not (OS_AUTH_URL and + OS_USERNAME and + OS_PASSWORD and + OS_TENANT_NAME): + raise Exception("Admin environment variables not found.") + + # TODO(tkammer): Add support for uri_v3 + config_identity_params = {'uri': OS_AUTH_URL, + 'admin_username': OS_USERNAME, + 'admin_password': OS_PASSWORD, + 'admin_tenant_name': OS_TENANT_NAME} + + update_config_section_with_params(config, + config_section, + config_identity_params) + + +def update_config_section_with_params(config, config_section, params): + """ + Updates a given config object with given params + :param config: a ConfigParser object representing the tempest config file + :param config_section: the section we would like to update + :param params: the parameters we wish to update for that section + """ + for option, value in params.items(): + config.set(config_section, option, value) + + +def get_identity_client_kwargs(config, config_section): + """ + Get the required arguments for the identity python client + :param config: a ConfigParser object representing the tempest config file + :param config_section: the section name in the configuration where the + arguments can be found + :return: a dictionary representing the needed arguments for the identity + client + """ + username = config.get(config_section, 'admin_username') + password = config.get(config_section, 'admin_password') + tenant_name = config.get(config_section, 'admin_tenant_name') + auth_url = config.get(config_section, 'uri') + dscv = config.get(config_section, 'disable_ssl_certificate_validation') + kwargs = {'username': username, + 'password': password, + 'tenant_name': tenant_name, + 'auth_url': auth_url, + 'insecure': dscv} + + return kwargs + + +def create_user_with_tenant(identity_client, username, password, tenant_name): + """ + Creates a user using a given identity client + :param identity_client: openstack identity python client + :param username: a string representing the username + :param password: a string representing the user's password + :param tenant_name: a string representing the tenant name of the user + """ + # Try to create the necessary tenant + tenant_id = None + try: + tenant_description = "Tenant for Tempest %s user" % username + tenant = identity_client.tenants.create(tenant_name, + tenant_description) + tenant_id = tenant.id + except keystone_exception.Conflict: + + # if already exist, use existing tenant + tenant_list = identity_client.tenants.list() + for tenant in tenant_list: + if tenant.name == tenant_name: + tenant_id = tenant.id + + # Try to create the user + try: + email = "%s@test.com" % username + identity_client.users.create(name=username, + password=password, + email=email, + tenant_id=tenant_id) + except keystone_exception.Conflict: + + # if already exist, use existing user + pass + + +def create_users_and_tenants(identity_client, + config, + config_section): + """ + Creates the two non admin users and tenants for tempest + :param identity_client: openstack identity python client + :param config: a ConfigParser object representing the tempest config file + :param config_section: the section name of identity in the config + """ + # Get the necessary params from the config file + tenant_name = config.get(config_section, 'tenant_name') + username = config.get(config_section, 'username') + password = config.get(config_section, 'password') + + alt_tenant_name = config.get(config_section, 'alt_tenant_name') + alt_username = config.get(config_section, 'alt_username') + alt_password = config.get(config_section, 'alt_password') + + # Create the necessary users for the test runs + create_user_with_tenant(identity_client, username, password, tenant_name) + create_user_with_tenant(identity_client, alt_username, alt_password, + alt_tenant_name) + + +def get_image_client_kwargs(identity_client, config, config_section): + """ + Get the required arguments for the image python client + :param identity_client: openstack identity python client + :param config: a ConfigParser object representing the tempest config file + :param config_section: the section name of identity in the config + :return: a dictionary representing the needed arguments for the image + client + """ + + token = identity_client.auth_token + endpoint = identity_client.\ + service_catalog.url_for(service_type='image', endpoint_type='publicURL' + ) + dscv = config.get(config_section, 'disable_ssl_certificate_validation') + kwargs = {'endpoint': endpoint, + 'token': token, + 'insecure': dscv} + + return kwargs + + +def images_exist(image_client): + """ + Checks whether the images ID's located in the environment variable are + indeed registered + :param image_client: the openstack python client representing the image + client + """ + exist = True + if not TEMPEST_IMAGE_ID or not TEMPEST_IMAGE_ID_ALT: + exist = False + else: + try: + image_client.images.get(TEMPEST_IMAGE_ID) + image_client.images.get(TEMPEST_IMAGE_ID_ALT) + except glance_exception.HTTPNotFound: + exist = False + + return exist + + +def download_and_register_uec_images(image_client, download_url, + download_folder): + """ + Downloads and registered the UEC AKI/AMI/ARI images + :param image_client: + :param download_url: the url of the uec tar file + :param download_folder: the destination folder we wish to save the file to + """ + basename = os.path.basename(download_url) + path = os.path.join(download_folder, basename) + + request = urllib2.urlopen(download_url) + + # First, download the file + with open(path, "wb") as fp: + while True: + chunk = request.read(IMAGE_DOWNLOAD_CHUNK_SIZE) + if not chunk: + break + + fp.write(chunk) + + # Then extract and register images + tar = tarfile.open(path, "r") + for name in tar.getnames(): + file_obj = tar.extractfile(name) + format = "aki" + + if file_obj.name.endswith(".img"): + format = "ami" + + if file_obj.name.endswith("initrd"): + format = "ari" + + # Register images in image client + image_client.images.create(name=file_obj.name, disk_format=format, + container_format=format, data=file_obj, + is_public="true") + + tar.close() + + +def create_images(image_client, config, config_section, + download_url=IMAGE_UEC_SOURCE_URL, + download_folder=TEMPEST_TEMP_DIR): + """ + Creates images for tempest's use and registers the environment variables + IMAGE_ID and IMAGE_ID_ALT with registered images + :param image_client: OpenStack python image client + :param config: a ConfigParser object representing the tempest config file + :param config_section: the section name where the IMAGE ids are set + :param download_url: the URL from which we should download the UEC tar + :param download_folder: the place where we want to save the download file + """ + if not images_exist(image_client): + # Falls down to the default uec images + download_and_register_uec_images(image_client, download_url, + download_folder) + image_ids = [] + for image in image_client.images.list(): + image_ids.append(image.id) + + os.environ["IMAGE_ID"] = image_ids[0] + os.environ["IMAGE_ID_ALT"] = image_ids[1] + + params = {'image_ref': os.getenv("IMAGE_ID"), + 'image_ref_alt': os.getenv("IMAGE_ID_ALT")} + + update_config_section_with_params(config, config_section, params) + + +def main(): + """ + Main module to control the script + """ + # Check if config file exists or fall to the default sample otherwise + path_to_config = TEMPEST_CONFIG_SAMPLE + + if os.path.isfile(TEMPEST_CONFIG_FILE): + path_to_config = TEMPEST_CONFIG_FILE + + config = get_tempest_config(path_to_config) + update_config_admin_credentials(config, 'identity') + + client_manager = ClientManager() + + # Set the identity related info for tempest + identity_client_kwargs = get_identity_client_kwargs(config, + 'identity') + identity_client = client_manager.get_identity_client( + **identity_client_kwargs) + + # Create the necessary users and tenants for tempest run + create_users_and_tenants(identity_client, config, 'identity') + + # Set the image related info for tempest + image_client_kwargs = get_image_client_kwargs(identity_client, + config, + 'identity') + image_client = client_manager.get_image_client(**image_client_kwargs) + + # Create the necessary users and tenants for tempest run + create_images(image_client, config, 'compute') + + # TODO(tkammer): add network implementation + +if __name__ == "__main__": + main() diff --git a/refstack/tools/tester/setup.cfg b/refstack/tools/tester/setup.cfg new file mode 100755 index 00000000..c2b62a04 --- /dev/null +++ b/refstack/tools/tester/setup.cfg @@ -0,0 +1,28 @@ +[metadata] +name = execute_test +version = 0.1 +summary = Tempest test wrapper for refstack +description-file = + README.rst +author = OpenStack +author-email = fits@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Developers + Intended Audience :: Information Technology + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3.3 + +[files] +packages = + refstack + +[global] +setup-hooks = + pbr.hooks.setup_hook + diff --git a/refstack/tools/tester/setup.py b/refstack/tools/tester/setup.py new file mode 100755 index 00000000..ed2f8fa7 --- /dev/null +++ b/refstack/tools/tester/setup.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# Copyright (c) 2014 Piston Cloud Computing, 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 setuptools + +setuptools.setup( + setup_requires=['pbr'], + version=0.1, + author='David Lenwell', + description='A testing tool used primarily by refstack.', + pbr=True) diff --git a/refstack/tools/tester/setup_ubuntu_env.sh b/refstack/tools/tester/setup_ubuntu_env.sh new file mode 100755 index 00000000..a392cbde --- /dev/null +++ b/refstack/tools/tester/setup_ubuntu_env.sh @@ -0,0 +1,21 @@ +# insure base requirements are installed +sudo apt-get install -y git python-pip wget unzip +sudo apt-get install -y libxml2-dev libxslt-dev lib32z1-dev python2.7-dev libssl-dev +sudo apt-get install -y python-dev libxslt1-dev libsasl2-dev libsqlite3-dev libldap2-dev libffi-dev +sudo apt-get install libxml2-python +sudo pip install virtualenv + +# If we've already created it, source it. If not, start and then source it. +if [ ! -d test_runner ]; then + virtualenv test_runner +fi + +source test_runner/bin/activate + +pip install -r havana_requirements.txt + +# output the base tempestsetup +cp etc/havana-tempest.conf.sample ./tempest.conf + +# generate some bs passwords +sed -i "s/\= secret/\= `date | md5sum | cut -f1 -d' '`/g" ./tempest.conf diff --git a/refstack/tools/tester/tester b/refstack/tools/tester/tester new file mode 100755 index 00000000..0be86467 --- /dev/null +++ b/refstack/tools/tester/tester @@ -0,0 +1,188 @@ +# +# Copyright (c) 2014 Piston Cloud Computing, 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. +# + + +""" +Run tempest and upload results to Refstack. + +This module deals with generating a working tempest conf file from +the environment variables (usually populated by sourcing openrc.sh) + +This is currently just built for Havana tempest. +We're also just starting with nova-net and not neutron support. +TODO: +* generate config + +""" +import argparse +import ConfigParser +import logging +import os +import requests +import subprocess + + +class Test: + log_format = "%(asctime)s %(name)s %(levelname)s %(message)s" + base_config_file = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + 'tempest.conf') + + expected_env = {'OS_AUTH_URL': "uri", + 'OS_TENANT_NAME': "admin_tenant_name", + 'OS_USERNAME': "admin_username", + 'OS_PASSWORD': "admin_password"} + + def __init__(self, args): + '''Prepare a tempest test against a cloud.''' + self.logger = logging.getLogger("execute_test") + self.console_log_handle = logging.StreamHandler() + self.console_log_handle.setFormatter( + logging.Formatter(self.log_format)) + self.logger.addHandler(self.console_log_handle) + + if args.verbose > 1: + self.logger.setLevel(logging.DEBUG) + elif args.verbose == 1: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.CRITICAL) + + # assign local vars to match args + self.url = args.url + self.test_id = args.test_id + self.tempest_dir = args.tempest_dir + self.output_conf = os.path.join(self.tempest_dir, 'tempest.config') + self.tempest_script = os.path.join(self.tempest_dir, 'run_tests.sh') + + # set up object config parser + self.config_parser = ConfigParser.SafeConfigParser() + self.config_parser.read(self.base_config_file) + + #import config + self.import_config_from_env() + + # write the config file + self.config_parser.write(open(self.output_conf, 'w+')) + + #prep cloud based on new config + self.prepare_cloud() + + def import_config_from_env(self): + """create config from environment variables if set otherwise + promt user for values""" + for env_var, key in self.expected_env.items(): + if not os.environ.get(env_var): + value = raw_input("Enter value for %s: " % env_var) + else: + value = os.environ.get(env_var) + + self.config_parser.set('identity', key, value) + + def prepare_cloud(self): + """triggers tempest auto generate code to add users and setup image""" + #TODO davidlenwell: figure out how to import and run code in + # scripts/prep_cloud.py + pass + + def post_test_result(self): + '''Post the combined results back to the server.''' + self.logger.info('Send back the result') + + with open(self.result, 'rb') as result_file: + # clean out any passwords or sensitive data. + result_file = self._scrub_sensitive_data(result_file) + # send the file home. + payload = {'file': result_file, 'test_id': self.test_id} + self._post_to_api('post-result', payload) + + def _scrub_sensitive_data(self, results_file): + '''stub function for cleaning the results before sending them back.''' + return results_file + + def _post_status(self, message): + '''this function posts status back to the api server + if it has a test_id to report it with.''' + if self.test_id: + payload = {'test_id': self.test_id, 'status': message} + self._post_to_api('update-test-status', payload) + + def _post_to_api(self, method, payload): + '''This is a utility function for posting to the api. it is used by + both _post_status and post_test_result''' + try: + url = '%s/v1/%s' % (self.url, method) + requests.post(url, payload) + self.logger.info('posted successfully') + except Exception as e: + #TODO davidlenwell: be more explicit with exceptions. + self.logger.critical('failed to post %s - %s ' % (url,e)) + raise + + def run(self): + '''Execute tempest test against the cloud.''' + try: + cmd = (self.tempest_script, '-C', self.output_conf) + subprocess.check_output(cmd, shell=True) + + self.post_test_result() + + except subprocess.CalledProcessError as e: + self.logger.error('%s failed to complete' % (e)) + + +if __name__ == '__main__': + ''' Generate tempest.conf from a tempest.conf.sample and then run test.''' + parser = argparse.ArgumentParser(description='Starts a tempest test', + formatter_class=argparse. + ArgumentDefaultsHelpFormatter) + + parser.add_argument('-s', '--silent', + action='store_true', + help='rigged for silent running') + + parser.add_argument("-v", "--verbose", + action="count", + help="show verbose output") + + parser.add_argument("--url", + action='store', + required=False, + default='https://api.refstack.org', + type=str, + help="refstack API url \ + retrieve configurations. i.e.:\ + --url https://127.0.0.1:8000") + + parser.add_argument("--test-id", + action='store', + required=False, + dest='test_id', + type=int, + help="refstack test ID i.e.:\ + --test-id 1234 ") + + parser.add_argument("--tempest-dir", + action='store', + required=False, + dest='tempest_dir', + default='test_runner/src/tempest', + help="tempest directory path") + + args = parser.parse_args() + + test = Test(args) + test.run