diff --git a/charmhelpers/contrib/openstack/amulet/utils.py b/charmhelpers/contrib/openstack/amulet/utils.py index d93cff3..6864fd3 100644 --- a/charmhelpers/contrib/openstack/amulet/utils.py +++ b/charmhelpers/contrib/openstack/amulet/utils.py @@ -367,13 +367,36 @@ class OpenStackAmuletUtils(AmuletUtils): project_domain_name=None, project_name=None): """Authenticate with Keystone""" self.log.debug('Authenticating with keystone...') - port = 5000 - if admin_port: - port = 35357 - base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'), - port) - if not api_version or api_version == 2: - ep = base_ep + "/v2.0" + if not api_version: + api_version = 2 + sess, auth = self.get_keystone_session( + keystone_ip=keystone_ip, + username=username, + password=password, + api_version=api_version, + admin_port=admin_port, + user_domain_name=user_domain_name, + domain_name=domain_name, + project_domain_name=project_domain_name, + project_name=project_name + ) + if api_version == 2: + client = keystone_client.Client(session=sess) + else: + client = keystone_client_v3.Client(session=sess) + # This populates the client.service_catalog + client.auth_ref = auth.get_access(sess) + return client + + def get_keystone_session(self, keystone_ip, username, password, + api_version=False, admin_port=False, + user_domain_name=None, domain_name=None, + project_domain_name=None, project_name=None): + """Return a keystone session object""" + ep = self.get_keystone_endpoint(keystone_ip, + api_version=api_version, + admin_port=admin_port) + if api_version == 2: auth = v2.Password( username=username, password=password, @@ -381,12 +404,7 @@ class OpenStackAmuletUtils(AmuletUtils): auth_url=ep ) sess = keystone_session.Session(auth=auth) - client = keystone_client.Client(session=sess) - # This populates the client.service_catalog - client.auth_ref = auth.get_access(sess) - return client else: - ep = base_ep + "/v3" auth = v3.Password( user_domain_name=user_domain_name, username=username, @@ -397,10 +415,57 @@ class OpenStackAmuletUtils(AmuletUtils): auth_url=ep ) sess = keystone_session.Session(auth=auth) - client = keystone_client_v3.Client(session=sess) - # This populates the client.service_catalog - client.auth_ref = auth.get_access(sess) - return client + return (sess, auth) + + def get_keystone_endpoint(self, keystone_ip, api_version=None, + admin_port=False): + """Return keystone endpoint""" + port = 5000 + if admin_port: + port = 35357 + base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'), + port) + if api_version == 2: + ep = base_ep + "/v2.0" + else: + ep = base_ep + "/v3" + return ep + + def get_default_keystone_session(self, keystone_sentry, + openstack_release=None): + """Return a keystone session object and client object assuming standard + default settings + + Example call in amulet tests: + self.keystone_session, self.keystone = u.get_default_keystone_session( + self.keystone_sentry, + openstack_release=self._get_openstack_release()) + + The session can then be used to auth other clients: + neutronclient.Client(session=session) + aodh_client.Client(session=session) + eyc + """ + self.log.debug('Authenticating keystone admin...') + api_version = 2 + client_class = keystone_client.Client + # 11 => xenial_queens + if openstack_release and openstack_release >= 11: + api_version = 3 + client_class = keystone_client_v3.Client + keystone_ip = keystone_sentry.info['public-address'] + session, auth = self.get_keystone_session( + keystone_ip, + api_version=api_version, + username='admin', + password='openstack', + project_name='admin', + user_domain_name='admin_domain', + project_domain_name='admin_domain') + client = client_class(session=session) + # This populates the client.service_catalog + client.auth_ref = auth.get_access(session) + return session, client def authenticate_keystone_admin(self, keystone_sentry, user, password, tenant=None, api_version=None, diff --git a/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py index b753275..e719426 100644 --- a/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -182,7 +182,7 @@ SWIFT_CODENAMES = OrderedDict([ ('pike', ['2.13.0', '2.15.0']), ('queens', - ['2.16.0']), + ['2.16.0', '2.17.0']), ]) # >= Liberty version->codename mapping diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 99eafaf..28065d9 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -32,6 +32,9 @@ u = OpenStackAmuletUtils(DEBUG) class CeilometerBasicDeployment(OpenStackAmuletDeployment): """Amulet tests on a basic ceilometer deployment.""" + no_origin = ['memcached', 'percona-cluster', 'rabbitmq-server', + 'ceph-mon', 'ceph-osd'] + def __init__(self, series, openstack=None, source=None, stable=False): """Deploy the entire test environment.""" super(CeilometerBasicDeployment, self).__init__(series, openstack, @@ -42,7 +45,7 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): self._deploy() u.log.info('Waiting on extended status checks...') - exclude_services = ['mongodb'] + exclude_services = ['mongodb', 'memcached'] self._auto_wait_for_status(exclude_services=exclude_services) self.d.sentry.wait() @@ -62,21 +65,29 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): {'name': 'percona-cluster', 'constraints': {'mem': '3072M'}}, {'name': 'rabbitmq-server'}, {'name': 'keystone'}, - {'name': 'mongodb', - 'location': 'cs:~thedac/{}/mongodb'.format(self.series)}, {'name': 'glance'}, # to satisfy workload status {'name': 'ceilometer-agent'}, {'name': 'nova-compute'} ] - super(CeilometerBasicDeployment, self)._add_services(this_service, - other_services) + if self._get_openstack_release() >= self.xenial_queens: + other_services.extend([ + {'name': 'gnocchi'}, + {'name': 'memcached', 'location': 'cs:memcached'}, + {'name': 'ceph-mon', 'units': 3}, + {'name': 'ceph-osd', 'units': 3}]) + else: + other_services.append({ + 'name': 'mongodb', + 'location': 'cs:~thedac/{}/mongodb'.format(self.series)}) + super(CeilometerBasicDeployment, self)._add_services( + this_service, + other_services, + no_origin=self.no_origin) def _add_relations(self): """Add all of the relations for the services.""" relations = { - 'ceilometer:shared-db': 'mongodb:database', 'ceilometer:amqp': 'rabbitmq-server:amqp', - 'ceilometer:identity-service': 'keystone:identity-service', 'ceilometer:identity-notifications': 'keystone:' 'identity-notifications', 'keystone:shared-db': 'percona-cluster:shared-db', @@ -90,6 +101,25 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): 'glance:amqp': 'rabbitmq-server:amqp', 'nova-compute:image-service': 'glance:image-service' } + if self._get_openstack_release() >= self.xenial_queens: + additional_relations = { + 'ceilometer:identity-credentials': 'keystone:' + 'identity-credentials', + 'ceilometer:identity-notifications': 'keystone:' + 'identity-notifications', + 'ceilometer:metric-service': 'gnocchi:metric-service', + 'ceph-mon:osd': 'ceph-osd:mon', + 'gnocchi:identity-service': 'keystone:identity-service', + 'gnocchi:shared-db': 'percona-cluster:shared-db', + 'gnocchi:storage-ceph': 'ceph-mon:client', + 'gnocchi:coordinator-memcached': 'memcached:cache', + } + else: + additional_relations = { + 'ceilometer:shared-db': 'mongodb:database', + 'ceilometer:identity-service': 'keystone:identity-service'} + relations.update(additional_relations) + print(relations) super(CeilometerBasicDeployment, self)._add_relations(relations) def _configure_services(self): @@ -106,6 +136,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): 'keystone': keystone_config, 'percona-cluster': pxc_config, } + if self._get_openstack_release() >= self.xenial_queens: + configs['ceph-osd'] = {'osd-devices': '/dev/vdb', + 'osd-reformat': 'yes', + 'ephemeral-unmount': '/mnt'} super(CeilometerBasicDeployment, self)._configure_services(configs) def _get_token(self): @@ -119,42 +153,50 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): self.pxc_sentry = self.d.sentry['percona-cluster'][0] self.keystone_sentry = self.d.sentry['keystone'][0] self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0] - self.mongodb_sentry = self.d.sentry['mongodb'][0] self.nova_sentry = self.d.sentry['nova-compute'][0] + if self._get_openstack_release() >= self.xenial_queens: + self.gnocchi_sentry = self.d.sentry['gnocchi'][0] + else: + self.mongodb_sentry = self.d.sentry['mongodb'][0] u.log.debug('openstack release val: {}'.format( self._get_openstack_release())) u.log.debug('openstack release str: {}'.format( self._get_openstack_release_string())) # Authenticate admin with keystone endpoint - self.keystone = u.authenticate_keystone_admin(self.keystone_sentry, - user='admin', - password='openstack', - tenant='admin') + self.keystone_session, self.keystone = u.get_default_keystone_session( + self.keystone_sentry, + openstack_release=self._get_openstack_release()) - # Authenticate admin with ceilometer endpoint - ep = self.keystone.service_catalog.url_for(service_type='metering', - interface='publicURL') - os_token = self.keystone.auth_token self.log.debug('Instantiating ceilometer client...') - self.ceil = ceilo_client.Client(endpoint=ep, token=os_token) + if self._get_openstack_release() >= self.xenial_queens: + self.ceil = ceilo_client.Client(session=self.keystone_session,) + else: + # Authenticate admin with ceilometer endpoint + ep = self.keystone.service_catalog.url_for(service_type='metering', + interface='publicURL') + os_token = self.keystone.auth_token + self.ceil = ceilo_client.Client(endpoint=ep, token=os_token) def test_100_services(self): """Verify the expected services are running on the corresponding service units.""" u.log.debug('Checking system services on units...') - + release = self._get_openstack_release() ceilometer_svcs = [ 'ceilometer-agent-central', - 'ceilometer-collector', 'ceilometer-agent-notification', ] - if self._get_openstack_release() >= self.xenial_ocata: + if release < self.xenial_queens: + ceilometer_svcs.append('ceilometer-collector') + + if (release >= self.xenial_ocata and release < self.xenial_queens): ceilometer_svcs.append('apache2') - else: + + if release < self.xenial_ocata: ceilometer_svcs.append('ceilometer-api') - if self._get_openstack_release() < self.trusty_mitaka: + if release < self.trusty_mitaka: ceilometer_svcs.append('ceilometer-alarm-evaluator') ceilometer_svcs.append('ceilometer-alarm-notifier') @@ -169,6 +211,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): u.log.debug('OK') def test_105_memcache(self): + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping memcache test as memcache server is external' + ' to ceilometer') + return u.validate_memcache(self.ceil_sentry, '/etc/ceilometer/ceilometer.conf', self._get_openstack_release(), @@ -176,6 +222,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_110_service_catalog(self): """Verify that the service catalog endpoint data is valid.""" + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping catalogue checks as ceilometer no longer ' + 'registers endpoints') + return u.log.debug('Checking keystone service catalog data...') endpoint_check = { 'adminURL': u.valid_url, @@ -190,7 +240,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): } actual = self.keystone.service_catalog.get_endpoints() - ret = u.validate_svc_catalog_endpoint_data(expected, actual) + ret = u.validate_svc_catalog_endpoint_data( + expected, + actual, + openstack_release=self._get_openstack_release()) if ret: amulet.raise_status(amulet.FAIL, msg=ret) @@ -198,6 +251,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_112_keystone_api_endpoint(self): """Verify the ceilometer api endpoint data.""" + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping catalogue checks as ceilometer no longer ' + 'registers endpoints') + return u.log.debug('Checking keystone api endpoint data...') endpoints = self.keystone.endpoints.list() u.log.debug(endpoints) @@ -210,8 +267,13 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): 'publicurl': u.valid_url, 'service_id': u.not_null} - ret = u.validate_endpoint_data(endpoints, admin_port, internal_port, - public_port, expected) + ret = u.validate_endpoint_data( + endpoints, + admin_port, + internal_port, + public_port, + expected, + openstack_release=self._get_openstack_release()) if ret: message = 'Keystone endpoint: {}'.format(ret) amulet.raise_status(amulet.FAIL, msg=message) @@ -220,6 +282,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_114_ceilometer_api_endpoint(self): """Verify the ceilometer api endpoint data.""" + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping catalogue checks as ceilometer no longer ' + 'registers endpoints') + return u.log.debug('Checking ceilometer api endpoint data...') endpoints = self.keystone.endpoints.list() u.log.debug(endpoints) @@ -241,6 +307,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_200_ceilometer_identity_relation(self): """Verify the ceilometer to keystone identity-service relation data""" + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping identity-service checks as ceilometer no ' + 'longer has this rerlation') + return u.log.debug('Checking ceilometer to keystone identity-service ' 'relation data...') unit = self.ceil_sentry @@ -268,6 +338,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_201_keystone_ceilometer_identity_relation(self): """Verify the keystone to ceilometer identity-service relation data""" + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping identity-service checks as ceilometer no ' + 'longer has this rerlation') + return u.log.debug('Checking keystone:ceilometer identity relation data...') unit = self.keystone_sentry relation = ['identity-service', 'ceilometer:identity-service'] @@ -304,7 +378,7 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): # May be glance- or keystone- or another endpoint-changed value, so # check that at least one ???-endpoint-changed value exists. unit = self.keystone_sentry - relation_data = unit.relation('identity-service', + relation_data = unit.relation('identity-notifications', 'ceilometer:identity-notifications') expected = '-endpoint-changed' @@ -358,42 +432,6 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): u.log.debug('OK') - def test_205_ceilometer_to_mongodb_relation(self): - """Verify the ceilometer to mongodb relation data""" - u.log.debug('Checking ceilometer:mongodb relation data...') - unit = self.ceil_sentry - relation = ['shared-db', 'mongodb:database'] - expected = { - 'ceilometer_database': 'ceilometer', - 'private-address': u.valid_ip, - } - - ret = u.validate_relation_data(unit, relation, expected) - if ret: - message = u.relation_error('ceilometer shared-db', ret) - amulet.raise_status(amulet.FAIL, msg=message) - - u.log.debug('OK') - - def test_206_mongodb_to_ceilometer_relation(self): - """Verify the mongodb to ceilometer relation data""" - u.log.debug('Checking mongodb:ceilometer relation data...') - unit = self.mongodb_sentry - relation = ['database', 'ceilometer:shared-db'] - expected = { - 'hostname': u.valid_ip, - 'port': '27017', - 'private-address': u.valid_ip, - 'type': 'database', - } - - ret = u.validate_relation_data(unit, relation, expected) - if ret: - message = u.relation_error('mongodb database', ret) - amulet.raise_status(amulet.FAIL, msg=message) - - u.log.debug('OK') - def test_207_ceilometer_ceilometer_agent_relation(self): """Verify the ceilometer to ceilometer-agent relation data""" u.log.debug('Checking ceilometer:ceilometer-agent relation data...') @@ -404,18 +442,22 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): 'rabbitmq_user': 'ceilometer', 'verbose': 'False', 'rabbitmq_host': u.valid_ip, - 'service_ports': "{'ceilometer_api': [8777, 8767]}", 'use_syslog': 'False', 'metering_secret': u.not_null, 'rabbitmq_virtual_host': 'openstack', - 'db_port': '27017', 'private-address': u.valid_ip, - 'db_name': 'ceilometer', - 'db_host': u.valid_ip, 'debug': 'False', 'rabbitmq_password': u.not_null, 'port': '8767' } + if self._get_openstack_release() >= self.xenial_queens: + expected['gnocchi_url'] = u.valid_url + expected['port'] = '8777' + else: + expected['db_port'] = '27017' + expected['db_name'] = 'ceilometer' + expected['db_host'] = u.valid_ip + expected['service_ports'] = "{'ceilometer_api': [8777, 8767]}" ret = u.validate_relation_data(unit, relation, expected) if ret: @@ -442,12 +484,6 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): """Verify the data in the ceilometer config file.""" u.log.debug('Checking ceilometer config file data...') unit = self.ceil_sentry - ks_rel = self.keystone_sentry.relation('identity-service', - 'ceilometer:identity-service') - db_relation = self.mongodb_sentry.relation('database', - 'ceilometer:shared-db') - db_conn = 'mongodb://%s:%s/ceilometer' % (db_relation['hostname'], - db_relation['port']) conf = '/etc/ceilometer/ceilometer.conf' expected = { 'DEFAULT': { @@ -458,10 +494,26 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): 'api': { 'port': '8767', }, - 'database': { - 'connection': db_conn, - }, } + if self._get_openstack_release() >= self.xenial_queens: + relation = self.gnocchi_sentry.relation( + 'metric-service', + 'ceilometer:metric-service') + expected['dispatcher_gnocchi'] = {'url': relation['gnocchi_url']} + ks_rel = self.keystone_sentry.relation( + 'identity-credentials', + 'ceilometer:identity-credentials') + ks_key_prefix = 'credentials' + else: + db_relation = self.mongodb_sentry.relation('database', + 'ceilometer:shared-db') + db_conn = 'mongodb://%s:%s/ceilometer' % (db_relation['hostname'], + db_relation['port']) + expected['database'] = {'connection': db_conn} + ks_rel = self.keystone_sentry.relation( + 'identity-service', + 'ceilometer:identity-service') + ks_key_prefix = 'service' if self._get_openstack_release() < self.trusty_mitaka: auth_uri = '%s://%s:%s/v2.0' % (ks_rel['service_protocol'], @@ -473,9 +525,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): 'os_password': ks_rel['service_password']} else: - auth_uri = '%s://%s:%s' % (ks_rel['service_protocol'], - ks_rel['service_host'], - ks_rel['service_port']) + auth_uri = '%s://%s:%s' % ( + ks_rel['{}_protocol'.format(ks_key_prefix)], + ks_rel['{}_host'.format(ks_key_prefix)], + ks_rel['{}_port'.format(ks_key_prefix)]) # NOTE(dosaboy): os_ prefix is deprecated and no longer used as # of Mitaka. project_domain_name = 'default' @@ -483,25 +536,21 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): if 'api_version' in ks_rel and float(ks_rel['api_version']) > 2: project_domain_name = 'service_domain' user_domain_name = 'service_domain' - expected['service_credentials'] = {'auth_url': auth_uri, - 'project_name': 'services', - 'project_domain_name': - project_domain_name, - 'user_domain_name': - user_domain_name, - 'username': 'ceilometer', - 'password': - ks_rel['service_password']} - expected['keystone_authtoken'] = {'auth_uri': auth_uri, - 'auth_type': 'password', - 'project_domain_name': - project_domain_name, - 'user_domain_name': - user_domain_name, - 'project_name': 'services', - 'username': 'ceilometer', - 'password': - ks_rel['service_password']} + expected['service_credentials'] = { + 'auth_url': auth_uri, + 'project_name': 'services', + 'project_domain_name': project_domain_name, + 'user_domain_name': user_domain_name, + 'username': 'ceilometer', + 'password': ks_rel['{}_password'.format(ks_key_prefix)]} + expected['keystone_authtoken'] = { + 'auth_uri': auth_uri, + 'auth_type': 'password', + 'project_domain_name': project_domain_name, + 'user_domain_name': user_domain_name, + 'project_name': 'services', + 'username': 'ceilometer', + 'password': ks_rel['{}_password'.format(ks_key_prefix)]} if self._get_openstack_release() >= self.xenial_ocata: del expected['api'] @@ -516,6 +565,10 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_400_api_connection(self): """Simple api calls to check service is up and responding""" + if self._get_openstack_release() >= self.xenial_queens: + u.log.debug('Skipping API checks as ceilometer api has been ' + 'removed') + return u.log.debug('Checking api functionality...') assert(self.ceil.samples.list() == []) assert(self.ceil.meters.list() == []) @@ -537,7 +590,13 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): # Services which are expected to restart upon config change, # and corresponding config files affected by the change conf_file = '/etc/ceilometer/ceilometer.conf' - if self._get_openstack_release() >= self.xenial_ocata: + if self._get_openstack_release() >= self.xenial_queens: + services = { + 'ceilometer-polling: AgentManager worker(0)': conf_file, + 'ceilometer-agent-notification: NotificationService worker(0)': + conf_file, + } + elif self._get_openstack_release() >= self.xenial_ocata: services = { 'ceilometer-collector: CollectorService worker(0)': conf_file, 'ceilometer-polling: AgentManager worker(0)': conf_file, diff --git a/tests/charmhelpers/contrib/openstack/amulet/deployment.py b/tests/charmhelpers/contrib/openstack/amulet/deployment.py index 5afbbd8..66beeda 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/tests/charmhelpers/contrib/openstack/amulet/deployment.py @@ -21,6 +21,9 @@ from collections import OrderedDict from charmhelpers.contrib.amulet.deployment import ( AmuletDeployment ) +from charmhelpers.contrib.openstack.amulet.utils import ( + OPENSTACK_RELEASES_PAIRS +) DEBUG = logging.DEBUG ERROR = logging.ERROR @@ -271,11 +274,8 @@ class OpenStackAmuletDeployment(AmuletDeployment): release. """ # Must be ordered by OpenStack release (not by Ubuntu release): - (self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty, - self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton, - self.yakkety_newton, self.xenial_ocata, self.zesty_ocata, - self.xenial_pike, self.artful_pike, self.xenial_queens, - self.bionic_queens,) = range(13) + for i, os_pair in enumerate(OPENSTACK_RELEASES_PAIRS): + setattr(self, os_pair, i) releases = { ('trusty', None): self.trusty_icehouse, diff --git a/tests/charmhelpers/contrib/openstack/amulet/utils.py b/tests/charmhelpers/contrib/openstack/amulet/utils.py index d93cff3..5fdcead 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/utils.py +++ b/tests/charmhelpers/contrib/openstack/amulet/utils.py @@ -50,6 +50,13 @@ ERROR = logging.ERROR NOVA_CLIENT_VERSION = "2" +OPENSTACK_RELEASES_PAIRS = [ + 'trusty_icehouse', 'trusty_kilo', 'trusty_liberty', + 'trusty_mitaka', 'xenial_mitaka', 'xenial_newton', + 'yakkety_newton', 'xenial_ocata', 'zesty_ocata', + 'xenial_pike', 'artful_pike', 'xenial_queens', + 'bionic_queens'] + class OpenStackAmuletUtils(AmuletUtils): """OpenStack amulet utilities. @@ -63,7 +70,34 @@ class OpenStackAmuletUtils(AmuletUtils): super(OpenStackAmuletUtils, self).__init__(log_level) def validate_endpoint_data(self, endpoints, admin_port, internal_port, - public_port, expected): + public_port, expected, openstack_release=None): + """Validate endpoint data. Pick the correct validator based on + OpenStack release. Expected data should be in the v2 format: + { + 'id': id, + 'region': region, + 'adminurl': adminurl, + 'internalurl': internalurl, + 'publicurl': publicurl, + 'service_id': service_id} + + """ + validation_function = self.validate_v2_endpoint_data + xenial_queens = OPENSTACK_RELEASES_PAIRS.index('xenial_queens') + if openstack_release and openstack_release >= xenial_queens: + validation_function = self.validate_v3_endpoint_data + expected = { + 'id': expected['id'], + 'region': expected['region'], + 'region_id': 'RegionOne', + 'url': self.valid_url, + 'interface': self.not_null, + 'service_id': expected['service_id']} + return validation_function(endpoints, admin_port, internal_port, + public_port, expected) + + def validate_v2_endpoint_data(self, endpoints, admin_port, internal_port, + public_port, expected): """Validate endpoint data. Validate actual endpoint data vs expected endpoint data. The ports @@ -141,7 +175,86 @@ class OpenStackAmuletUtils(AmuletUtils): if len(found) != expected_num_eps: return 'Unexpected number of endpoints found' - def validate_svc_catalog_endpoint_data(self, expected, actual): + def convert_svc_catalog_endpoint_data_to_v3(self, ep_data): + """Convert v2 endpoint data into v3. + + { + 'service_name1': [ + { + 'adminURL': adminURL, + 'id': id, + 'region': region. + 'publicURL': publicURL, + 'internalURL': internalURL + }], + 'service_name2': [ + { + 'adminURL': adminURL, + 'id': id, + 'region': region. + 'publicURL': publicURL, + 'internalURL': internalURL + }], + } + """ + self.log.warn("Endpoint ID and Region ID validation is limited to not " + "null checks after v2 to v3 conversion") + for svc in ep_data.keys(): + assert len(ep_data[svc]) == 1, "Unknown data format" + svc_ep_data = ep_data[svc][0] + ep_data[svc] = [ + { + 'url': svc_ep_data['adminURL'], + 'interface': 'admin', + 'region': svc_ep_data['region'], + 'region_id': self.not_null, + 'id': self.not_null}, + { + 'url': svc_ep_data['publicURL'], + 'interface': 'public', + 'region': svc_ep_data['region'], + 'region_id': self.not_null, + 'id': self.not_null}, + { + 'url': svc_ep_data['internalURL'], + 'interface': 'internal', + 'region': svc_ep_data['region'], + 'region_id': self.not_null, + 'id': self.not_null}] + return ep_data + + def validate_svc_catalog_endpoint_data(self, expected, actual, + openstack_release=None): + """Validate service catalog endpoint data. Pick the correct validator + for the OpenStack version. Expected data should be in the v2 format: + { + 'service_name1': [ + { + 'adminURL': adminURL, + 'id': id, + 'region': region. + 'publicURL': publicURL, + 'internalURL': internalURL + }], + 'service_name2': [ + { + 'adminURL': adminURL, + 'id': id, + 'region': region. + 'publicURL': publicURL, + 'internalURL': internalURL + }], + } + + """ + validation_function = self.validate_v2_svc_catalog_endpoint_data + xenial_queens = OPENSTACK_RELEASES_PAIRS.index('xenial_queens') + if openstack_release and openstack_release >= xenial_queens: + validation_function = self.validate_v3_svc_catalog_endpoint_data + expected = self.convert_svc_catalog_endpoint_data_to_v3(expected) + return validation_function(expected, actual) + + def validate_v2_svc_catalog_endpoint_data(self, expected, actual): """Validate service catalog endpoint data. Validate a list of actual service catalog endpoints vs a list of @@ -367,13 +480,36 @@ class OpenStackAmuletUtils(AmuletUtils): project_domain_name=None, project_name=None): """Authenticate with Keystone""" self.log.debug('Authenticating with keystone...') - port = 5000 - if admin_port: - port = 35357 - base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'), - port) - if not api_version or api_version == 2: - ep = base_ep + "/v2.0" + if not api_version: + api_version = 2 + sess, auth = self.get_keystone_session( + keystone_ip=keystone_ip, + username=username, + password=password, + api_version=api_version, + admin_port=admin_port, + user_domain_name=user_domain_name, + domain_name=domain_name, + project_domain_name=project_domain_name, + project_name=project_name + ) + if api_version == 2: + client = keystone_client.Client(session=sess) + else: + client = keystone_client_v3.Client(session=sess) + # This populates the client.service_catalog + client.auth_ref = auth.get_access(sess) + return client + + def get_keystone_session(self, keystone_ip, username, password, + api_version=False, admin_port=False, + user_domain_name=None, domain_name=None, + project_domain_name=None, project_name=None): + """Return a keystone session object""" + ep = self.get_keystone_endpoint(keystone_ip, + api_version=api_version, + admin_port=admin_port) + if api_version == 2: auth = v2.Password( username=username, password=password, @@ -381,12 +517,7 @@ class OpenStackAmuletUtils(AmuletUtils): auth_url=ep ) sess = keystone_session.Session(auth=auth) - client = keystone_client.Client(session=sess) - # This populates the client.service_catalog - client.auth_ref = auth.get_access(sess) - return client else: - ep = base_ep + "/v3" auth = v3.Password( user_domain_name=user_domain_name, username=username, @@ -397,10 +528,57 @@ class OpenStackAmuletUtils(AmuletUtils): auth_url=ep ) sess = keystone_session.Session(auth=auth) - client = keystone_client_v3.Client(session=sess) - # This populates the client.service_catalog - client.auth_ref = auth.get_access(sess) - return client + return (sess, auth) + + def get_keystone_endpoint(self, keystone_ip, api_version=None, + admin_port=False): + """Return keystone endpoint""" + port = 5000 + if admin_port: + port = 35357 + base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'), + port) + if api_version == 2: + ep = base_ep + "/v2.0" + else: + ep = base_ep + "/v3" + return ep + + def get_default_keystone_session(self, keystone_sentry, + openstack_release=None): + """Return a keystone session object and client object assuming standard + default settings + + Example call in amulet tests: + self.keystone_session, self.keystone = u.get_default_keystone_session( + self.keystone_sentry, + openstack_release=self._get_openstack_release()) + + The session can then be used to auth other clients: + neutronclient.Client(session=session) + aodh_client.Client(session=session) + eyc + """ + self.log.debug('Authenticating keystone admin...') + api_version = 2 + client_class = keystone_client.Client + # 11 => xenial_queens + if openstack_release and openstack_release >= 11: + api_version = 3 + client_class = keystone_client_v3.Client + keystone_ip = keystone_sentry.info['public-address'] + session, auth = self.get_keystone_session( + keystone_ip, + api_version=api_version, + username='admin', + password='openstack', + project_name='admin', + user_domain_name='admin_domain', + project_domain_name='admin_domain') + client = client_class(session=session) + # This populates the client.service_catalog + client.auth_ref = auth.get_access(session) + return session, client def authenticate_keystone_admin(self, keystone_sentry, user, password, tenant=None, api_version=None, diff --git a/tests/dev-basic-xenial-queens b/tests/gate-basic-xenial-queens similarity index 100% rename from tests/dev-basic-xenial-queens rename to tests/gate-basic-xenial-queens