From ac37215e4524a39c4b6b40d40f1f7a9082b9513c Mon Sep 17 00:00:00 2001 From: David Lenwell Date: Thu, 10 Jul 2014 11:48:59 -0700 Subject: [PATCH] First Commit --- .testr.conf | 4 + LICENSE | 176 ++++++++++++ README.rst | 12 + refstack-client/refstack-client | 188 ++++++++++++ refstack-client/scripts/prep_cloud.py | 395 ++++++++++++++++++++++++++ requirements.txt | 1 + setup.cfg | 28 ++ setup.py | 24 ++ setup_ubuntu_env.sh | 15 + test-requirements.txt | 6 + tests/__init__.py | 0 tests/unit/__init__.py | 1 + tests/unit/tests.py | 29 ++ tox.ini | 40 +++ 14 files changed, 919 insertions(+) create mode 100644 .testr.conf create mode 100644 LICENSE create mode 100644 README.rst create mode 100755 refstack-client/refstack-client create mode 100644 refstack-client/scripts/prep_cloud.py create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100755 setup.py create mode 100755 setup_ubuntu_env.sh create mode 100644 test-requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/unit/__init__.py create mode 100755 tests/unit/tests.py create mode 100644 tox.ini diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..2109af6 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..83044ff --- /dev/null +++ b/README.rst @@ -0,0 +1,12 @@ +refstack-client +=============== + +refstack-client test is a command line utility that allows you to execute tempest runs a config you specify. When finished running tempest it sends the passed test data back to the refstack api. + +**Usage (ubuntu)** + +We've created an "easy button" for ubuntu. + + $ sh ./setup_ubuntu_env.sh + +## TODO finish this once code is in a working state \ No newline at end of file diff --git a/refstack-client/refstack-client b/refstack-client/refstack-client new file mode 100755 index 0000000..0be8646 --- /dev/null +++ b/refstack-client/refstack-client @@ -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 diff --git a/refstack-client/scripts/prep_cloud.py b/refstack-client/scripts/prep_cloud.py new file mode 100644 index 0000000..5b8d05b --- /dev/null +++ b/refstack-client/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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a7b9adf --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +-e git+https://github.com/openstack/tempest.git \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..cd4bd35 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,28 @@ +[metadata] +name = refstack-client +version = 0.1 +summary = Tempest test wrapper and result uploader 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-client + +[global] +setup-hooks = + pbr.hooks.setup_hook + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..ed2f8fa --- /dev/null +++ b/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/setup_ubuntu_env.sh b/setup_ubuntu_env.sh new file mode 100755 index 0000000..6382f9e --- /dev/null +++ b/setup_ubuntu_env.sh @@ -0,0 +1,15 @@ +# 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 requirements.txt \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..f897c0c --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,6 @@ +pep8==1.4.5 +pyflakes>=0.7.2,<0.7.4 +flake8==2.0 +python-subunit>=0.0.18 +testrepository>=0.0.18 +testtools>=0.9.34 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..97037fd --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +__author__ = 'dlenwell' diff --git a/tests/unit/tests.py b/tests/unit/tests.py new file mode 100755 index 0000000..4b6e09e --- /dev/null +++ b/tests/unit/tests.py @@ -0,0 +1,29 @@ +# +# 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 unittest + + +class TestSequenceFunctions(unittest.TestCase): + + def setUp(self): + pass + + def test_nothing(self): + # make sure the shuffled sequence does not lose any elements + pass + +if __name__ == '__main__': + unittest.main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..0d88f2e --- /dev/null +++ b/tox.ini @@ -0,0 +1,40 @@ +[tox] +envlist = py27,py33,pep8 +minversion = 1.6 +skipsdist = True + +[testenv] +usedevelop = True +install_command = pip install -U {opts} {packages} +setenv = VIRTUAL_ENV={envdir} + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = python setup.py testr --testr-args='{posargs}' +distribute = false + +[testenv:pep8] +commands = flake8 +distribute = false + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = python setup.py testr --coverage --testr-args='{posargs}' + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv:docs] +commands = python setup.py build_sphinx + +[flake8] +# E125 continuation line does not distinguish itself from next logical line +# H404 multi line docstring should start with a summary +ignore = E125,H404 +show-source = true +builtins = _ +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build