From 7605fb49cf01aab4a431fe846ee5dbb86fe90ed9 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 6 May 2016 19:08:45 +0000 Subject: [PATCH] Docstrings and trusty deploy fixes --- actions/__init__.py | 3 ++ actions/run-tempest.py | 36 +++++++++----- hooks/install | 16 ++++++- lib/charm/openstack/tempest.py | 88 +++++++++++++++++++++++++--------- templates/tempest.conf | 6 +-- 5 files changed, 109 insertions(+), 40 deletions(-) create mode 100644 actions/__init__.py diff --git a/actions/__init__.py b/actions/__init__.py new file mode 100644 index 0000000..1504258 --- /dev/null +++ b/actions/__init__.py @@ -0,0 +1,3 @@ +import sys + +sys.path.append('lib') diff --git a/actions/run-tempest.py b/actions/run-tempest.py index 682530b..61c2af4 100755 --- a/actions/run-tempest.py +++ b/actions/run-tempest.py @@ -13,6 +13,7 @@ import charmhelpers.fetch as fetch def setup_git(branch, git_dir, tempest_conf): + """Clone tempest and symlink in rendered tempest.conf""" conf = hookenv.config() if not os.path.exists(git_dir): git_url = conf['tempest-source'] @@ -23,36 +24,47 @@ def setup_git(branch, git_dir, tempest_conf): os.symlink(tempest_conf, conf_symlink) -def execute_tox(run_dir, logfile): +def execute_tox(run_dir, logfile, tox_target): + """Trigger tempest run through tox setting proxies if needed""" env = os.environ.copy() conf = hookenv.config() if conf.get('http-proxy'): env['http_proxy'] = conf['http-proxy'] if conf.get('https-proxy'): env['https_proxy'] = conf['https-proxy'] - cmd = ['tox', '-e', 'smoke'] + cmd = ['tox', '-e', tox_target] f = open(logfile, "w") subprocess.call(cmd, cwd=run_dir, stdout=f, stderr=f) -def run_smoke_test(): - action_args = hookenv.action_get() - branch = action_args['branch'] +def get_tempest_files(branch_name): + """Prepare tempets files and directories + + @return git_dir, logfile, run_dir + """ log_time_str = time.strftime("%Y%m%d%H%M%S", time.gmtime()) - tempest_git_dir = '{}/tempest-{}'.format( + git_dir = '{}/tempest-{}'.format( tempest.TempestCharm.TEMPEST_ROOT, - branch + branch_name ) - tempest_logfile = '{}/run_{}.log'.format( + logfile = '{}/run_{}.log'.format( tempest.TempestCharm.TEMPEST_LOGDIR, log_time_str ) + run_dir = git_dir + '/tempest' + return git_dir, logfile, run_dir + + +def run_test(tox_target): + """Run smoke tests""" + action_args = hookenv.action_get() + branch_name = action_args['branch'] + tempest_git_dir, tempest_logfile, run_dir = get_tempest_files(branch_name) action_info = { 'tempest-logfile': tempest_logfile, } - run_dir = tempest_git_dir + '/tempest' - setup_git(branch, tempest_git_dir, tempest.TempestCharm.TEMPEST_CONF) - execute_tox(run_dir, tempest_logfile) + setup_git(branch_name, tempest_git_dir, tempest.TempestCharm.TEMPEST_CONF) + execute_tox(run_dir, tempest_logfile, tox_target) hookenv.action_set(action_info) @@ -60,4 +72,4 @@ if __name__ == '__main__': # Cloud may have different artifacts (flavors, images etc) since last run # so rerun handlers file to regenerate config. reactive.main() - run_smoke_test() + run_test('smoke') diff --git a/hooks/install b/hooks/install index acc7c12..cf402ff 100755 --- a/hooks/install +++ b/hooks/install @@ -2,8 +2,6 @@ # Wrapper to deal with newer Ubuntu versions that don't have py2 installed # by default. -declare -a DEPS=('libssl-dev' 'libffi-dev' 'apt' 'python3-netaddr' 'python3-netifaces' 'python3-pip' 'python3-yaml' 'python-cinderclient' 'python-glanceclient' 'python-heatclient' 'python-keystoneclient' 'python-neutronclient' 'python-novaclient' 'python-swiftclient' 'python-ceilometerclient' 'openvswitch-test' 'python3-cinderclient' 'python3-glanceclient' 'python3-heatclient' 'python3-keystoneclient' 'python3-neutronclient' 'python3-novaclient' 'python3-swiftclient' 'python3-ceilometerclient', 'tox') - check_and_install() { pkg="${1}" if ! dpkg -s ${pkg} 2>&1 > /dev/null; then @@ -11,6 +9,20 @@ check_and_install() { fi } +if [[ $(lsb_release -sc) -eq "trusty" ]]; then + # Add a random cloud archive for the Openstack python3 clients + add-apt-repository --yes ppa:ubuntu-cloud-archive/mitaka-staging + apt-get update + # The trusty version of tox is too low (tox version is 1.6, required is at least 2.3.1) + # pip install tox to get around this and die a little inside + pip install tox +else + check_and_install 'tox' +fi + +declare -a DEPS=('libssl-dev' 'libffi-dev' 'apt' 'python3-netaddr' 'python3-netifaces' 'python3-pip' 'python3-yaml' 'python-cinderclient' 'python-glanceclient' 'python-heatclient' 'python-keystoneclient' 'python-neutronclient' 'python-novaclient' 'python-swiftclient' 'python-ceilometerclient' 'openvswitch-test' 'python3-cinderclient' 'python3-glanceclient' 'python3-heatclient' 'python3-keystoneclient' 'python3-neutronclient' 'python3-novaclient' 'python3-swiftclient' 'python3-ceilometerclient') + + PYTHON="python" for dep in ${DEPS[@]}; do diff --git a/lib/charm/openstack/tempest.py b/lib/charm/openstack/tempest.py index f4cbdb5..383490c 100644 --- a/lib/charm/openstack/tempest.py +++ b/lib/charm/openstack/tempest.py @@ -12,6 +12,9 @@ tempest_charm = None def get_charm(): + """ Return a new instance of TempestCharm or existing global instance + @returns TempestCharm + """ global tempest_charm if tempest_charm is None: tempest_charm = TempestCharmFactory.charm() @@ -20,28 +23,38 @@ def get_charm(): class TempestAdminAdapter(adapters.OpenStackRelationAdapter): + """Inspect relations and provide properties that can be used when + rendering templates""" + interface_type = "identity-admin" def __init__(self, relation): + """Initialise a keystone client and collect user defined config""" self.kc = None super(TempestAdminAdapter, self).__init__(relation) self.init_keystone_client() self.uconfig = hookenv.config() + @property + def keystone_info(self): + """Collection keystone information from keystone relation""" + return self.relation.credentials() + def init_keystone_client(self): + """Initialise keystone client""" if self.kc: return self.keystone_auth_url = '{}://{}:{}/v2.0'.format( 'http', - self.creds['service_hostname'], - self.creds['service_port'] + self.keystone_info['service_hostname'], + self.keystone_info['service_port'] ) auth = { - 'username': self.creds['service_username'], - 'password': self.creds['service_password'], + 'username': self.keystone_info['service_username'], + 'password': self.keystone_info['service_password'], 'auth_url': self.keystone_auth_url, - 'tenant_name': self.creds['service_tenant_name'], - 'region_name': self.creds['service_region'], + 'tenant_name': self.keystone_info['service_tenant_name'], + 'region_name': self.keystone_info['service_region'], } try: self.kc = keystoneclient.client.Client(**auth) @@ -50,6 +63,10 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): @property def ec2_creds(self): + """Generate EC2 style tokens or return existing EC2 tokens + + @returns {'access_token' token1, 'secret_token': token2} + """ self.init_keystone_client() if not self.kc: return {} @@ -62,6 +79,10 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): @property def image_info(self): + """Return image ids for the user-defined image names + + @returns {'image_id' id1, 'image_alt_id': id2} + """ self.init_keystone_client() glance_endpoint = self.kc.service_catalog.url_for( service_type='image', @@ -81,6 +102,11 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): @property def network_info(self): + """Return public network and router ids for user-defined router and + network names + + @returns {'image_id' id1, 'image_alt_id': id2} + """ self.init_keystone_client() neutron_ep = self.kc.service_catalog.url_for( service_type='network', @@ -110,6 +136,10 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): @property def compute_info(self): + """Return flavor ids for user-defined flavors + + @returns {'flavor_id' id1, 'flavor_alt_id': id2} + """ self.init_keystone_client() nova_ep = self.kc.service_catalog.url_for( service_type='compute', @@ -122,9 +152,9 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): url.netloc.split(':')[0]) try: nova_client = novaclient.client.Client( - self.creds['service_username'], - self.creds['service_password'], - self.creds['service_tenant_name'], + self.keystone_info['service_username'], + self.keystone_info['service_password'], + self.keystone_info['service_tenant_name'], self.keystone_auth_url, ) for flavor in nova_client.flavors.list(): @@ -136,24 +166,26 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): hookenv.log("Nova is not ready, deferring nova query") return compute_info - @property - def creds(self): - return self.relation.credentials() - - @property - def auth_url(self): - bob = self.get_keystone_client() or 'bugger' - return bob def get_present_services(self): + """Query keystone catalogue for a list for registered services + + @returns [svc1, svc2, ...]: List of registered services + """ self.init_keystone_client() services = [svc.name for svc in self.kc.services.list() if svc.enabled] -# if DashboardRelation().get('dashboard_url'): -# services.append('horizon') return services @property def service_info(self): + """Assemble a list of services tempest should tests + + Compare the list of keystone registered services with the services the + user has requested be tested. If in 'auto' mode test all services + registered in keystone. + + @returns [svc1, svc2, ...]: List of services to test + """ service_info = {} tempest_candidates = ['ceilometer', 'cinder', 'glance', 'heat', 'horizon', 'ironic', 'neutron', 'nova', @@ -181,7 +213,7 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter): class TempestAdapters(adapters.OpenStackRelationAdapters): """ - Adapters class for the Designate charm. + Adapters class for the Tempest charm. """ relation_adapters = { 'identity_admin': TempestAdminAdapter, @@ -194,18 +226,26 @@ class TempestAdapters(adapters.OpenStackRelationAdapters): class TempestConfigurationAdapter(adapters.ConfigurationAdapter): - + """ + Manipulate user supplied config as needed + """ def __init__(self): super(TempestConfigurationAdapter, self).__init__() class TempestCharm(charm.OpenStackCharm): + """Directories and files used for running tempest""" TEMPEST_ROOT = '/var/lib/tempest/' TEMPEST_LOGDIR = TEMPEST_ROOT + '/logs' TEMPEST_CONF = TEMPEST_ROOT + '/tempest.conf' + """pip.conf for proxy settings etc""" PIP_CONF = '/root/.pip/pip.conf' + """List of packages charm should install + XXX The install hook is currently installing most packages ahead of + this because modules like keystoneclient are needed at load time + """ packages = [ 'git', 'testrepository', 'subunit', 'python-nose', 'python-lxml', 'python-boto', 'python-junitxml', 'python-subunit', @@ -217,10 +257,14 @@ class TempestCharm(charm.OpenStackCharm): 'python3-keystoneclient', 'python3-neutronclient', 'python3-novaclient', 'python3-swiftclient', 'python3-ceilometerclient', 'openvswitch-common', 'libffi-dev', - 'libssl-dev', 'python-dev', 'python-cffi', 'tox' + 'libssl-dev', 'python-dev', 'python-cffi' ] + """Use the Tempest specific adapters""" adapters_class = TempestAdapters + """Tempest has no running services so no services need restarting on + config file change + """ restart_map = { TEMPEST_CONF: [], PIP_CONF: [], diff --git a/templates/tempest.conf b/templates/tempest.conf index 0534b49..aa896b5 100644 --- a/templates/tempest.conf +++ b/templates/tempest.conf @@ -1,5 +1,3 @@ -# {{ identity_admin.auth_url }} -# {{ identity_admin.creds }} # {{ identity_admin.ec2_creds.access_token }} # {{ identity_admin.image_info }} # {{ identity_admin.image_info.image_id }} @@ -39,8 +37,8 @@ login_url={{ dashboard_url }}/horizon/auth/login/ [data_processing] [debug] [identity] -uri=http://{{ identity_admin.creds.service_hostname }}:5000/v2.0 -uri_v3=http://{{ identity_admin.creds.service_hostname }}:5000/v3 +uri=http://{{ identity_admin.keystone_info.service_hostname }}:5000/v2.0 +uri_v3=http://{{ identity_admin.keystone_info.service_hostname }}:5000/v3 admin_username=admin admin_tenant_name=admin admin_password=openstack