Refactor to improve modularity, scalability, OOP
* each service class was moved to a new file * Service and VersionedService were moved from api_discovery to service_base.py * api_discovery.py is removed and methods for discovery were moved to a newly created class Services - class holds methods related to instantiating services, discovering their versions and extensions, configuring them * constants were moved to an independent file - constants.py Change-Id: I00880f4bd30cc4d1609c20aecca820854312b1e7
This commit is contained in:
parent
c1951c379e
commit
0e085cdf85
@ -1,279 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
#
|
||||
# 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 json
|
||||
import logging
|
||||
import re
|
||||
import requests
|
||||
import urllib3
|
||||
import urlparse
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
MULTIPLE_SLASH = re.compile(r'/+')
|
||||
|
||||
|
||||
class ServiceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Service(object):
|
||||
def __init__(self, name, service_url, token, disable_ssl_validation):
|
||||
self.name = name
|
||||
self.service_url = service_url
|
||||
self.headers = {'Accept': 'application/json', 'X-Auth-Token': token}
|
||||
self.disable_ssl_validation = disable_ssl_validation
|
||||
|
||||
def do_get(self, url, top_level=False, top_level_path=""):
|
||||
parts = list(urlparse.urlparse(url))
|
||||
# 2 is the path offset
|
||||
if top_level:
|
||||
parts[2] = '/' + top_level_path
|
||||
|
||||
parts[2] = MULTIPLE_SLASH.sub('/', parts[2])
|
||||
url = urlparse.urlunparse(parts)
|
||||
|
||||
try:
|
||||
if self.disable_ssl_validation:
|
||||
urllib3.disable_warnings()
|
||||
http = urllib3.PoolManager(cert_reqs='CERT_NONE')
|
||||
else:
|
||||
http = urllib3.PoolManager()
|
||||
r = http.request('GET', url, headers=self.headers)
|
||||
except Exception as e:
|
||||
LOG.error("Request on service '%s' with url '%s' failed",
|
||||
(self.name, url))
|
||||
raise e
|
||||
if r.status >= 400:
|
||||
raise ServiceError("Request on service '%s' with url '%s' failed"
|
||||
" with code %d" % (self.name, url, r.status))
|
||||
return r.data
|
||||
|
||||
def get_extensions(self):
|
||||
return []
|
||||
|
||||
def get_versions(self):
|
||||
return []
|
||||
|
||||
|
||||
class VersionedService(Service):
|
||||
def get_versions(self, top_level=True):
|
||||
body = self.do_get(self.service_url, top_level=top_level)
|
||||
body = json.loads(body)
|
||||
return self.deserialize_versions(body)
|
||||
|
||||
def deserialize_versions(self, body):
|
||||
return map(lambda x: x['id'], body['versions'])
|
||||
|
||||
def no_port_cut_url(self):
|
||||
# if there is no port defined, cut the url from version to the end
|
||||
u = urllib3.util.parse_url(self.service_url)
|
||||
url = self.service_url
|
||||
if u.port is None:
|
||||
found = re.findall(r'v\d', url)
|
||||
if len(found) > 0:
|
||||
index = url.index(found[0])
|
||||
url = self.service_url[:index]
|
||||
return (url, u.port is not None)
|
||||
|
||||
|
||||
class ComputeService(VersionedService):
|
||||
def get_extensions(self):
|
||||
body = self.do_get(self.service_url + '/extensions')
|
||||
body = json.loads(body)
|
||||
return map(lambda x: x['alias'], body['extensions'])
|
||||
|
||||
def get_versions(self):
|
||||
url, top_level = self.no_port_cut_url()
|
||||
body = self.do_get(url, top_level=top_level)
|
||||
body = json.loads(body)
|
||||
return self.deserialize_versions(body)
|
||||
|
||||
|
||||
class ImageService(VersionedService):
|
||||
def get_versions(self):
|
||||
return super(ImageService, self).get_versions(top_level=False)
|
||||
|
||||
|
||||
class NetworkService(VersionedService):
|
||||
def get_extensions(self):
|
||||
body = self.do_get(self.service_url + '/v2.0/extensions.json')
|
||||
body = json.loads(body)
|
||||
return map(lambda x: x['alias'], body['extensions'])
|
||||
|
||||
|
||||
class VolumeService(VersionedService):
|
||||
def get_extensions(self):
|
||||
body = self.do_get(self.service_url + '/extensions')
|
||||
body = json.loads(body)
|
||||
return map(lambda x: x['alias'], body['extensions'])
|
||||
|
||||
def get_versions(self):
|
||||
url, top_level = self.no_port_cut_url()
|
||||
body = self.do_get(url, top_level=top_level)
|
||||
body = json.loads(body)
|
||||
return self.deserialize_versions(body)
|
||||
|
||||
|
||||
class IdentityService(VersionedService):
|
||||
def __init__(self, name, service_url, token, disable_ssl_validation):
|
||||
super(VersionedService, self).__init__(
|
||||
name, service_url, token, disable_ssl_validation)
|
||||
version = ''
|
||||
if 'v2' in self.service_url:
|
||||
version = '/v2.0'
|
||||
url_parse = urlparse.urlparse(self.service_url)
|
||||
self.service_url = '{}://{}{}'.format(url_parse.scheme,
|
||||
url_parse.netloc, version)
|
||||
|
||||
def get_extensions(self):
|
||||
if 'v2' in self.service_url:
|
||||
body = self.do_get(self.service_url + '/extensions')
|
||||
body = json.loads(body)
|
||||
return map(lambda x: x['alias'], body['extensions']['values'])
|
||||
# Keystone api changed in v3, the concept of extensions change. Right
|
||||
# now, all the existin extensions are part of keystone core api, so,
|
||||
# there's no longer the /extensions endpoint. The extensions that are
|
||||
# stable, are enabled by default, the ones marked as experimental are
|
||||
# disabled by default. Checking the tempest source, there's no test
|
||||
# pointing to extensions endpoint, so I am very confident that this
|
||||
# will not be an issue. If so, we need to list all the /OS-XYZ
|
||||
# extensions to identify what is enabled or not. This would be a manual
|
||||
# check every time keystone change, add or delete an extension, so I
|
||||
# rather prefer to return empty here for now.
|
||||
return []
|
||||
|
||||
def deserialize_versions(self, body):
|
||||
try:
|
||||
versions = []
|
||||
for v in body['versions']['values']:
|
||||
# TripleO is in transition to v3 only, so the environment
|
||||
# still returns v2 versions even though they're deprecated.
|
||||
# Therefor pick only versions with stable status.
|
||||
if v['status'] == 'stable':
|
||||
versions.append(v['id'])
|
||||
return versions
|
||||
except KeyError:
|
||||
return [body['version']['id']]
|
||||
|
||||
def get_versions(self):
|
||||
return super(IdentityService, self).get_versions(top_level=False)
|
||||
|
||||
|
||||
class ObjectStorageService(Service):
|
||||
def get_extensions(self):
|
||||
body = self.do_get(self.service_url, top_level=True,
|
||||
top_level_path="info")
|
||||
body = json.loads(body)
|
||||
# Remove Swift general information from extensions list
|
||||
body.pop('swift')
|
||||
return body.keys()
|
||||
|
||||
|
||||
service_dict = {'compute': ComputeService,
|
||||
'image': ImageService,
|
||||
'network': NetworkService,
|
||||
'object-store': ObjectStorageService,
|
||||
'volumev3': VolumeService,
|
||||
'identity': IdentityService}
|
||||
|
||||
|
||||
def get_service_class(service_name):
|
||||
return service_dict.get(service_name, Service)
|
||||
|
||||
|
||||
def get_identity_v3_extensions(keystone_v3_url):
|
||||
"""Returns discovered identity v3 extensions
|
||||
|
||||
As keystone V3 uses a JSON Home to store the extensions,
|
||||
this method is kept here just for the sake of functionality, but it
|
||||
implements a different discovery method.
|
||||
|
||||
:param keystone_v3_url: Keystone V3 auth url
|
||||
:return: A list with the discovered extensions
|
||||
"""
|
||||
try:
|
||||
r = requests.get(keystone_v3_url,
|
||||
verify=False,
|
||||
headers={'Accept': 'application/json-home'})
|
||||
except requests.exceptions.RequestException as re:
|
||||
LOG.error("Request on service '%s' with url '%s' failed",
|
||||
'identity', keystone_v3_url)
|
||||
raise re
|
||||
ext_h = 'http://docs.openstack.org/api/openstack-identity/3/ext/'
|
||||
res = [x for x in json.loads(r.content)['resources'].keys()]
|
||||
ext = [ex for ex in res if 'ext' in ex]
|
||||
return list(set([str(e).replace(ext_h, '').split('/')[0] for e in ext]))
|
||||
|
||||
|
||||
def discover(auth_provider, region, object_store_discovery=True,
|
||||
api_version=2, disable_ssl_certificate_validation=True):
|
||||
"""Returns a dict with discovered apis.
|
||||
|
||||
:param auth_provider: An AuthProvider to obtain service urls.
|
||||
:param region: A specific region to use. If the catalog has only one region
|
||||
then that region will be used.
|
||||
:return: A dict with an entry for the type of each discovered service.
|
||||
Each entry has keys for 'extensions' and 'versions'.
|
||||
"""
|
||||
token, auth_data = auth_provider.get_auth()
|
||||
services = {}
|
||||
service_catalog = 'serviceCatalog'
|
||||
public_url = 'publicURL'
|
||||
identity_port = urlparse.urlparse(auth_provider.auth_url).port
|
||||
if identity_port is None:
|
||||
identity_port = ""
|
||||
else:
|
||||
identity_port = ":" + str(identity_port)
|
||||
identity_version = urlparse.urlparse(auth_provider.auth_url).path
|
||||
if api_version == 3:
|
||||
service_catalog = 'catalog'
|
||||
public_url = 'url'
|
||||
|
||||
# FIXME(chandankumar): It is a workaround to filter services whose
|
||||
# endpoints does not exist. Once it is merged. Let's rewrite the whole
|
||||
# stuff.
|
||||
auth_data[service_catalog] = [data for data in auth_data[service_catalog]
|
||||
if data['endpoints']]
|
||||
|
||||
for entry in auth_data[service_catalog]:
|
||||
name = entry['type']
|
||||
services[name] = dict()
|
||||
for _ep in entry['endpoints']:
|
||||
if api_version == 3:
|
||||
if _ep['region'] == region and _ep['interface'] == 'public':
|
||||
ep = _ep
|
||||
break
|
||||
else:
|
||||
if _ep['region'] == region:
|
||||
ep = _ep
|
||||
break
|
||||
else:
|
||||
ep = entry['endpoints'][0]
|
||||
if 'identity' in ep[public_url]:
|
||||
services[name]['url'] = ep[public_url].replace(
|
||||
"/identity", "{0}{1}".format(
|
||||
identity_port, identity_version))
|
||||
else:
|
||||
services[name]['url'] = ep[public_url]
|
||||
service_class = get_service_class(name)
|
||||
service = service_class(name, services[name]['url'], token,
|
||||
disable_ssl_certificate_validation)
|
||||
if name == 'object-store' and not object_store_discovery:
|
||||
services[name]['extensions'] = []
|
||||
elif 'v3' not in ep[public_url]: # is not v3 url
|
||||
services[name]['extensions'] = service.get_extensions()
|
||||
services[name]['versions'] = service.get_versions()
|
||||
return services
|
@ -1,17 +1,17 @@
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
# Copyright 2016, 2018 Red Hat, 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
from tempest.lib import exceptions
|
||||
from tempest.lib.services.compute import flavors_client
|
||||
@ -41,8 +41,10 @@ class ProjectsClient(object):
|
||||
def __init__(self, auth, catalog_type, identity_region, endpoint_type,
|
||||
identity_version, **default_params):
|
||||
self.identity_version = identity_version
|
||||
self.project_class = tenants_client.TenantsClient if \
|
||||
self.identity_version == "v2" else projects_client.ProjectsClient
|
||||
if self.identity_version == "v2":
|
||||
self.project_class = tenants_client.TenantsClient
|
||||
else:
|
||||
self.project_class = projects_client.ProjectsClient
|
||||
self.client = self.project_class(auth, catalog_type, identity_region,
|
||||
endpoint_type, **default_params)
|
||||
|
||||
@ -76,24 +78,19 @@ class ClientManager(object):
|
||||
:param conf: TempestConf object
|
||||
:param creds: Credentials object
|
||||
"""
|
||||
|
||||
self.identity_region = conf.get_defaulted('identity', 'region')
|
||||
self.identity_region = creds.identity_region
|
||||
self.auth_provider = creds.get_auth_provider()
|
||||
|
||||
default_params = {
|
||||
'disable_ssl_certificate_validation':
|
||||
conf.get_defaulted('identity',
|
||||
'disable_ssl_certificate_validation'),
|
||||
'ca_certs': conf.get_defaulted('identity', 'ca_certificates_file')
|
||||
}
|
||||
compute_params = {
|
||||
'service': conf.get_defaulted('compute', 'catalog_type'),
|
||||
'region': self.identity_region,
|
||||
'endpoint_type': conf.get_defaulted('compute', 'endpoint_type')
|
||||
}
|
||||
default_params = self._get_default_params(conf)
|
||||
compute_params = self._get_compute_params(conf)
|
||||
compute_params.update(default_params)
|
||||
|
||||
self.identity = self.get_identity_client(conf, default_params)
|
||||
catalog_type = conf.get_defaulted('identity', 'catalog_type')
|
||||
|
||||
self.identity = self.get_identity_client(
|
||||
creds.identity_version,
|
||||
catalog_type,
|
||||
default_params)
|
||||
|
||||
self.tenants = ProjectsClient(
|
||||
self.auth_provider,
|
||||
@ -105,15 +102,15 @@ class ClientManager(object):
|
||||
|
||||
self.set_roles_client(
|
||||
auth=self.auth_provider,
|
||||
creds=creds,
|
||||
conf=conf,
|
||||
identity_version=creds.identity_version,
|
||||
catalog_type=catalog_type,
|
||||
endpoint_type='publicURL',
|
||||
default_params=default_params)
|
||||
|
||||
self.set_users_client(
|
||||
auth=self.auth_provider,
|
||||
creds=creds,
|
||||
conf=conf,
|
||||
identity_version=creds.identity_version,
|
||||
catalog_type=catalog_type,
|
||||
endpoint_type='publicURL',
|
||||
default_params=default_params)
|
||||
|
||||
@ -134,7 +131,7 @@ class ClientManager(object):
|
||||
self.identity_region,
|
||||
**default_params)
|
||||
|
||||
self.volume_service = services_client.ServicesClient(
|
||||
self.volume_client = services_client.ServicesClient(
|
||||
self.auth_provider,
|
||||
conf.get_defaulted('volume', 'catalog_type'),
|
||||
self.identity_region,
|
||||
@ -167,65 +164,99 @@ class ClientManager(object):
|
||||
tenant = self.tenants.get_project_by_name(creds.tenant_name)
|
||||
conf.set('identity', 'admin_tenant_id', tenant['id'])
|
||||
|
||||
def get_identity_client(self, conf, default_params):
|
||||
def _get_default_params(self, conf):
|
||||
default_params = {
|
||||
'disable_ssl_certificate_validation':
|
||||
conf.get_defaulted('identity',
|
||||
'disable_ssl_certificate_validation'),
|
||||
'ca_certs': conf.get_defaulted('identity', 'ca_certificates_file')
|
||||
}
|
||||
return default_params
|
||||
|
||||
def _get_compute_params(self, conf):
|
||||
compute_params = {
|
||||
'service': conf.get_defaulted('compute', 'catalog_type'),
|
||||
'region': self.identity_region,
|
||||
'endpoint_type': conf.get_defaulted('compute', 'endpoint_type')
|
||||
}
|
||||
return compute_params
|
||||
|
||||
def get_identity_client(self, identity_version, catalog_type,
|
||||
default_params):
|
||||
"""Obtain identity client.
|
||||
|
||||
:type conf: TempestConf object
|
||||
:type identity_version: string
|
||||
:type default_params: dict
|
||||
"""
|
||||
if "v2.0" in conf.get("identity", "uri"):
|
||||
return identity_client.IdentityClient(
|
||||
if "v3" in identity_version:
|
||||
return identity_v3_client.IdentityClient(
|
||||
self.auth_provider,
|
||||
conf.get_defaulted('identity', 'catalog_type'),
|
||||
catalog_type,
|
||||
self.identity_region, endpoint_type='publicURL',
|
||||
**default_params)
|
||||
else:
|
||||
return identity_v3_client.IdentityClient(
|
||||
return identity_client.IdentityClient(
|
||||
self.auth_provider,
|
||||
conf.get_defaulted('identity', 'catalog_type'),
|
||||
catalog_type,
|
||||
self.identity_region, endpoint_type='publicURL',
|
||||
**default_params)
|
||||
|
||||
def set_users_client(self, auth, creds, conf, endpoint_type,
|
||||
default_params):
|
||||
def get_service_client(self, service_name):
|
||||
"""Returns name of the service's client.
|
||||
|
||||
:type service_name: string
|
||||
:rtype: client object or None when the client doesn't exist
|
||||
"""
|
||||
if service_name == "image":
|
||||
return self.images
|
||||
elif service_name == "network":
|
||||
# return whole ClientManager object because NetworkService
|
||||
# currently needs to have an access to get_neutron/nova_client
|
||||
# methods which are chosen according to neutron presence
|
||||
return self
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_users_client(self, auth, identity_version, catalog_type,
|
||||
endpoint_type, default_params):
|
||||
"""Sets users client.
|
||||
|
||||
:param auth: auth provider
|
||||
:type auth: auth.KeystoneV2AuthProvider (or V3)
|
||||
:type creds: Credentials object
|
||||
:type conf: TempestConf object
|
||||
:type identity_version: string
|
||||
:type catalog_type: string
|
||||
:type endpoint_type: string
|
||||
:type default_params: dict
|
||||
"""
|
||||
users_class = users_client.UsersClient
|
||||
if "v3" in creds.identity_version:
|
||||
if "v3" in identity_version:
|
||||
users_class = users_v3_client.UsersClient
|
||||
|
||||
self.users = users_class(
|
||||
auth,
|
||||
conf.get_defaulted('identity', 'catalog_type'),
|
||||
catalog_type,
|
||||
self.identity_region,
|
||||
endpoint_type=endpoint_type,
|
||||
**default_params)
|
||||
|
||||
def set_roles_client(self, auth, creds, conf, endpoint_type,
|
||||
default_params):
|
||||
def set_roles_client(self, auth, identity_version, catalog_type,
|
||||
endpoint_type, default_params):
|
||||
"""Sets roles client.
|
||||
|
||||
:param auth: auth provider
|
||||
:type auth: auth.KeystoneV2AuthProvider (or V3)
|
||||
:type creds: Credentials object
|
||||
:type conf: TempestConf object
|
||||
:type identity_version: string
|
||||
:type catalog_type: string
|
||||
:type endpoint_type: string
|
||||
:type default_params: dict
|
||||
"""
|
||||
roles_class = roles_client.RolesClient
|
||||
if "v3" in creds.identity_version:
|
||||
if "v3" in identity_version:
|
||||
roles_class = roles_v3_client.RolesClient
|
||||
|
||||
self.roles = roles_class(
|
||||
auth,
|
||||
conf.get_defaulted('identity', 'catalog_type'),
|
||||
catalog_type,
|
||||
self.identity_region,
|
||||
endpoint_type=endpoint_type,
|
||||
**default_params)
|
||||
|
67
config_tempest/constants.py
Normal file
67
config_tempest/constants.py
Normal file
@ -0,0 +1,67 @@
|
||||
# Copyright 2013, 2016, 2018 Red Hat, 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 logging
|
||||
import os
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# Get the current tempest workspace path
|
||||
TEMPEST_WORKSPACE = os.getcwd()
|
||||
|
||||
DEFAULTS_FILE = os.path.join(TEMPEST_WORKSPACE, "etc",
|
||||
"default-overrides.conf")
|
||||
DEFAULT_IMAGE = ("http://download.cirros-cloud.net/0.3.5/"
|
||||
"cirros-0.3.5-x86_64-disk.img")
|
||||
DEFAULT_IMAGE_FORMAT = 'qcow2'
|
||||
|
||||
# services and their codenames
|
||||
SERVICE_NAMES = {
|
||||
'baremetal': 'ironic',
|
||||
'compute': 'nova',
|
||||
'database': 'trove',
|
||||
'data-processing': 'sahara',
|
||||
'image': 'glance',
|
||||
'network': 'neutron',
|
||||
'object-store': 'swift',
|
||||
'orchestration': 'heat',
|
||||
'share': 'manila',
|
||||
'telemetry': 'ceilometer',
|
||||
'volume': 'cinder',
|
||||
'messaging': 'zaqar',
|
||||
'metric': 'gnocchi',
|
||||
'event': 'panko',
|
||||
}
|
||||
|
||||
# what API versions could the service have and should be enabled/disabled
|
||||
# depending on whether they get discovered as supported. Services with only one
|
||||
# version don't need to be here, neither do service versions that are not
|
||||
# configurable in tempest.conf
|
||||
SERVICE_VERSIONS = {
|
||||
'image': {'supported_versions': ['v1', 'v2'], 'catalog': 'image'},
|
||||
'identity': {'supported_versions': ['v2', 'v3'], 'catalog': 'identity'},
|
||||
'volume': {'supported_versions': ['v2', 'v3'], 'catalog': 'volumev3'}
|
||||
}
|
||||
|
||||
# Keep track of where the extensions are saved for that service.
|
||||
# This is necessary because the configuration file is inconsistent - it uses
|
||||
# different option names for service extension depending on the service.
|
||||
SERVICE_EXTENSION_KEY = {
|
||||
'compute': 'api_extensions',
|
||||
'object-store': 'discoverable_apis',
|
||||
'network': 'api_extensions',
|
||||
'volume': 'api_extensions',
|
||||
'identity': 'api_extensions'
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
# Copyright 2016, 2018 Red Hat, 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
from tempest.lib import auth
|
||||
|
||||
@ -35,6 +35,8 @@ class Credentials(object):
|
||||
self.password = self.get_credential('password')
|
||||
self.tenant_name = self.get_credential('tenant_name')
|
||||
self.identity_version = self._get_identity_version()
|
||||
self.api_version = 3 if self.identity_version == "v3" else 2
|
||||
self.identity_region = self._conf.get_defaulted('identity', 'region')
|
||||
self.disable_ssl_certificate_validation = self._conf.get_defaulted(
|
||||
'identity',
|
||||
'disable_ssl_certificate_validation'
|
||||
|
91
config_tempest/flavors.py
Normal file
91
config_tempest/flavors.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Copyright 2016 Red Hat, 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.
|
||||
|
||||
from constants import LOG
|
||||
|
||||
|
||||
class Flavors(object):
|
||||
def __init__(self, client, allow_creation, conf):
|
||||
"""Init.
|
||||
|
||||
:type client: FlavorsClient object from tempest lib
|
||||
:type allow_creation: boolean
|
||||
:type conf: TempestConf object
|
||||
"""
|
||||
self.client = client
|
||||
self.allow_creation = allow_creation
|
||||
self._conf = conf
|
||||
|
||||
def create_tempest_flavors(self):
|
||||
"""Find or create flavors and set them in conf.
|
||||
|
||||
If 'flavor_ref' and 'flavor_ref_alt' are specified in conf, it will
|
||||
first try to find those - otherwise it will try finding or creating
|
||||
'm1.nano' and 'm1.micro' and overwrite those options in conf.
|
||||
"""
|
||||
# m1.nano flavor
|
||||
flavor_id = None
|
||||
if self._conf.has_option('compute', 'flavor_ref'):
|
||||
flavor_id = self._conf.get('compute', 'flavor_ref')
|
||||
flavor_id = self.find_or_create_flavor(flavor_id, 'm1.nano', ram=64)
|
||||
self._conf.set('compute', 'flavor_ref', flavor_id)
|
||||
|
||||
# m1.micro flavor
|
||||
alt_flavor_id = None
|
||||
if self._conf.has_option('compute', 'flavor_ref_alt'):
|
||||
alt_flavor_id = self._conf.get('compute', 'flavor_ref_alt')
|
||||
alt_flavor_id = self.find_or_create_flavor(alt_flavor_id, 'm1.micro',
|
||||
ram=128)
|
||||
self._conf.set('compute', 'flavor_ref_alt', alt_flavor_id)
|
||||
|
||||
def find_or_create_flavor(self, flavor_id, flavor_name,
|
||||
ram=64, vcpus=1, disk=0):
|
||||
"""Try finding flavor by ID or name, create if not found.
|
||||
|
||||
:param flavor_id: first try finding the flavor by this
|
||||
:param flavor_name: find by this if it was not found by ID, create new
|
||||
flavor with this name if not found at allCLIENT_MOCK
|
||||
:param ram: memory of created flavor in MB
|
||||
:param vcpus: number of VCPUs for the flavor
|
||||
:param disk: size of disk for flavor in GB
|
||||
"""
|
||||
flavor = None
|
||||
flavors = self.client.list_flavors()['flavors']
|
||||
# try finding it by the ID first
|
||||
if flavor_id:
|
||||
found = [f for f in flavors if f['id'] == flavor_id]
|
||||
if found:
|
||||
flavor = found[0]
|
||||
# if not found, try finding it by name
|
||||
if flavor_name and not flavor:
|
||||
found = [f for f in flavors if f['name'] == flavor_name]
|
||||
if found:
|
||||
flavor = found[0]
|
||||
|
||||
if not flavor and not self.allow_creation:
|
||||
raise Exception("Flavor '%s' not found, but resource creation"
|
||||
" isn't allowed. Either use '--create' or provide"
|
||||
" an existing flavor" % flavor_name)
|
||||
|
||||
if not flavor:
|
||||
LOG.info("Creating flavor '%s'", flavor_name)
|
||||
flavor = self.client.create_flavor(name=flavor_name,
|
||||
ram=ram, vcpus=vcpus,
|
||||
disk=disk, id=None)
|
||||
return flavor['flavor']['id']
|
||||
else:
|
||||
LOG.info("(no change) Found flavor '%s'", flavor['name'])
|
||||
|
||||
return flavor['id']
|
@ -1,17 +1,17 @@
|
||||
# Copyright 2016 Red Hat, 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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 will generate the etc/tempest.conf file by applying a series of
|
||||
specified options in the following order:
|
||||
@ -36,71 +36,26 @@ https://docs.openstack.org/developer/os-client-config/
|
||||
obtained by querying the cloud.
|
||||
"""
|
||||
|
||||
import api_discovery
|
||||
import argparse
|
||||
import ConfigParser
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
from clients import ClientManager
|
||||
import constants as C
|
||||
from constants import LOG
|
||||
from credentials import Credentials
|
||||
from flavors import Flavors
|
||||
import os_client_config
|
||||
from oslo_config import cfg
|
||||
from tempest.lib import exceptions
|
||||
from services import boto
|
||||
from services import ceilometer
|
||||
from services.horizon import configure_horizon
|
||||
from services.services import Services
|
||||
from services import volume
|
||||
import tempest_conf
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# Get the current tempest workspace path
|
||||
TEMPEST_WORKSPACE = os.getcwd()
|
||||
|
||||
DEFAULTS_FILE = os.path.join(TEMPEST_WORKSPACE, "etc",
|
||||
"default-overrides.conf")
|
||||
DEFAULT_IMAGE = ("http://download.cirros-cloud.net/0.3.5/"
|
||||
"cirros-0.3.5-x86_64-disk.img")
|
||||
DEFAULT_IMAGE_FORMAT = 'qcow2'
|
||||
|
||||
# services and their codenames
|
||||
SERVICE_NAMES = {
|
||||
'baremetal': 'ironic',
|
||||
'compute': 'nova',
|
||||
'database': 'trove',
|
||||
'data-processing': 'sahara',
|
||||
'image': 'glance',
|
||||
'network': 'neutron',
|
||||
'object-store': 'swift',
|
||||
'orchestration': 'heat',
|
||||
'share': 'manila',
|
||||
'telemetry': 'ceilometer',
|
||||
'volume': 'cinder',
|
||||
'messaging': 'zaqar',
|
||||
'metric': 'gnocchi',
|
||||
'event': 'panko',
|
||||
}
|
||||
|
||||
# what API versions could the service have and should be enabled/disabled
|
||||
# depending on whether they get discovered as supported. Services with only one
|
||||
# version don't need to be here, neither do service versions that are not
|
||||
# configurable in tempest.conf
|
||||
SERVICE_VERSIONS = {
|
||||
'image': {'supported_versions': ['v1', 'v2'], 'catalog': 'image'},
|
||||
'identity': {'supported_versions': ['v2', 'v3'], 'catalog': 'identity'},
|
||||
'volume': {'supported_versions': ['v2', 'v3'], 'catalog': 'volumev3'}
|
||||
}
|
||||
|
||||
# Keep track of where the extensions are saved for that service.
|
||||
# This is necessary because the configuration file is inconsistent - it uses
|
||||
# different option names for service extension depending on the service.
|
||||
SERVICE_EXTENSION_KEY = {
|
||||
'compute': 'api_extensions',
|
||||
'object-store': 'discoverable_apis',
|
||||
'network': 'api_extensions',
|
||||
'volume': 'api_extensions',
|
||||
'identity': 'api_extensions'
|
||||
}
|
||||
from users import Users
|
||||
|
||||
|
||||
def set_logging(debug, verbose):
|
||||
@ -154,9 +109,9 @@ def set_options(conf, deployer_input, non_admin, overrides=[],
|
||||
:param cloud_creds: Cloud credentials from client's config
|
||||
:type cloud_creds: dict
|
||||
"""
|
||||
if os.path.isfile(DEFAULTS_FILE):
|
||||
LOG.info("Reading defaults from file '%s'", DEFAULTS_FILE)
|
||||
conf.read(DEFAULTS_FILE)
|
||||
if os.path.isfile(C.DEFAULTS_FILE):
|
||||
LOG.info("Reading defaults from file '%s'", C.DEFAULTS_FILE)
|
||||
conf.read(C.DEFAULTS_FILE)
|
||||
|
||||
if deployer_input and os.path.isfile(deployer_input):
|
||||
read_deployer_input(deployer_input, conf)
|
||||
@ -192,6 +147,14 @@ def set_options(conf, deployer_input, non_admin, overrides=[],
|
||||
for section, key, value in overrides:
|
||||
conf.set(section, key, value, priority=True)
|
||||
|
||||
uri = conf.get("identity", "uri")
|
||||
if "v3" in uri:
|
||||
conf.set("identity", "auth_version", "v3")
|
||||
conf.set("identity", "uri_v3", uri)
|
||||
else:
|
||||
# TODO(arxcruz) make a check if v3 is enabled
|
||||
conf.set("identity", "uri_v3", uri.replace("v2.0", "v3"))
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
cloud_config = os_client_config.OpenStackConfig()
|
||||
@ -223,14 +186,14 @@ def parse_arguments():
|
||||
help='Run without admin creds')
|
||||
parser.add_argument('--test-accounts', default=None, metavar='PATH',
|
||||
help='Use accounts from accounts.yaml')
|
||||
parser.add_argument('--image-disk-format', default=DEFAULT_IMAGE_FORMAT,
|
||||
parser.add_argument('--image-disk-format', default=C.DEFAULT_IMAGE_FORMAT,
|
||||
help="""a format of an image to be uploaded to glance.
|
||||
Default is '%s'""" % DEFAULT_IMAGE_FORMAT)
|
||||
parser.add_argument('--image', default=DEFAULT_IMAGE,
|
||||
Default is '%s'""" % C.DEFAULT_IMAGE_FORMAT)
|
||||
parser.add_argument('--image', default=C.DEFAULT_IMAGE,
|
||||
help="""an image to be uploaded to glance. The name of
|
||||
the image is the leaf name of the path which
|
||||
can be either a filename or url. Default is
|
||||
'%s'""" % DEFAULT_IMAGE)
|
||||
'%s'""" % C.DEFAULT_IMAGE)
|
||||
parser.add_argument('--network-id',
|
||||
help="""The ID of an existing network in our openstack
|
||||
instance with external connectivity""")
|
||||
@ -247,7 +210,6 @@ def parse_arguments():
|
||||
" together, since creating" " resources requires"
|
||||
" admin rights")
|
||||
args.overrides = parse_overrides(args.overrides)
|
||||
args.remove = parse_values_to_remove(args.remove)
|
||||
cloud = cloud_config.get_one_cloud(argparse=args)
|
||||
return cloud
|
||||
|
||||
@ -333,451 +295,9 @@ def set_cloud_config_values(non_admin, cloud_creds, conf):
|
||||
'Could not load some identity options from cloud config file')
|
||||
|
||||
|
||||
def create_tempest_users(tenants_client, roles_client, users_client, conf,
|
||||
services):
|
||||
"""Create users necessary for Tempest if they don't exist already."""
|
||||
create_user_with_tenant(tenants_client, users_client,
|
||||
conf.get('identity', 'username'),
|
||||
conf.get('identity', 'password'),
|
||||
conf.get('identity', 'tenant_name'))
|
||||
|
||||
username = conf.get_defaulted('auth', 'admin_username')
|
||||
if username is None:
|
||||
username = conf.get_defaulted('identity', 'admin_username')
|
||||
give_role_to_user(tenants_client, roles_client, users_client,
|
||||
username,
|
||||
conf.get('identity', 'tenant_name'), role_name='admin')
|
||||
|
||||
# Prior to juno, and with earlier juno defaults, users needed to have
|
||||
# the heat_stack_owner role to use heat stack apis. We assign that role
|
||||
# to the user if the role is present.
|
||||
if 'orchestration' in services:
|
||||
give_role_to_user(tenants_client, roles_client, users_client,
|
||||
conf.get('identity', 'username'),
|
||||
conf.get('identity', 'tenant_name'),
|
||||
role_name='heat_stack_owner',
|
||||
role_required=False)
|
||||
|
||||
create_user_with_tenant(tenants_client, users_client,
|
||||
conf.get('identity', 'alt_username'),
|
||||
conf.get('identity', 'alt_password'),
|
||||
conf.get('identity', 'alt_tenant_name'))
|
||||
|
||||
|
||||
def give_role_to_user(tenants_client, roles_client, users_client, username,
|
||||
tenant_name, role_name, role_required=True):
|
||||
"""Give the user a role in the project (tenant).""",
|
||||
tenant_id = tenants_client.get_project_by_name(tenant_name)['id']
|
||||
users = users_client.list_users()
|
||||
user_ids = [u['id'] for u in users['users'] if u['name'] == username]
|
||||
user_id = user_ids[0]
|
||||
roles = roles_client.list_roles()
|
||||
role_ids = [r['id'] for r in roles['roles'] if r['name'] == role_name]
|
||||
if not role_ids:
|
||||
if role_required:
|
||||
raise Exception("required role %s not found" % role_name)
|
||||
LOG.debug("%s role not required", role_name)
|
||||
return
|
||||
role_id = role_ids[0]
|
||||
try:
|
||||
roles_client.create_user_role_on_project(tenant_id, user_id, role_id)
|
||||
LOG.debug("User '%s' was given the '%s' role in project '%s'",
|
||||
username, role_name, tenant_name)
|
||||
except exceptions.Conflict:
|
||||
LOG.debug("(no change) User '%s' already has the '%s' role in"
|
||||
" project '%s'", username, role_name, tenant_name)
|
||||
|
||||
|
||||
def create_user_with_tenant(tenants_client, users_client, username,
|
||||
password, tenant_name):
|
||||
"""Create a user and a tenant if it doesn't exist."""
|
||||
|
||||
LOG.info("Creating user '%s' with tenant '%s' and password '%s'",
|
||||
username, tenant_name, password)
|
||||
tenant_description = "Tenant for Tempest %s user" % username
|
||||
email = "%s@test.com" % username
|
||||
# create a tenant
|
||||
try:
|
||||
tenants_client.create_project(name=tenant_name,
|
||||
description=tenant_description)
|
||||
except exceptions.Conflict:
|
||||
LOG.info("(no change) Tenant '%s' already exists", tenant_name)
|
||||
|
||||
tenant_id = tenants_client.get_project_by_name(tenant_name)['id']
|
||||
|
||||
# create a user
|
||||
try:
|
||||
users_client.create_user(**{'name': username, 'password': password,
|
||||
'tenantId': tenant_id, 'email': email})
|
||||
except exceptions.Conflict:
|
||||
LOG.info("User '%s' already exists.", username)
|
||||
|
||||
|
||||
def create_tempest_flavors(client, conf, allow_creation):
|
||||
"""Find or create flavors 'm1.nano' and 'm1.micro' and set them in conf.
|
||||
|
||||
If 'flavor_ref' and 'flavor_ref_alt' are specified in conf, it will first
|
||||
try to find those - otherwise it will try finding or creating 'm1.nano' and
|
||||
'm1.micro' and overwrite those options in conf.
|
||||
|
||||
:param allow_creation: if False, fail if flavors were not found
|
||||
"""
|
||||
# m1.nano flavor
|
||||
flavor_id = None
|
||||
if conf.has_option('compute', 'flavor_ref'):
|
||||
flavor_id = conf.get('compute', 'flavor_ref')
|
||||
flavor_id = find_or_create_flavor(client,
|
||||
flavor_id, 'm1.nano',
|
||||
allow_creation, ram=64)
|
||||
conf.set('compute', 'flavor_ref', flavor_id)
|
||||
|
||||
# m1.micro flavor
|
||||
alt_flavor_id = None
|
||||
if conf.has_option('compute', 'flavor_ref_alt'):
|
||||
alt_flavor_id = conf.get('compute', 'flavor_ref_alt')
|
||||
alt_flavor_id = find_or_create_flavor(client,
|
||||
alt_flavor_id, 'm1.micro',
|
||||
allow_creation, ram=128)
|
||||
conf.set('compute', 'flavor_ref_alt', alt_flavor_id)
|
||||
|
||||
|
||||
def find_or_create_flavor(client, flavor_id, flavor_name,
|
||||
allow_creation, ram=64, vcpus=1, disk=0):
|
||||
"""Try finding flavor by ID or name, create if not found.
|
||||
|
||||
:param flavor_id: first try finding the flavor by this
|
||||
:param flavor_name: find by this if it was not found by ID, create new
|
||||
flavor with this name if not found at all
|
||||
:param allow_creation: if False, fail if flavors were not found
|
||||
:param ram: memory of created flavor in MB
|
||||
:param vcpus: number of VCPUs for the flavor
|
||||
:param disk: size of disk for flavor in GB
|
||||
"""
|
||||
flavor = None
|
||||
flavors = client.list_flavors()['flavors']
|
||||
# try finding it by the ID first
|
||||
if flavor_id:
|
||||
found = [f for f in flavors if f['id'] == flavor_id]
|
||||
if found:
|
||||
flavor = found[0]
|
||||
# if not found previously, try finding it by name
|
||||
if flavor_name and not flavor:
|
||||
found = [f for f in flavors if f['name'] == flavor_name]
|
||||
if found:
|
||||
flavor = found[0]
|
||||
|
||||
if not flavor and not allow_creation:
|
||||
raise Exception("Flavor '%s' not found, but resource creation"
|
||||
" isn't allowed. Either use '--create' or provide"
|
||||
" an existing flavor" % flavor_name)
|
||||
|
||||
if not flavor:
|
||||
LOG.info("Creating flavor '%s'", flavor_name)
|
||||
flavor = client.create_flavor(name=flavor_name,
|
||||
ram=ram, vcpus=vcpus,
|
||||
disk=disk, id=None)
|
||||
return flavor['flavor']['id']
|
||||
else:
|
||||
LOG.info("(no change) Found flavor '%s'", flavor['name'])
|
||||
|
||||
return flavor['id']
|
||||
|
||||
|
||||
def create_tempest_images(client, conf, image_path, allow_creation,
|
||||
disk_format):
|
||||
img_path = os.path.join(conf.get("scenario", "img_dir"),
|
||||
os.path.basename(image_path))
|
||||
name = image_path[image_path.rfind('/') + 1:]
|
||||
conf.set('scenario', 'img_file', name)
|
||||
alt_name = name + "_alt"
|
||||
image_id = None
|
||||
if conf.has_option('compute', 'image_ref'):
|
||||
image_id = conf.get('compute', 'image_ref')
|
||||
image_id = find_or_upload_image(client,
|
||||
image_id, name, allow_creation,
|
||||
image_source=image_path,
|
||||
image_dest=img_path,
|
||||
disk_format=disk_format)
|
||||
alt_image_id = None
|
||||
if conf.has_option('compute', 'image_ref_alt'):
|
||||
alt_image_id = conf.get('compute', 'image_ref_alt')
|
||||
alt_image_id = find_or_upload_image(client,
|
||||
alt_image_id, alt_name, allow_creation,
|
||||
image_source=image_path,
|
||||
image_dest=img_path,
|
||||
disk_format=disk_format)
|
||||
|
||||
conf.set('compute', 'image_ref', image_id)
|
||||
conf.set('compute', 'image_ref_alt', alt_image_id)
|
||||
|
||||
|
||||
def check_ceilometer_service(client, conf, services):
|
||||
try:
|
||||
services = client.list_services(**{'type': 'metering'})
|
||||
except exceptions.Forbidden:
|
||||
LOG.warning("User has no permissions to list services - "
|
||||
"metering service can't be discovered.")
|
||||
return
|
||||
if services and len(services['services']):
|
||||
metering = services['services'][0]
|
||||
if 'ceilometer' in metering['name'] and metering['enabled']:
|
||||
conf.set('service_available', 'ceilometer', 'True')
|
||||
|
||||
|
||||
def check_volume_backup_service(client, conf, services):
|
||||
"""Verify if the cinder backup service is enabled"""
|
||||
if 'volumev3' not in services:
|
||||
LOG.info("No volume service found, skipping backup service check")
|
||||
return
|
||||
try:
|
||||
params = {'binary': 'cinder-backup'}
|
||||
backup_service = client.list_services(**params)
|
||||
except exceptions.Forbidden:
|
||||
LOG.warning("User has no permissions to list services - "
|
||||
"cinder-backup service can't be discovered.")
|
||||
return
|
||||
|
||||
if backup_service:
|
||||
# We only set backup to false if the service isn't running otherwise we
|
||||
# keep the default value
|
||||
service = backup_service['services']
|
||||
if not service or service[0]['state'] == 'down':
|
||||
conf.set('volume-feature-enabled', 'backup', 'False')
|
||||
|
||||
|
||||
def find_or_upload_image(client, image_id, image_name, allow_creation,
|
||||
image_source='', image_dest='', disk_format=''):
|
||||
image = _find_image(client, image_id, image_name)
|
||||
if not image and not allow_creation:
|
||||
raise Exception("Image '%s' not found, but resource creation"
|
||||
" isn't allowed. Either use '--create' or provide"
|
||||
" an existing image_ref" % image_name)
|
||||
|
||||
if image:
|
||||
LOG.info("(no change) Found image '%s'", image['name'])
|
||||
path = os.path.abspath(image_dest)
|
||||
if not os.path.isfile(path):
|
||||
_download_image(client, image['id'], path)
|
||||
else:
|
||||
LOG.info("Creating image '%s'", image_name)
|
||||
if image_source.startswith("http:") or \
|
||||
image_source.startswith("https:"):
|
||||
_download_file(image_source, image_dest)
|
||||
else:
|
||||
shutil.copyfile(image_source, image_dest)
|
||||
image = _upload_image(client, image_name, image_dest, disk_format)
|
||||
return image['id']
|
||||
|
||||
|
||||
def create_tempest_networks(clients, conf, has_neutron, public_network_id):
|
||||
label = None
|
||||
public_network_name = None
|
||||
# TODO(tkammer): separate logic to different func of Nova network
|
||||
# vs Neutron
|
||||
if has_neutron:
|
||||
client = clients.get_neutron_client()
|
||||
|
||||
# if user supplied the network we should use
|
||||
if public_network_id:
|
||||
LOG.info("Looking for existing network id: {0}"
|
||||
"".format(public_network_id))
|
||||
|
||||
# check if network exists
|
||||
network_list = client.list_networks()
|
||||
for network in network_list['networks']:
|
||||
if network['id'] == public_network_id:
|
||||
public_network_name = network['name']
|
||||
break
|
||||
else:
|
||||
raise ValueError('provided network id: {0} was not found.'
|
||||
''.format(public_network_id))
|
||||
|
||||
# no network id provided, try to auto discover a public network
|
||||
else:
|
||||
LOG.info("No network supplied, trying auto discover for network")
|
||||
network_list = client.list_networks()
|
||||
for network in network_list['networks']:
|
||||
if network['router:external'] and network['subnets']:
|
||||
LOG.info("Found network, using: {0}".format(network['id']))
|
||||
public_network_id = network['id']
|
||||
public_network_name = network['name']
|
||||
break
|
||||
|
||||
# Couldn't find an existing external network
|
||||
else:
|
||||
LOG.error("No external networks found. "
|
||||
"Please note that any test that relies on external "
|
||||
"connectivity would most likely fail.")
|
||||
|
||||
if public_network_id is not None:
|
||||
conf.set('network', 'public_network_id', public_network_id)
|
||||
if public_network_name is not None:
|
||||
conf.set('network', 'floating_network_name', public_network_name)
|
||||
|
||||
else:
|
||||
client = clients.get_nova_net_client()
|
||||
networks = client.list_networks()
|
||||
if networks:
|
||||
label = networks['networks'][0]['label']
|
||||
|
||||
if label:
|
||||
conf.set('compute', 'fixed_network_name', label)
|
||||
elif not has_neutron:
|
||||
raise Exception('fixed_network_name could not be discovered and'
|
||||
' must be specified')
|
||||
|
||||
|
||||
def configure_keystone_feature_flags(conf, services):
|
||||
"""Set keystone feature flags based upon version ID."""
|
||||
supported_versions = services.get('identity', {}).get('versions', [])
|
||||
if len(supported_versions) <= 1:
|
||||
return
|
||||
for version in supported_versions:
|
||||
major, minor = version.split('.')[:2]
|
||||
# Enable the domain specific roles feature flag. For more information,
|
||||
# see https://developer.openstack.org/api-ref/identity/v3
|
||||
if major == 'v3' and int(minor) >= 6:
|
||||
conf.set('identity-feature-enabled',
|
||||
'forbid_global_implied_dsr',
|
||||
'True')
|
||||
|
||||
|
||||
def configure_boto(conf, services):
|
||||
"""Set boto URLs based on discovered APIs."""
|
||||
if 'ec2' in services:
|
||||
conf.set('boto', 'ec2_url', services['ec2']['url'])
|
||||
if 's3' in services:
|
||||
conf.set('boto', 's3_url', services['s3']['url'])
|
||||
|
||||
|
||||
def configure_horizon(conf):
|
||||
"""Derive the horizon URIs from the identity's URI."""
|
||||
uri = conf.get('identity', 'uri')
|
||||
u = urllib2.urlparse.urlparse(uri)
|
||||
base = '%s://%s%s' % (u.scheme, u.netloc.replace(
|
||||
':' + str(u.port), ''), '/dashboard')
|
||||
assert base.startswith('http:') or base.startswith('https:')
|
||||
has_horizon = True
|
||||
try:
|
||||
urllib2.urlopen(base)
|
||||
except urllib2.URLError:
|
||||
has_horizon = False
|
||||
conf.set('service_available', 'horizon', str(has_horizon))
|
||||
conf.set('dashboard', 'dashboard_url', base + '/')
|
||||
conf.set('dashboard', 'login_url', base + '/auth/login/')
|
||||
|
||||
|
||||
def configure_discovered_services(conf, services):
|
||||
"""Set service availability and supported extensions and versions.
|
||||
|
||||
Set True/False per service in the [service_available] section of `conf`
|
||||
depending of wheter it is in services. In the [<service>-feature-enabled]
|
||||
section, set extensions and versions found in `services`.
|
||||
|
||||
:param conf: ConfigParser configuration
|
||||
:param services: dictionary of discovered services - expects each service
|
||||
to have a dictionary containing 'extensions' and 'versions' keys
|
||||
"""
|
||||
# check if volume service is disabled
|
||||
if conf.has_section('services') and conf.has_option('services', 'volume'):
|
||||
if not conf.getboolean('services', 'volume'):
|
||||
SERVICE_NAMES.pop('volume')
|
||||
SERVICE_VERSIONS.pop('volume')
|
||||
# set service availability
|
||||
for service, codename in SERVICE_NAMES.iteritems():
|
||||
# ceilometer is still transitioning from metering to telemetry
|
||||
if service == 'telemetry' and 'metering' in services:
|
||||
service = 'metering'
|
||||
conf.set('service_available', codename, str(service in services))
|
||||
|
||||
# TODO(arxcruz): Remove this once/if we get the following reviews merged
|
||||
# in all branches supported by tempestconf, or once/if tempestconf do not
|
||||
# support anymore the OpenStack release where those patches are not
|
||||
# available.
|
||||
# https://review.openstack.org/#/c/492526/
|
||||
# https://review.openstack.org/#/c/492525/
|
||||
|
||||
if 'alarming' in services:
|
||||
conf.set('service_available', 'aodh', 'True')
|
||||
conf.set('service_available', 'aodh_plugin', 'True')
|
||||
|
||||
# set supported API versions for services with more of them
|
||||
for service, service_info in SERVICE_VERSIONS.iteritems():
|
||||
supported_versions = services.get(
|
||||
service_info['catalog'], {}).get('versions', [])
|
||||
section = service + '-feature-enabled'
|
||||
for version in service_info['supported_versions']:
|
||||
is_supported = any(version in item
|
||||
for item in supported_versions)
|
||||
conf.set(section, 'api_' + version, str(is_supported))
|
||||
|
||||
# set service extensions
|
||||
keystone_v3_support = conf.get('identity-feature-enabled', 'api_v3')
|
||||
for service, ext_key in SERVICE_EXTENSION_KEY.iteritems():
|
||||
if service in services:
|
||||
extensions = ','.join(services[service].get('extensions', ""))
|
||||
if service == 'object-store':
|
||||
# tempest.conf is inconsistent and uses 'object-store' for the
|
||||
# catalog name but 'object-storage-feature-enabled'
|
||||
service = 'object-storage'
|
||||
elif service == 'identity' and keystone_v3_support:
|
||||
identity_v3_ext = api_discovery.get_identity_v3_extensions(
|
||||
conf.get("identity", "uri_v3"))
|
||||
extensions = list(set(extensions.split(',') + identity_v3_ext))
|
||||
extensions = ','.join(extensions)
|
||||
conf.set(service + '-feature-enabled', ext_key, extensions)
|
||||
|
||||
|
||||
def _download_file(url, destination):
|
||||
if os.path.exists(destination):
|
||||
LOG.info("Image '%s' already fetched to '%s'.", url, destination)
|
||||
return
|
||||
LOG.info("Downloading '%s' and saving as '%s'", url, destination)
|
||||
f = urllib2.urlopen(url)
|
||||
data = f.read()
|
||||
with open(destination, "wb") as dest:
|
||||
dest.write(data)
|
||||
|
||||
|
||||
def _download_image(client, id, path):
|
||||
"""Download file from glance."""
|
||||
LOG.info("Downloading image %s to %s", id, path)
|
||||
body = client.show_image_file(id)
|
||||
LOG.debug(type(body.data))
|
||||
with open(path, 'wb') as out:
|
||||
out.write(body.data)
|
||||
|
||||
|
||||
def _upload_image(client, name, path, disk_format):
|
||||
"""Upload image file from `path` into Glance with `name."""
|
||||
LOG.info("Uploading image '%s' from '%s'", name, os.path.abspath(path))
|
||||
|
||||
with open(path) as data:
|
||||
image = client.create_image(name=name,
|
||||
disk_format=disk_format,
|
||||
container_format='bare',
|
||||
visibility="public")
|
||||
client.store_image_file(image['id'], data)
|
||||
return image
|
||||
|
||||
|
||||
def _find_image(client, image_id, image_name):
|
||||
"""Find image by ID or name (the image client doesn't have this)."""
|
||||
if image_id:
|
||||
try:
|
||||
return client.show_image(image_id)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
found = filter(lambda x: x['name'] == image_name,
|
||||
client.list_images()['images'])
|
||||
if found:
|
||||
return found[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
args.remove = parse_values_to_remove(args.remove)
|
||||
set_logging(args.debug, args.verbose)
|
||||
|
||||
conf = tempest_conf.TempestConf()
|
||||
@ -785,46 +305,35 @@ def main():
|
||||
set_options(conf, args.deployer_input, args.non_admin,
|
||||
args.overrides, args.test_accounts, cloud_creds)
|
||||
|
||||
uri = conf.get("identity", "uri")
|
||||
api_version = 2
|
||||
if "v3" in uri:
|
||||
api_version = 3
|
||||
conf.set("identity", "auth_version", "v3")
|
||||
conf.set("identity", "uri_v3", uri)
|
||||
else:
|
||||
# TODO(arxcruz) make a check if v3 is enabled
|
||||
conf.set("identity", "uri_v3", uri.replace("v2.0", "v3"))
|
||||
credentials = Credentials(conf, not args.non_admin)
|
||||
clients = ClientManager(conf, credentials)
|
||||
swift_discover = conf.get_defaulted('object-storage-feature-enabled',
|
||||
'discoverability')
|
||||
services = api_discovery.discover(
|
||||
clients.auth_provider,
|
||||
clients.identity_region,
|
||||
object_store_discovery=conf.get_bool_value(swift_discover),
|
||||
api_version=api_version,
|
||||
disable_ssl_certificate_validation=conf.get_defaulted(
|
||||
'identity',
|
||||
'disable_ssl_certificate_validation'
|
||||
)
|
||||
)
|
||||
services = Services(clients, conf, credentials)
|
||||
|
||||
if args.create and args.test_accounts is None:
|
||||
create_tempest_users(clients.tenants, clients.roles, clients.users,
|
||||
conf, services)
|
||||
create_tempest_flavors(clients.flavors, conf, args.create)
|
||||
create_tempest_images(clients.images, conf, args.image, args.create,
|
||||
args.image_disk_format)
|
||||
has_neutron = "network" in services
|
||||
users = Users(clients.tenants, clients.roles, clients.users, conf)
|
||||
users.create_tempest_users(services.is_service('orchestration'))
|
||||
flavors = Flavors(clients.flavors, args.create, conf)
|
||||
flavors.create_tempest_flavors()
|
||||
|
||||
LOG.info("Setting up network")
|
||||
LOG.debug("Is neutron present: {0}".format(has_neutron))
|
||||
create_tempest_networks(clients, conf, has_neutron, args.network_id)
|
||||
image = services.get_service('image')
|
||||
image.set_image_preferences(args.create, args.image,
|
||||
args.image_disk_format)
|
||||
image.create_tempest_images(conf)
|
||||
|
||||
configure_discovered_services(conf, services)
|
||||
check_volume_backup_service(clients.volume_service, conf, services)
|
||||
check_ceilometer_service(clients.service_client, conf, services)
|
||||
configure_boto(conf, services)
|
||||
configure_keystone_feature_flags(conf, services)
|
||||
has_neutron = services.is_service("network")
|
||||
network = services.get_service("network")
|
||||
network.create_tempest_networks(has_neutron, conf, args.network_id)
|
||||
|
||||
services.set_service_availability()
|
||||
services.set_supported_api_versions()
|
||||
services.set_service_extensions()
|
||||
volume.check_volume_backup_service(conf, clients.volume_client,
|
||||
services.is_service("volumev3"))
|
||||
ceilometer.check_ceilometer_service(conf, clients.service_client)
|
||||
boto.configure_boto(conf,
|
||||
s3_service=services.get_service("s3"))
|
||||
identity = services.get_service('identity')
|
||||
identity.configure_keystone_feature_flags(conf)
|
||||
configure_horizon(conf)
|
||||
|
||||
# remove all unwanted values if were specified
|
||||
|
0
config_tempest/services/__init__.py
Normal file
0
config_tempest/services/__init__.py
Normal file
97
config_tempest/services/base.py
Normal file
97
config_tempest/services/base.py
Normal file
@ -0,0 +1,97 @@
|
||||
# Copyright 2013 Red Hat, 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 json
|
||||
import re
|
||||
import urllib3
|
||||
import urlparse
|
||||
|
||||
from config_tempest.constants import LOG
|
||||
MULTIPLE_SLASH = re.compile(r'/+')
|
||||
|
||||
|
||||
class ServiceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Service(object):
|
||||
def __init__(self, name, service_url, token, disable_ssl_validation,
|
||||
client=None):
|
||||
self.name = name
|
||||
self.service_url = service_url
|
||||
self.headers = {'Accept': 'application/json', 'X-Auth-Token': token}
|
||||
self.disable_ssl_validation = disable_ssl_validation
|
||||
self.client = client
|
||||
|
||||
self.extensions = []
|
||||
self.versions = []
|
||||
|
||||
def do_get(self, url, top_level=False, top_level_path=""):
|
||||
parts = list(urlparse.urlparse(url))
|
||||
# 2 is the path offset
|
||||
if top_level:
|
||||
parts[2] = '/' + top_level_path
|
||||
|
||||
parts[2] = MULTIPLE_SLASH.sub('/', parts[2])
|
||||
url = urlparse.urlunparse(parts)
|
||||
|
||||
try:
|
||||
if self.disable_ssl_validation:
|
||||
urllib3.disable_warnings()
|
||||
http = urllib3.PoolManager(cert_reqs='CERT_NONE')
|
||||
else:
|
||||
http = urllib3.PoolManager()
|
||||
r = http.request('GET', url, headers=self.headers)
|
||||
except Exception as e:
|
||||
LOG.error("Request on service '%s' with url '%s' failed",
|
||||
(self.name, url))
|
||||
raise e
|
||||
if r.status >= 400:
|
||||
raise ServiceError("Request on service '%s' with url '%s' failed"
|
||||
" with code %d" % (self.name, url, r.status))
|
||||
return r.data
|
||||
|
||||
def set_extensions(self):
|
||||
self.extensions = []
|
||||
|
||||
def set_versions(self):
|
||||
self.versions = []
|
||||
|
||||
def get_extensions(self):
|
||||
return self.extensions
|
||||
|
||||
def get_versions(self):
|
||||
return self.versions
|
||||
|
||||
|
||||
class VersionedService(Service):
|
||||
def set_versions(self, top_level=True):
|
||||
body = self.do_get(self.service_url, top_level=top_level)
|
||||
body = json.loads(body)
|
||||
self.versions = self.deserialize_versions(body)
|
||||
|
||||
def deserialize_versions(self, body):
|
||||
return map(lambda x: x['id'], body['versions'])
|
||||
|
||||
def no_port_cut_url(self):
|
||||
# if there is no port defined, cut the url from version to the end
|
||||
u = urllib3.util.parse_url(self.service_url)
|
||||
url = self.service_url
|
||||
if u.port is None:
|
||||
found = re.findall(r'v\d', url)
|
||||
if len(found) > 0:
|
||||
index = url.index(found[0])
|
||||
url = self.service_url[:index]
|
||||
return (url, u.port is not None)
|
26
config_tempest/services/boto.py
Normal file
26
config_tempest/services/boto.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright 2016 Red Hat, 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.
|
||||
|
||||
|
||||
def configure_boto(conf, ec2_service=None, s3_service=None):
|
||||
"""Set boto URLs based on discovered APIs.
|
||||
|
||||
:type ec2_service: config_tempest.services.base.Service
|
||||
:type s3_service: config_tempest.services.base.Service
|
||||
"""
|
||||
if ec2_service:
|
||||
conf.set('boto', 'ec2_url', ec2_service.service_url)
|
||||
if s3_service:
|
||||
conf.set('boto', 's3_url', s3_service.service_url)
|
36
config_tempest/services/ceilometer.py
Normal file
36
config_tempest/services/ceilometer.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright 2016 Red Hat, 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 config_tempest.constants as C
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
def check_ceilometer_service(conf, service_client):
|
||||
"""If a metering service is available, set it to conf
|
||||
|
||||
:type conf: TempestConf object
|
||||
:type service_client: Tempest's identity.v3.services_client.ServicesClient
|
||||
"""
|
||||
try:
|
||||
params = {'type': 'metering'}
|
||||
services = service_client.list_services(**params)
|
||||
except exceptions.Forbidden:
|
||||
C.LOG.warning("User has no permissions to list services - "
|
||||
"metering service can't be discovered.")
|
||||
return
|
||||
if services and len(services['services']):
|
||||
metering = services['services'][0]
|
||||
if 'ceilometer' in metering['name'] and metering['enabled']:
|
||||
conf.set('service_available', 'ceilometer', 'True')
|
30
config_tempest/services/compute.py
Normal file
30
config_tempest/services/compute.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2013 Red Hat, 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.
|
||||
|
||||
from base import VersionedService
|
||||
import json
|
||||
|
||||
|
||||
class ComputeService(VersionedService):
|
||||
def set_extensions(self):
|
||||
body = self.do_get(self.service_url + '/extensions')
|
||||
body = json.loads(body)
|
||||
self.extensions = map(lambda x: x['alias'], body['extensions'])
|
||||
|
||||
def set_versions(self):
|
||||
url, top_level = self.no_port_cut_url()
|
||||
body = self.do_get(url, top_level=top_level)
|
||||
body = json.loads(body)
|
||||
self.versions = self.deserialize_versions(body)
|
33
config_tempest/services/horizon.py
Normal file
33
config_tempest/services/horizon.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2016 Red Hat, 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 urllib2
|
||||
|
||||
|
||||
def configure_horizon(conf):
|
||||
"""Derive the horizon URIs from the identity's URI."""
|
||||
uri = conf.get('identity', 'uri')
|
||||
u = urllib2.urlparse.urlparse(uri)
|
||||
base = '%s://%s%s' % (u.scheme, u.netloc.replace(
|
||||
':' + str(u.port), ''), '/dashboard')
|
||||
assert base.startswith('http:') or base.startswith('https:')
|
||||
has_horizon = True
|
||||
try:
|
||||
urllib2.urlopen(base)
|
||||
except urllib2.URLError:
|
||||
has_horizon = False
|
||||
conf.set('service_available', 'horizon', str(has_horizon))
|
||||
conf.set('dashboard', 'dashboard_url', base + '/')
|
||||
conf.set('dashboard', 'login_url', base + '/auth/login/')
|
112
config_tempest/services/identity.py
Normal file
112
config_tempest/services/identity.py
Normal file
@ -0,0 +1,112 @@
|
||||
# Copyright 2013 Red Hat, 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 json
|
||||
import requests
|
||||
import urlparse
|
||||
|
||||
from base import VersionedService
|
||||
from config_tempest.constants import LOG
|
||||
|
||||
|
||||
class IdentityService(VersionedService):
|
||||
def __init__(self, name, service_url, token, disable_ssl_validation,
|
||||
client=None):
|
||||
super(IdentityService, self).__init__(
|
||||
name, service_url, token, disable_ssl_validation, client)
|
||||
self.extensions_v3 = []
|
||||
version = ''
|
||||
if 'v2' in self.service_url:
|
||||
version = '/v2.0'
|
||||
url_parse = urlparse.urlparse(self.service_url)
|
||||
self.service_url = '{}://{}{}'.format(url_parse.scheme,
|
||||
url_parse.netloc, version)
|
||||
|
||||
def set_extensions(self):
|
||||
if 'v2' in self.service_url:
|
||||
body = self.do_get(self.service_url + '/extensions')
|
||||
body = json.loads(body)
|
||||
values = body['extensions']['values']
|
||||
self.extensions = map(lambda x: x['alias'], values)
|
||||
return
|
||||
# Keystone api changed in v3, the concept of extensions changed. Right
|
||||
# now, all the existing extensions are part of keystone core api, so,
|
||||
# there's no longer the /extensions endpoint. The extensions that are
|
||||
# stable, are enabled by default, the ones marked as experimental are
|
||||
# disabled by default. Checking the tempest source, there's no test
|
||||
# pointing to extensions endpoint, so I am very confident that this
|
||||
# will not be an issue. If so, we need to list all the /OS-XYZ
|
||||
# extensions to identify what is enabled or not. This would be a manual
|
||||
# check every time keystone change, add or delete an extension, so I
|
||||
# rather prefer to set empty list here for now.
|
||||
self.extensions = []
|
||||
|
||||
def set_identity_v3_extensions(self):
|
||||
"""Returns discovered identity v3 extensions
|
||||
|
||||
As keystone V3 uses a JSON Home to store the extensions.
|
||||
This method implements a different discovery method.
|
||||
|
||||
:param keystone_v3_url: Keystone V3 auth url
|
||||
:return: A list with the discovered extensions
|
||||
"""
|
||||
try:
|
||||
r = requests.get(self.service_url,
|
||||
verify=False,
|
||||
headers={'Accept': 'application/json-home'})
|
||||
except requests.exceptions.RequestException as re:
|
||||
LOG.error("Request on service '%s' with url '%s' failed",
|
||||
'identity', self.service_url)
|
||||
raise re
|
||||
ext_h = 'http://docs.openstack.org/api/openstack-identity/3/ext/'
|
||||
res = [x for x in json.loads(r.content)['resources'].keys()]
|
||||
ext = [ex for ex in res if 'ext' in ex]
|
||||
ext = [str(e).replace(ext_h, '').split('/')[0] for e in ext]
|
||||
self.extensions_v3 = list(set(ext))
|
||||
|
||||
def set_versions(self):
|
||||
super(IdentityService, self).set_versions(top_level=False)
|
||||
|
||||
def get_extensions(self):
|
||||
all_ext_lst = self.extensions + self.extensions_v3
|
||||
return list(set(all_ext_lst))
|
||||
|
||||
def deserialize_versions(self, body):
|
||||
try:
|
||||
versions = []
|
||||
for v in body['versions']['values']:
|
||||
# TripleO is in transition to v3 only, so the environment
|
||||
# still returns v2 versions even though they're deprecated.
|
||||
# Therefor pick only versions with stable status.
|
||||
if v['status'] == 'stable':
|
||||
versions.append(v['id'])
|
||||
return versions
|
||||
except KeyError:
|
||||
return [body['version']['id']]
|
||||
|
||||
def configure_keystone_feature_flags(self, conf):
|
||||
"""Set keystone feature flags based upon version ID."""
|
||||
supported_versions = self.get_versions()
|
||||
if len(supported_versions) <= 1:
|
||||
return
|
||||
for version in supported_versions:
|
||||
major, minor = version.split('.')[:2]
|
||||
# Enable the domain specific roles feature flag.
|
||||
# For more information see:
|
||||
# https://developer.openstack.org/api-ref/identity/v3
|
||||
if major == 'v3' and int(minor) >= 6:
|
||||
conf.set('identity-feature-enabled',
|
||||
'forbid_global_implied_dsr',
|
||||
'True')
|
168
config_tempest/services/image.py
Normal file
168
config_tempest/services/image.py
Normal file
@ -0,0 +1,168 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import urllib2
|
||||
|
||||
from base import VersionedService
|
||||
from config_tempest.constants import LOG
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
class ImageService(VersionedService):
|
||||
|
||||
def __init__(self, name, service_url, token, disable_ssl_validation,
|
||||
client=None):
|
||||
super(ImageService, self).__init__(name, service_url, token,
|
||||
disable_ssl_validation,
|
||||
client)
|
||||
self.allow_creation = False
|
||||
self.image_path = ""
|
||||
self.disk_format = ""
|
||||
|
||||
def set_image_preferences(self, allow_creation, image_path, disk_format):
|
||||
"""Sets image prefferences.
|
||||
|
||||
:type allow_creation: boolean
|
||||
:type image_path: string
|
||||
:type disk_format: string
|
||||
"""
|
||||
self.allow_creation = allow_creation
|
||||
self.image_path = image_path
|
||||
self.disk_format = disk_format
|
||||
|
||||
def set_versions(self):
|
||||
super(ImageService, self).set_versions(top_level=False)
|
||||
|
||||
def create_tempest_images(self, conf):
|
||||
"""Uploads an image to the glance.
|
||||
|
||||
The method creates images specified in conf, if they're not created
|
||||
already. Then it sets their IDs to the conf.
|
||||
|
||||
:type conf: TempestConf object
|
||||
"""
|
||||
img_path = os.path.join(conf.get("scenario", "img_dir"),
|
||||
os.path.basename(self.image_path))
|
||||
name = self.image_path[self.image_path.rfind('/') + 1:]
|
||||
conf.set('scenario', 'img_file', name)
|
||||
alt_name = name + "_alt"
|
||||
image_id = None
|
||||
if conf.has_option('compute', 'image_ref'):
|
||||
image_id = conf.get('compute', 'image_ref')
|
||||
image_id = self.find_or_upload_image(image_id, name,
|
||||
image_source=self.image_path,
|
||||
image_dest=img_path)
|
||||
alt_image_id = None
|
||||
if conf.has_option('compute', 'image_ref_alt'):
|
||||
alt_image_id = conf.get('compute', 'image_ref_alt')
|
||||
alt_image_id = self.find_or_upload_image(alt_image_id, alt_name,
|
||||
image_source=self.image_path,
|
||||
image_dest=img_path)
|
||||
|
||||
conf.set('compute', 'image_ref', image_id)
|
||||
conf.set('compute', 'image_ref_alt', alt_image_id)
|
||||
|
||||
def find_or_upload_image(self, image_id, image_name, image_source='',
|
||||
image_dest=''):
|
||||
"""If the image is not found, uploads it.
|
||||
|
||||
:type image_id: string
|
||||
:type image_name: string
|
||||
:type image_source: string
|
||||
:type image_dest: string
|
||||
"""
|
||||
image = self._find_image(image_id, image_name)
|
||||
if not image and not self.allow_creation:
|
||||
raise Exception("Image '%s' not found, but resource creation"
|
||||
" isn't allowed. Either use '--create' or provide"
|
||||
" an existing image_ref" % image_name)
|
||||
|
||||
if image:
|
||||
LOG.info("(no change) Found image '%s'", image['name'])
|
||||
path = os.path.abspath(image_dest)
|
||||
if not os.path.isfile(path):
|
||||
self._download_image(image['id'], path)
|
||||
else:
|
||||
LOG.info("Creating image '%s'", image_name)
|
||||
if image_source.startswith("http:") or \
|
||||
image_source.startswith("https:"):
|
||||
self._download_file(image_source, image_dest)
|
||||
else:
|
||||
shutil.copyfile(image_source, image_dest)
|
||||
image = self._upload_image(image_name, image_dest)
|
||||
return image['id']
|
||||
|
||||
def _find_image(self, image_id, image_name):
|
||||
"""Find image by ID or name (the image client doesn't have this).
|
||||
|
||||
:type image_id: string
|
||||
:type image_name: string
|
||||
"""
|
||||
if image_id:
|
||||
try:
|
||||
return self.client.show_image(image_id)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
found = filter(lambda x: x['name'] == image_name,
|
||||
self.client.list_images()['images'])
|
||||
if found:
|
||||
return found[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _upload_image(self, name, path):
|
||||
"""Upload image file from `path` into Glance with `name`.
|
||||
|
||||
:type name: string
|
||||
:type path: string
|
||||
"""
|
||||
LOG.info("Uploading image '%s' from '%s'", name, os.path.abspath(path))
|
||||
|
||||
with open(path) as data:
|
||||
image = self.client.create_image(name=name,
|
||||
disk_format=self.disk_format,
|
||||
container_format='bare',
|
||||
visibility="public")
|
||||
self.client.store_image_file(image['id'], data)
|
||||
return image
|
||||
|
||||
def _download_image(self, id, path):
|
||||
"""Download image from glance.
|
||||
|
||||
:type id: string
|
||||
:type path: string
|
||||
"""
|
||||
LOG.info("Downloading image %s to %s", id, path)
|
||||
body = self.client.show_image_file(id)
|
||||
LOG.debug(type(body.data))
|
||||
with open(path, 'wb') as out:
|
||||
out.write(body.data)
|
||||
|
||||
def _download_file(self, url, destination):
|
||||
"""Downloads a file specified by `url` to `destination`.
|
||||
|
||||
:type url: string
|
||||
:type destination: string
|
||||
"""
|
||||
if os.path.exists(destination):
|
||||
LOG.info("Image '%s' already fetched to '%s'.", url, destination)
|
||||
return
|
||||
LOG.info("Downloading '%s' and saving as '%s'", url, destination)
|
||||
f = urllib2.urlopen(url)
|
||||
data = f.read()
|
||||
with open(destination, "wb") as dest:
|
||||
dest.write(data)
|
90
config_tempest/services/network.py
Normal file
90
config_tempest/services/network.py
Normal file
@ -0,0 +1,90 @@
|
||||
# Copyright 2013, 2016 Red Hat, 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 json
|
||||
|
||||
from base import VersionedService
|
||||
from config_tempest.constants import LOG
|
||||
|
||||
|
||||
class NetworkService(VersionedService):
|
||||
def set_extensions(self):
|
||||
body = self.do_get(self.service_url + '/v2.0/extensions.json')
|
||||
body = json.loads(body)
|
||||
self.extensions = map(lambda x: x['alias'], body['extensions'])
|
||||
|
||||
def create_tempest_networks(self, has_neutron, conf, network_id):
|
||||
LOG.info("Setting up network")
|
||||
LOG.debug("Is neutron present: {0}".format(has_neutron))
|
||||
if has_neutron:
|
||||
self.client = self.client.get_neutron_client()
|
||||
self.create_tempest_networks_neutron(conf, network_id)
|
||||
else:
|
||||
self.client = self.client.get_nova_net_client()
|
||||
self.create_tempest_networks_nova(conf)
|
||||
|
||||
def create_tempest_networks_neutron(self, conf, public_network_id):
|
||||
self._public_network_name = None
|
||||
self._public_network_id = public_network_id
|
||||
# if user supplied the network we should use
|
||||
if public_network_id:
|
||||
self._supplied_network()
|
||||
# no network id provided, try to auto discover a public network
|
||||
else:
|
||||
self._discover_network()
|
||||
if self._public_network_id is not None:
|
||||
conf.set('network', 'public_network_id', self._public_network_id)
|
||||
if self._public_network_name is not None:
|
||||
conf.set('network', 'floating_network_name',
|
||||
self._public_network_name)
|
||||
|
||||
def _supplied_network(self):
|
||||
LOG.info("Looking for existing network id: {0}"
|
||||
"".format(self._public_network_id))
|
||||
# check if network exists
|
||||
network_list = self.client.list_networks()
|
||||
for network in network_list['networks']:
|
||||
if network['id'] == self._public_network_id:
|
||||
self._public_network_name = network['name']
|
||||
break
|
||||
else:
|
||||
raise ValueError('provided network id: {0} was not found.'
|
||||
''.format(self._public_network_id))
|
||||
|
||||
def _discover_network(self):
|
||||
LOG.info("No network supplied, trying auto discover for network")
|
||||
network_list = self.client.list_networks()
|
||||
for network in network_list['networks']:
|
||||
if network['router:external'] and network['subnets']:
|
||||
LOG.info("Found network, using: {0}".format(network['id']))
|
||||
self._public_network_id = network['id']
|
||||
self._public_network_name = network['name']
|
||||
break
|
||||
|
||||
# Couldn't find an existing external network
|
||||
else:
|
||||
LOG.error("No external networks found. "
|
||||
"Please note that any test that relies on external "
|
||||
"connectivity would most likely fail.")
|
||||
|
||||
def create_tempest_networks_nova(self, conf):
|
||||
networks = self.client.list_networks()
|
||||
if networks:
|
||||
label = networks['networks'][0]['label']
|
||||
if label:
|
||||
conf.set('compute', 'fixed_network_name', label)
|
||||
else:
|
||||
raise Exception('fixed_network_name could not be '
|
||||
'discovered and must be specified')
|
31
config_tempest/services/object_storage.py
Normal file
31
config_tempest/services/object_storage.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright 2016 Red Hat, 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 json
|
||||
|
||||
from base import Service
|
||||
|
||||
|
||||
class ObjectStorageService(Service):
|
||||
def set_extensions(self, object_store_discovery=False):
|
||||
if not object_store_discovery:
|
||||
self.extensions = []
|
||||
elif 'v3' not in self.service_url: # it's not a v3 url
|
||||
body = self.do_get(self.service_url, top_level=True,
|
||||
top_level_path="info")
|
||||
body = json.loads(body)
|
||||
# Remove Swift general information from extensions list
|
||||
body.pop('swift')
|
||||
self.extensions = body.keys()
|
204
config_tempest/services/services.py
Normal file
204
config_tempest/services/services.py
Normal file
@ -0,0 +1,204 @@
|
||||
# Copyright 2013, 2016, 2018 Red Hat, 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 urlparse
|
||||
|
||||
from base import Service
|
||||
from compute import ComputeService
|
||||
import config_tempest.constants as C
|
||||
from identity import IdentityService
|
||||
from image import ImageService
|
||||
from network import NetworkService
|
||||
from object_storage import ObjectStorageService
|
||||
from volume import VolumeService
|
||||
|
||||
service_dict = {'compute': ComputeService,
|
||||
'image': ImageService,
|
||||
'network': NetworkService,
|
||||
'object-store': ObjectStorageService,
|
||||
'volumev3': VolumeService,
|
||||
'identity': IdentityService}
|
||||
|
||||
|
||||
class Services(object):
|
||||
def __init__(self, clients, conf, creds):
|
||||
self._clients = clients
|
||||
self._conf = conf
|
||||
self._creds = creds
|
||||
swift_discover = conf.get_defaulted('object-storage-feature-enabled',
|
||||
'discoverability')
|
||||
self._object_store_discovery = conf.get_bool_value(swift_discover)
|
||||
self._ssl_validation = creds.disable_ssl_certificate_validation
|
||||
self._region = clients.identity_region
|
||||
self._services = []
|
||||
self.set_catalog_and_url()
|
||||
|
||||
self.discover()
|
||||
|
||||
def discover(self):
|
||||
token, auth_data = self._clients.auth_provider.get_auth()
|
||||
|
||||
for entry in auth_data[self.service_catalog]:
|
||||
name = entry['type']
|
||||
ep = self.get_endpoints(entry)
|
||||
|
||||
url = ep[self.public_url]
|
||||
if 'identity' in url:
|
||||
url = self.edit_identity_url(ep[self.public_url])
|
||||
|
||||
service_class = self.get_service_class(name)
|
||||
service = service_class(name, url, token, self._ssl_validation,
|
||||
self._clients.get_service_client(name))
|
||||
# discover extensions of the service
|
||||
if name == 'object-store':
|
||||
service.set_extensions(self._object_store_discovery)
|
||||
else:
|
||||
service.set_extensions()
|
||||
# discover versions of the service
|
||||
service.set_versions()
|
||||
|
||||
self._services.append(service)
|
||||
|
||||
def get_endpoints(self, entry):
|
||||
for ep in entry['endpoints']:
|
||||
if self._creds.api_version == 3:
|
||||
if (ep['region'] == self._region and
|
||||
ep['interface'] == 'public'):
|
||||
return ep
|
||||
else:
|
||||
if ep['region'] == self._region:
|
||||
return ep
|
||||
try:
|
||||
return entry['endpoints'][0]
|
||||
except IndexError:
|
||||
return []
|
||||
|
||||
def set_catalog_and_url(self):
|
||||
if self._creds.api_version == 3:
|
||||
self.service_catalog = 'catalog'
|
||||
self.public_url = 'url'
|
||||
else:
|
||||
self.service_catalog = 'serviceCatalog'
|
||||
self.public_url = 'publicURL'
|
||||
|
||||
def edit_identity_url(self, url):
|
||||
"""A port and identity version are added to url if contains 'identity'
|
||||
|
||||
:param url: url address of an endpoint
|
||||
:type url: string
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
# self._clients.auth_provider.auth_url stores identity.uri(_v3) value
|
||||
# from TempestConf
|
||||
port = urlparse.urlparse(self._clients.auth_provider.auth_url).port
|
||||
if port is None:
|
||||
port = ""
|
||||
else:
|
||||
port = ":" + str(port)
|
||||
replace_text = port + "/identity/" + self._creds.identity_version
|
||||
return url.replace("/identity", replace_text)
|
||||
|
||||
def get_service_class(self, name):
|
||||
"""Returns class name by the service name
|
||||
|
||||
:param name: Codename of a service
|
||||
:type name: string
|
||||
:return: Class name of the service
|
||||
:rtype: string
|
||||
"""
|
||||
return service_dict.get(name, Service)
|
||||
|
||||
def get_service(self, name):
|
||||
"""Finds and returns a service object
|
||||
|
||||
:param name: Codename of a service
|
||||
:type name: string
|
||||
:return: Service object
|
||||
"""
|
||||
for service in self._services:
|
||||
if service.name == name:
|
||||
return service
|
||||
return None
|
||||
|
||||
def is_service(self, name):
|
||||
"""Returns true if a service is available, false otherwise
|
||||
|
||||
:param name: Codename of a service
|
||||
:type name: string
|
||||
:rtype: boolean
|
||||
"""
|
||||
if self.get_service(name) is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_service_availability(self):
|
||||
# check if volume service is disabled
|
||||
if self._conf.has_option('services', 'volume'):
|
||||
if not self._conf.getboolean('services', 'volume'):
|
||||
C.SERVICE_NAMES.pop('volume')
|
||||
C.SERVICE_VERSIONS.pop('volume')
|
||||
|
||||
for service, codename in C.SERVICE_NAMES.iteritems():
|
||||
# ceilometer is still transitioning from metering to telemetry
|
||||
if service == 'telemetry' and self.is_service('metering'):
|
||||
service = 'metering'
|
||||
available = str(self.is_service(service))
|
||||
self._conf.set('service_available', codename, available)
|
||||
|
||||
# TODO(arxcruz): Remove this once/if we get the following reviews
|
||||
# merged in all branches supported by tempestconf, or once/if
|
||||
# tempestconf do not support anymore the OpenStack release where
|
||||
# those patches are not available.
|
||||
# https://review.openstack.org/#/c/492526/
|
||||
# https://review.openstack.org/#/c/492525/
|
||||
|
||||
if self.is_service('alarming'):
|
||||
self._conf.set('service_available', 'aodh', 'True')
|
||||
self._conf.set('service_available', 'aodh_plugin', 'True')
|
||||
|
||||
def set_supported_api_versions(self):
|
||||
# set supported API versions for services with more of them
|
||||
for service, service_info in C.SERVICE_VERSIONS.iteritems():
|
||||
service_object = self.get_service(service_info['catalog'])
|
||||
if service_object is None:
|
||||
supported_versions = []
|
||||
else:
|
||||
supported_versions = service_object.get_versions()
|
||||
section = service + '-feature-enabled'
|
||||
for version in service_info['supported_versions']:
|
||||
is_supported = any(version in item
|
||||
for item in supported_versions)
|
||||
self._conf.set(section, 'api_' + version, str(is_supported))
|
||||
|
||||
def set_service_extensions(self):
|
||||
postfix = "-feature-enabled"
|
||||
keystone_v3_support = self._conf.get('identity' + postfix, 'api_v3')
|
||||
if keystone_v3_support:
|
||||
self.get_service('identity').set_identity_v3_extensions()
|
||||
|
||||
for service, ext_key in C.SERVICE_EXTENSION_KEY.iteritems():
|
||||
if not self.is_service(service):
|
||||
continue
|
||||
service_object = self.get_service(service)
|
||||
if service_object is not None:
|
||||
extensions = ','.join(service_object.get_extensions())
|
||||
|
||||
if service == 'object-store':
|
||||
# tempest.conf is inconsistent and uses 'object-store' for
|
||||
# the catalog name but 'object-storage-feature-enabled'
|
||||
service = 'object-storage'
|
||||
|
||||
self._conf.set(service + postfix, ext_key, extensions)
|
55
config_tempest/services/volume.py
Normal file
55
config_tempest/services/volume.py
Normal file
@ -0,0 +1,55 @@
|
||||
# Copyright 2013, 2016 Red Hat, 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 json
|
||||
|
||||
from base import VersionedService
|
||||
import config_tempest.constants as C
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
class VolumeService(VersionedService):
|
||||
def set_extensions(self):
|
||||
body = self.do_get(self.service_url + '/extensions')
|
||||
body = json.loads(body)
|
||||
self.extensions = map(lambda x: x['alias'], body['extensions'])
|
||||
|
||||
def set_versions(self):
|
||||
url, top_level = self.no_port_cut_url()
|
||||
body = self.do_get(url, top_level=top_level)
|
||||
body = json.loads(body)
|
||||
self.versions = self.deserialize_versions(body)
|
||||
|
||||
|
||||
def check_volume_backup_service(conf, volume_client, is_volumev3):
|
||||
"""Verify if the cinder backup service is enabled"""
|
||||
if not is_volumev3:
|
||||
C.LOG.info("No volume service found, "
|
||||
"skipping backup service check")
|
||||
return
|
||||
try:
|
||||
params = {'binary': 'cinder-backup'}
|
||||
is_backup = volume_client.list_services(**params)
|
||||
except exceptions.Forbidden:
|
||||
C.LOG.warning("User has no permissions to list services - "
|
||||
"cinder-backup service can't be discovered.")
|
||||
return
|
||||
|
||||
if is_backup:
|
||||
# We only set backup to false if the service isn't running
|
||||
# otherwise we keep the default value
|
||||
service = is_backup['services']
|
||||
if not service or service[0]['state'] == 'down':
|
||||
conf.set('volume-feature-enabled', 'backup', 'False')
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
# Copyright 2016, 2017 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -14,14 +14,12 @@
|
||||
# under the License.
|
||||
|
||||
import ConfigParser
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from constants import LOG
|
||||
from oslo_config import cfg
|
||||
import tempest.config
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TempestConf(ConfigParser.SafeConfigParser):
|
||||
# causes the config parser to preserve case of the options
|
||||
|
@ -20,7 +20,6 @@ import json
|
||||
import mock
|
||||
from oslotest import base
|
||||
|
||||
from config_tempest import api_discovery as api
|
||||
from config_tempest.clients import ClientManager
|
||||
from config_tempest.credentials import Credentials
|
||||
from config_tempest import tempest_conf
|
||||
@ -83,6 +82,8 @@ class BaseServiceTest(base.BaseTestCase):
|
||||
"""Test case base class for all api_discovery unit tests"""
|
||||
|
||||
FAKE_TOKEN = "s6d5f45sdf4s564f4s6464sdfsd514"
|
||||
FAKE_CLIENT_MOCK = 'config_tempest.tests.base.BaseServiceTest' + \
|
||||
'.FakeServiceClient'
|
||||
FAKE_HEADERS = {
|
||||
'Accept': 'application/json', 'X-Auth-Token': FAKE_TOKEN
|
||||
}
|
||||
@ -131,6 +132,14 @@ class BaseServiceTest(base.BaseTestCase):
|
||||
}
|
||||
}
|
||||
)
|
||||
FAKE_IDENTITY_VERSION = (
|
||||
{
|
||||
'version': {
|
||||
'status': 'stable',
|
||||
'id': 'v3.8',
|
||||
}
|
||||
}
|
||||
)
|
||||
FAKE_EXTENSIONS = (
|
||||
{
|
||||
"extensions": [{
|
||||
@ -202,22 +211,54 @@ class BaseServiceTest(base.BaseTestCase):
|
||||
def __init__(self):
|
||||
self.content = json.dumps(self.FAKE_V3_EXTENSIONS)
|
||||
|
||||
class FakeServiceClient(object):
|
||||
def __init__(self, services=None):
|
||||
self.client = mock.Mock()
|
||||
self.return_value = mock.Mock()
|
||||
self.services = services
|
||||
|
||||
def list_networks(self):
|
||||
return self.return_value
|
||||
|
||||
def list_services(self, **kwargs):
|
||||
return self.services
|
||||
|
||||
def _fake_service_do_get_method(self, fake_data):
|
||||
function2mock = 'config_tempest.api_discovery.Service.do_get'
|
||||
function2mock = 'config_tempest.services.base.Service.do_get'
|
||||
do_get_output = json.dumps(fake_data)
|
||||
mocked_do_get = mock.Mock()
|
||||
mocked_do_get.return_value = do_get_output
|
||||
self.useFixture(MonkeyPatch(function2mock, mocked_do_get))
|
||||
|
||||
def _test_get_service_class(self, service, cls):
|
||||
resp = api.get_service_class(service)
|
||||
self.assertEqual(resp, cls)
|
||||
|
||||
def _get_extensions(self, service, expected_resp, fake_data):
|
||||
def _set_get_extensions(self, service, expected_resp, fake_data):
|
||||
# mock do_get response
|
||||
self._fake_service_do_get_method(fake_data)
|
||||
# set the fake extensions
|
||||
service.set_extensions()
|
||||
# check if extensions were set
|
||||
self.assertItemsEqual(service.extensions, expected_resp)
|
||||
# check if get method returns the right data
|
||||
resp = service.get_extensions()
|
||||
self.assertItemsEqual(resp, expected_resp)
|
||||
|
||||
def _set_get_versions(self, service, expected_resp, fake_data):
|
||||
# mock do_get response
|
||||
self._fake_service_do_get_method(fake_data)
|
||||
# set the fake versions
|
||||
service.set_versions()
|
||||
# check if versions were set
|
||||
self.assertItemsEqual(service.versions, expected_resp)
|
||||
# check if get method returns the right data
|
||||
resp = service.get_versions()
|
||||
self.assertItemsEqual(resp, expected_resp)
|
||||
|
||||
def _test_deserialize_versions(self, service, expected_resp, fake_data):
|
||||
resp = service.deserialize_versions(fake_data)
|
||||
self.assertItemsEqual(resp, expected_resp)
|
||||
|
||||
def _assert_conf_get_not_raises(self, exc, section, value):
|
||||
try:
|
||||
self.conf.get(section, value)
|
||||
except exc:
|
||||
return
|
||||
self.assertTrue(False)
|
||||
|
0
config_tempest/tests/services/__init__.py
Normal file
0
config_tempest/tests/services/__init__.py
Normal file
105
config_tempest/tests/services/test_base.py
Normal file
105
config_tempest/tests/services/test_base.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright 2018 Red Hat, 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 mock
|
||||
import unittest
|
||||
|
||||
from config_tempest.services.base import Service
|
||||
from config_tempest.services.base import VersionedService
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestService, self).setUp()
|
||||
self.Service = Service("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def _mocked_do_get(self, mock_urllib3):
|
||||
mock_http = mock_urllib3.PoolManager()
|
||||
expected_resp = mock_http.request('GET',
|
||||
self.FAKE_URL,
|
||||
self.FAKE_HEADERS)
|
||||
return expected_resp.data
|
||||
|
||||
@unittest.skip('Failing due to Storyboard: 2001245')
|
||||
@mock.patch('config_tempest.api_discovery.urllib3')
|
||||
def test_do_get(self, mock_urllib3):
|
||||
resp = self.Service.do_get(self.FAKE_URL)
|
||||
expected_resp = self._mocked_do_get(mock_urllib3)
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
def test_service_properties(self):
|
||||
self.assertEqual(self.Service.name, "ServiceName")
|
||||
self.assertEqual(self.Service.service_url, self.FAKE_URL)
|
||||
self.assertEqual(self.Service.headers, self.FAKE_HEADERS)
|
||||
self.assertEqual(self.Service.disable_ssl_validation, False)
|
||||
self.assertEqual(self.Service.client, None)
|
||||
self.assertEqual(self.Service.extensions, [])
|
||||
self.assertEqual(self.Service.versions, [])
|
||||
|
||||
def test_set_extensions(self):
|
||||
self.Service.extensions = ['ext']
|
||||
self.Service.set_extensions()
|
||||
self.assertEqual(self.Service.extensions, [])
|
||||
|
||||
def test_set_versions(self):
|
||||
self.Service.versions = ['ver']
|
||||
self.Service.set_versions()
|
||||
self.assertEqual(self.Service.versions, [])
|
||||
|
||||
def test_get_extensions(self):
|
||||
self.Service.extensions = ['ext']
|
||||
self.assertEqual(self.Service.get_extensions(), ['ext'])
|
||||
|
||||
def test_get_versions(self):
|
||||
self.Service.versions = ['ver']
|
||||
self.assertEqual(self.Service.get_versions(), ['ver'])
|
||||
|
||||
|
||||
class TestVersionedService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestVersionedService, self).setUp()
|
||||
self.Service = VersionedService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_set_get_versions(self):
|
||||
expected_resp = ['v2.0', 'v2.1']
|
||||
self._fake_service_do_get_method(self.FAKE_VERSIONS)
|
||||
self.Service.set_versions()
|
||||
resp = self.Service.get_versions()
|
||||
self.assertItemsEqual(resp, expected_resp)
|
||||
self.assertItemsEqual(self.Service.versions, expected_resp)
|
||||
|
||||
def test_deserialize_versions(self):
|
||||
expected_resp = ['v2.0', 'v2.1']
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_VERSIONS)
|
||||
|
||||
def test_no_port_cut_url(self):
|
||||
resp = self.Service.no_port_cut_url()
|
||||
self.assertEqual(resp, (self.FAKE_URL, True))
|
||||
url = "http://10.200.16.10"
|
||||
self.Service.service_url = url
|
||||
resp = self.Service.no_port_cut_url()
|
||||
self.assertEqual(resp, (self.Service.service_url, False))
|
||||
self.Service.service_url = url + "/v3/cut/it/off"
|
||||
resp = self.Service.no_port_cut_url()
|
||||
self.assertEqual(resp, (url + '/', False))
|
47
config_tempest/tests/services/test_boto.py
Normal file
47
config_tempest/tests/services/test_boto.py
Normal file
@ -0,0 +1,47 @@
|
||||
# Copyright 2018 Red Hat, 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 ConfigParser
|
||||
|
||||
from config_tempest.services import base
|
||||
from config_tempest.services import boto
|
||||
from config_tempest.tempest_conf import TempestConf
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestBotoService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestBotoService, self).setUp()
|
||||
self.conf = TempestConf()
|
||||
self.es2 = base.Service("ec2",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
self.s3 = base.Service("s3",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_configure_boto(self):
|
||||
boto.configure_boto(self.conf)
|
||||
self._assert_conf_get_not_raises(ConfigParser.NoSectionError,
|
||||
"boto",
|
||||
"ec2_url")
|
||||
self._assert_conf_get_not_raises(ConfigParser.NoSectionError,
|
||||
"boto",
|
||||
"s3_url")
|
||||
boto.configure_boto(self.conf, self.es2, self.s3)
|
||||
self.assertEqual(self.conf.get("boto", "ec2_url"), self.FAKE_URL)
|
||||
self.assertEqual(self.conf.get("boto", "s3_url"), self.FAKE_URL)
|
46
config_tempest/tests/services/test_ceilometer.py
Normal file
46
config_tempest/tests/services/test_ceilometer.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright 2018 Red Hat, 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 ConfigParser
|
||||
|
||||
from config_tempest.services import ceilometer
|
||||
from config_tempest.tempest_conf import TempestConf
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestCeilometerService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestCeilometerService, self).setUp()
|
||||
self.conf = TempestConf()
|
||||
|
||||
def test_check_ceilometer_service(self):
|
||||
client_service_mock = self.FakeServiceClient(services={})
|
||||
ceilometer.check_ceilometer_service(self.conf, client_service_mock)
|
||||
|
||||
self._assert_conf_get_not_raises(ConfigParser.NoSectionError,
|
||||
"service_available",
|
||||
"ceilometer")
|
||||
|
||||
client_service_mock = self.FakeServiceClient(services={
|
||||
'services': [
|
||||
{
|
||||
"name": "ceilometer",
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
})
|
||||
ceilometer.check_ceilometer_service(self.conf, client_service_mock)
|
||||
self.assertEqual(self.conf.get('service_available', 'ceilometer'),
|
||||
'True')
|
34
config_tempest/tests/services/test_compute.py
Normal file
34
config_tempest/tests/services/test_compute.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright 2018 Red Hat, 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.
|
||||
|
||||
from config_tempest.services.compute import ComputeService
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestComputeService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestComputeService, self).setUp()
|
||||
self.Service = ComputeService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_set_get_extensions(self):
|
||||
exp_resp = ['NMN', 'OS-DCF']
|
||||
self._set_get_extensions(self.Service, exp_resp, self.FAKE_EXTENSIONS)
|
||||
|
||||
def test_set_get_versions(self):
|
||||
exp_resp = ['v2.0', 'v2.1']
|
||||
self._set_get_versions(self.Service, exp_resp, self.FAKE_VERSIONS)
|
48
config_tempest/tests/services/test_horizon.py
Normal file
48
config_tempest/tests/services/test_horizon.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Copyright 2018 Red Hat, 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.
|
||||
|
||||
from fixtures import MonkeyPatch
|
||||
import mock
|
||||
|
||||
from config_tempest.services import horizon
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
|
||||
|
||||
class TestConfigTempest(BaseConfigTempestTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestConfigTempest, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
|
||||
def test_configure_horizon_ipv4(self):
|
||||
mock_function = mock.Mock(return_value=True)
|
||||
self.useFixture(MonkeyPatch('urllib2.urlopen', mock_function))
|
||||
horizon.configure_horizon(self.conf)
|
||||
self.assertEqual(self.conf.get('service_available', 'horizon'), "True")
|
||||
self.assertEqual(self.conf.get('dashboard', 'dashboard_url'),
|
||||
"http://172.16.52.151/dashboard/")
|
||||
self.assertEqual(self.conf.get('dashboard', 'login_url'),
|
||||
"http://172.16.52.151/dashboard/auth/login/")
|
||||
|
||||
def test_configure_horizon_ipv6(self):
|
||||
mock_function = mock.Mock(return_value=True)
|
||||
self.useFixture(MonkeyPatch('urllib2.urlopen', mock_function))
|
||||
self.conf.set('identity', 'uri', 'http://[::1]:5000/v3', priority=True)
|
||||
horizon.configure_horizon(self.conf)
|
||||
self.assertEqual(self.conf.get('service_available', 'horizon'), "True")
|
||||
self.assertEqual(self.conf.get('dashboard', 'dashboard_url'),
|
||||
"http://[::1]/dashboard/")
|
||||
self.assertEqual(self.conf.get('dashboard', 'login_url'),
|
||||
"http://[::1]/dashboard/auth/login/")
|
90
config_tempest/tests/services/test_identity.py
Normal file
90
config_tempest/tests/services/test_identity.py
Normal file
@ -0,0 +1,90 @@
|
||||
# Copyright 2018 Red Hat, 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.
|
||||
|
||||
from fixtures import MonkeyPatch
|
||||
import mock
|
||||
|
||||
from config_tempest.services.identity import IdentityService
|
||||
from config_tempest.tempest_conf import TempestConf
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestIdentityService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestIdentityService, self).setUp()
|
||||
self.Service = IdentityService("ServiceName",
|
||||
self.FAKE_URL + 'v2.0/',
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_set_extensions(self):
|
||||
expected_resp = ['OS-DCF', 'NMN']
|
||||
self._set_get_extensions(self.Service, expected_resp,
|
||||
self.FAKE_IDENTITY_EXTENSIONS)
|
||||
# try with URL containing v3 version
|
||||
self.Service.service_url = self.FAKE_URL + 'v3'
|
||||
expected_resp = []
|
||||
self._set_get_extensions(self.Service, expected_resp,
|
||||
self.FAKE_IDENTITY_EXTENSIONS)
|
||||
|
||||
def test_get_extensions(self):
|
||||
exp_resp = ['OS-INHERIT', 'OS-OAUTH1',
|
||||
'OS-SIMPLE-CERT', 'OS-EP-FILTER']
|
||||
self.Service.extensions = exp_resp[:2]
|
||||
self.Service.extensions_v3 = exp_resp[2:]
|
||||
self.assertItemsEqual(self.Service.get_extensions(), exp_resp)
|
||||
|
||||
def test_set_identity_v3_extensions(self):
|
||||
expected_resp = ['OS-INHERIT', 'OS-OAUTH1',
|
||||
'OS-SIMPLE-CERT', 'OS-EP-FILTER']
|
||||
fake_resp = self.FakeRequestResponse()
|
||||
mocked_requests = mock.Mock()
|
||||
mocked_requests.return_value = fake_resp
|
||||
self.useFixture(MonkeyPatch('requests.get', mocked_requests))
|
||||
self.Service.service_url = self.FAKE_URL + "v3"
|
||||
self.Service.set_identity_v3_extensions()
|
||||
self.assertItemsEqual(self.Service.extensions_v3, expected_resp)
|
||||
self.assertItemsEqual(self.Service.get_extensions(), expected_resp)
|
||||
|
||||
def test_set_get_versions(self):
|
||||
exp_resp = ['v3.8']
|
||||
self._set_get_versions(self.Service, exp_resp,
|
||||
self.FAKE_IDENTITY_VERSIONS)
|
||||
|
||||
def test_deserialize_versions(self):
|
||||
expected_resp = ['v3.8']
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_IDENTITY_VERSIONS)
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_IDENTITY_VERSION)
|
||||
expected_resp = ['v2.1', 'v3.8']
|
||||
# add not deprecated v2 version to FAKE_IDENTITY_VERSIONS
|
||||
v2 = {'status': 'stable', 'id': 'v2.1'}
|
||||
self.FAKE_IDENTITY_VERSIONS['versions']['values'].append(v2)
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_IDENTITY_VERSIONS)
|
||||
|
||||
@mock.patch('config_tempest.services.identity'
|
||||
'.IdentityService.get_versions')
|
||||
def test_configure_keystone_feature_flags(self, mock_get_versions):
|
||||
conf = TempestConf()
|
||||
mock_get_versions.return_value = ['v3.8', 'v2.0']
|
||||
self.Service.configure_keystone_feature_flags(conf)
|
||||
self.assertEqual(
|
||||
conf.get('identity-feature-enabled',
|
||||
'forbid_global_implied_dsr'), 'True')
|
@ -1,6 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 Red Hat, Inc.
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -18,47 +16,86 @@
|
||||
from fixtures import MonkeyPatch
|
||||
import mock
|
||||
|
||||
from config_tempest import main as tool
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
from config_tempest.services.image import ImageService
|
||||
from config_tempest.tempest_conf import TempestConf
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestCreateTempestImages(BaseConfigTempestTest):
|
||||
class TestImageService(BaseServiceTest):
|
||||
|
||||
CLIENT_MOCK = 'tempest.lib.services.image.v2.images_client.ImagesClient'
|
||||
IMAGES_LIST = [
|
||||
{"status": "active", "name": "ImageName"},
|
||||
{"status": "default", "name": "MyImage"},
|
||||
{"status": "active", "name": "MyImage"}
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateTempestImages, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(self.conf).images
|
||||
self.image_path = "my_path/my_image.qcow2"
|
||||
self.allow_creation = False
|
||||
self.disk_format = ".format"
|
||||
super(TestImageService, self).setUp()
|
||||
self.Service = ImageService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
self.Service.allow_creation = False
|
||||
self.Service.image_path = "my_path/my_image.qcow2"
|
||||
self.Service.disk_format = ".format"
|
||||
self.Service.client = self.FakeServiceClient()
|
||||
|
||||
self.dir = "/img/"
|
||||
self.conf = TempestConf()
|
||||
self.conf.set("scenario", "img_dir", self.dir)
|
||||
|
||||
@mock.patch('config_tempest.main.find_or_upload_image')
|
||||
def test_create_tempest_images_exception(self, mock_find_upload):
|
||||
mock_find_upload.side_effect = Exception
|
||||
exc = Exception
|
||||
self.assertRaises(exc,
|
||||
tool.create_tempest_images,
|
||||
client=self.client,
|
||||
conf=self.conf,
|
||||
image_path=self.image_path,
|
||||
allow_creation=self.allow_creation,
|
||||
disk_format=self.disk_format)
|
||||
|
||||
@mock.patch('config_tempest.main.find_or_upload_image')
|
||||
@mock.patch('config_tempest.services.image.ImageService'
|
||||
'.find_or_upload_image')
|
||||
def _test_create_tempest_images(self, mock_find_upload):
|
||||
mock_find_upload.side_effect = ["id_c", "id_d"]
|
||||
tool.create_tempest_images(client=self.client,
|
||||
conf=self.conf,
|
||||
image_path=self.image_path,
|
||||
allow_creation=self.allow_creation,
|
||||
disk_format=self.disk_format)
|
||||
self.Service.create_tempest_images(conf=self.conf)
|
||||
self.assertEqual(self.conf.get('compute', 'image_ref'), 'id_c')
|
||||
self.assertEqual(self.conf.get('compute', 'image_ref_alt'), 'id_d')
|
||||
self.assertEqual(self.conf.get('scenario', 'img_file'),
|
||||
'my_image.qcow2')
|
||||
|
||||
@mock.patch('config_tempest.services.image.ImageService._find_image')
|
||||
@mock.patch('config_tempest.services.image.ImageService._download_file')
|
||||
@mock.patch('config_tempest.services.image.ImageService._upload_image')
|
||||
def _test_find_or_upload_image_not_found_creation_allowed_format(
|
||||
self, mock_upload_image,
|
||||
mock_download_file, mock_find_image, format):
|
||||
mock_find_image.return_value = None
|
||||
mock_upload_image.return_value = {"id": "my_fake_id"}
|
||||
image_source = format + "://any_random_url"
|
||||
image_dest = "my_dest"
|
||||
image_name = "my_image"
|
||||
self.Service.allow_creation = True
|
||||
image_id = self.Service.find_or_upload_image(
|
||||
image_id=None, image_dest=image_dest,
|
||||
image_name=image_name, image_source=image_source)
|
||||
mock_download_file.assert_called_with(image_source, image_dest)
|
||||
mock_upload_image.assert_called_with(image_name, image_dest)
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
def _mock_list_images(self, return_value, image_name, expected_resp):
|
||||
|
||||
mock_function = mock.Mock(return_value=return_value)
|
||||
func2mock = self.FAKE_CLIENT_MOCK + '.list_images'
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
resp = self.Service._find_image(image_id=None,
|
||||
image_name=image_name)
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
def test_set_get_versions(self):
|
||||
exp_resp = ['v2.0', 'v2.1']
|
||||
self._set_get_versions(self.Service, exp_resp, self.FAKE_VERSIONS)
|
||||
|
||||
@mock.patch('config_tempest.services.image.ImageService'
|
||||
'.find_or_upload_image')
|
||||
def test_create_tempest_images_exception(self, mock_find_upload):
|
||||
mock_find_upload.side_effect = Exception
|
||||
exc = Exception
|
||||
self.assertRaises(exc,
|
||||
self.Service.create_tempest_images,
|
||||
conf=self.conf)
|
||||
|
||||
def test_create_tempest_images_ref_alt_ref(self):
|
||||
self.conf.set('compute', 'image_ref', 'id_a')
|
||||
self.conf.set('compute', 'image_ref_alt', 'id_b')
|
||||
@ -75,29 +112,68 @@ class TestCreateTempestImages(BaseConfigTempestTest):
|
||||
def test_create_tempest_images_no_ref_no_alt_ref(self):
|
||||
self._test_create_tempest_images()
|
||||
|
||||
@mock.patch('config_tempest.services.image.ImageService._find_image')
|
||||
def test_find_or_upload_image_not_found_creation_not_allowed(
|
||||
self, mock_find_image):
|
||||
mock_find_image.return_value = None
|
||||
exc = Exception
|
||||
self.assertRaises(exc, self.Service.find_or_upload_image,
|
||||
image_id=None, image_name=None)
|
||||
|
||||
class TestFindImage(BaseConfigTempestTest):
|
||||
def test_find_or_upload_image_not_found_creation_allowed_http(self):
|
||||
self._test_find_or_upload_image_not_found_creation_allowed_format(
|
||||
format="http")
|
||||
|
||||
CLIENT_MOCK = 'tempest.lib.services.image.v2.images_client.ImagesClient'
|
||||
IMAGES_LIST = [
|
||||
{"status": "active", "name": "ImageName"},
|
||||
{"status": "default", "name": "MyImage"},
|
||||
{"status": "active", "name": "MyImage"}
|
||||
]
|
||||
def test_find_or_upload_image_not_found_creation_allowed_https(self):
|
||||
self._test_find_or_upload_image_not_found_creation_allowed_format(
|
||||
format="https")
|
||||
|
||||
def setUp(self):
|
||||
super(TestFindImage, self).setUp()
|
||||
conf = self._get_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(conf).images
|
||||
@mock.patch('shutil.copyfile')
|
||||
@mock.patch('config_tempest.services.image.ImageService._find_image')
|
||||
@mock.patch('config_tempest.services.image.ImageService._download_file')
|
||||
@mock.patch('config_tempest.services.image.ImageService._upload_image')
|
||||
def test_find_or_upload_image_not_found_creation_allowed_ftp_old(
|
||||
self, mock_upload_image, mock_download_file, mock_find_image,
|
||||
mock_copy):
|
||||
mock_find_image.return_value = None
|
||||
mock_upload_image.return_value = {"id": "my_fake_id"}
|
||||
# image source does not start with http or https
|
||||
image_source = "ftp://any_random_url"
|
||||
image_dest = "place_on_disk"
|
||||
image_name = "my_image"
|
||||
self.Service.allow_creation = True
|
||||
image_id = self.Service.find_or_upload_image(
|
||||
image_id=None, image_name=image_name,
|
||||
image_source=image_source, image_dest=image_dest)
|
||||
mock_copy.assert_called_with(image_source, image_dest)
|
||||
mock_upload_image.assert_called_with(
|
||||
image_name, image_dest)
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
def _mock_list_images(self, return_value, image_name, expected_resp):
|
||||
mock_function = mock.Mock(return_value=return_value)
|
||||
func2mock = self.CLIENT_MOCK + '.list_images'
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
resp = tool._find_image(client=self.client,
|
||||
image_id=None,
|
||||
image_name=image_name)
|
||||
self.assertEqual(resp, expected_resp)
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('config_tempest.services.image.ImageService._find_image')
|
||||
def test_find_or_upload_image_found_downloaded(
|
||||
self, mock_find_image, mock_isfile):
|
||||
mock_find_image.return_value = \
|
||||
{"status": "active", "name": "ImageName", "id": "my_fake_id"}
|
||||
mock_isfile.return_value = True
|
||||
image_id = self.Service.find_or_upload_image(
|
||||
image_id=None, image_name=None)
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
@mock.patch('config_tempest.services.image.ImageService._download_image')
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('config_tempest.services.image.ImageService._find_image')
|
||||
def test_find_or_upload_image_found_not_downloaded(
|
||||
self, mock_find_image, mock_isfile, mock_download_image):
|
||||
image_id = "my_fake_id"
|
||||
mock_find_image.return_value = \
|
||||
{"status": "active", "name": "ImageName", "id": image_id}
|
||||
mock_isfile.return_value = False
|
||||
image_id = self.Service.find_or_upload_image(
|
||||
image_id=None, image_name=None)
|
||||
mock_download_image.assert_called()
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
def test_find_image_found(self):
|
||||
expected_resp = {"status": "default", "name": "MyImage"}
|
||||
@ -113,106 +189,8 @@ class TestFindImage(BaseConfigTempestTest):
|
||||
def test_find_image_by_id(self):
|
||||
expected_resp = {"id": "001", "status": "active", "name": "ImageName"}
|
||||
mock_function = mock.Mock(return_value=expected_resp)
|
||||
func2mock = self.CLIENT_MOCK + '.show_image'
|
||||
func2mock = self.FAKE_CLIENT_MOCK + '.show_image'
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
resp = tool._find_image(client=self.client,
|
||||
image_id="001",
|
||||
image_name="cirros")
|
||||
resp = self.Service._find_image(image_id="001",
|
||||
image_name="cirros")
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
|
||||
class TestFindOrUploadImage(BaseConfigTempestTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestFindOrUploadImage, self).setUp()
|
||||
conf = self._get_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(conf).images
|
||||
|
||||
@mock.patch('config_tempest.main._find_image')
|
||||
def test_find_or_upload_image_not_found_creation_not_allowed(
|
||||
self, mock_find_image):
|
||||
mock_find_image.return_value = None
|
||||
exc = Exception
|
||||
self.assertRaises(exc, tool.find_or_upload_image, client=self.client,
|
||||
image_id=None, image_name=None,
|
||||
allow_creation=False)
|
||||
|
||||
@mock.patch('config_tempest.main._find_image')
|
||||
@mock.patch('config_tempest.main._download_file')
|
||||
@mock.patch('config_tempest.main._upload_image')
|
||||
def _test_find_or_upload_image_not_found_creation_allowed_format(
|
||||
self, mock_upload_image,
|
||||
mock_download_file, mock_find_image, format):
|
||||
mock_find_image.return_value = None
|
||||
mock_upload_image.return_value = {"id": "my_fake_id"}
|
||||
image_source = format + "://any_random_url"
|
||||
image_dest = "my_dest"
|
||||
image_name = "my_image"
|
||||
disk_format = "my_format"
|
||||
image_id = tool.find_or_upload_image(
|
||||
client=self.client, image_id=None, image_dest=image_dest,
|
||||
image_name=image_name, image_source=image_source,
|
||||
allow_creation=True, disk_format=disk_format)
|
||||
mock_download_file.assert_called_with(image_source, image_dest)
|
||||
mock_upload_image.assert_called_with(self.client,
|
||||
image_name, image_dest,
|
||||
disk_format)
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
def test_find_or_upload_image_not_found_creation_allowed_http(self):
|
||||
self._test_find_or_upload_image_not_found_creation_allowed_format(
|
||||
format="http")
|
||||
|
||||
def test_find_or_upload_image_not_found_creation_allowed_https(self):
|
||||
self._test_find_or_upload_image_not_found_creation_allowed_format(
|
||||
format="https")
|
||||
|
||||
@mock.patch('shutil.copyfile')
|
||||
@mock.patch('config_tempest.main._find_image')
|
||||
@mock.patch('config_tempest.main._download_file')
|
||||
@mock.patch('config_tempest.main._upload_image')
|
||||
def test_find_or_upload_image_not_found_creation_allowed_ftp_old(
|
||||
self, mock_upload_image, mock_download_file, mock_find_image,
|
||||
mock_copy):
|
||||
mock_find_image.return_value = None
|
||||
mock_upload_image.return_value = {"id": "my_fake_id"}
|
||||
# image source does not start with http or https
|
||||
image_source = "ftp://any_random_url"
|
||||
image_dest = "place_on_disk"
|
||||
disk_format = "my_format"
|
||||
image_name = "my_image"
|
||||
image_id = tool.find_or_upload_image(
|
||||
client=self.client, image_id=None, image_name=image_name,
|
||||
image_source=image_source, image_dest=image_dest,
|
||||
allow_creation=True, disk_format=disk_format)
|
||||
mock_copy.assert_called_with(image_source, image_dest)
|
||||
mock_upload_image.assert_called_with(
|
||||
self.client, image_name, image_dest, disk_format)
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('config_tempest.main._find_image')
|
||||
def test_find_or_upload_image_found_downloaded(
|
||||
self, mock_find_image, mock_isfile):
|
||||
mock_find_image.return_value = \
|
||||
{"status": "active", "name": "ImageName", "id": "my_fake_id"}
|
||||
mock_isfile.return_value = True
|
||||
image_id = tool.find_or_upload_image(
|
||||
client=self.client, image_id=None,
|
||||
image_name=None, allow_creation=True)
|
||||
self.assertEqual(image_id, "my_fake_id")
|
||||
|
||||
@mock.patch('config_tempest.main._download_image')
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('config_tempest.main._find_image')
|
||||
def test_find_or_upload_image_found_not_downloaded(
|
||||
self, mock_find_image, mock_isfile, mock_download_image):
|
||||
image_id = "my_fake_id"
|
||||
mock_find_image.return_value = \
|
||||
{"status": "active", "name": "ImageName", "id": image_id}
|
||||
mock_isfile.return_value = False
|
||||
image_id = tool.find_or_upload_image(
|
||||
client=self.client, image_id=None,
|
||||
image_name=None, allow_creation=True)
|
||||
mock_download_image.assert_called()
|
||||
self.assertEqual(image_id, "my_fake_id")
|
101
config_tempest/tests/services/test_network.py
Normal file
101
config_tempest/tests/services/test_network.py
Normal file
@ -0,0 +1,101 @@
|
||||
# Copyright 2018 Red Hat, 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 mock
|
||||
|
||||
from config_tempest.services.network import NetworkService
|
||||
from config_tempest.tempest_conf import TempestConf
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestNetworkService(BaseServiceTest):
|
||||
|
||||
FAKE_NETWORK_LIST = {
|
||||
'networks': [{
|
||||
'provider:physical_network': None,
|
||||
'id': '1ea533d7-4c65-4f25',
|
||||
'router:external': True,
|
||||
'availability_zone_hints': [],
|
||||
'availability_zones': [],
|
||||
'ipv4_address_scope': None,
|
||||
'status': 'ACTIVE',
|
||||
'subnets': ['fake_subnet'],
|
||||
'label': 'my_fake_label',
|
||||
'name': 'tempest-network',
|
||||
'admin_state_up': True,
|
||||
}]
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestNetworkService, self).setUp()
|
||||
self.conf = TempestConf()
|
||||
self.Service = NetworkService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
self.Service.client = self.FakeServiceClient()
|
||||
|
||||
def test_set_get_extensions(self):
|
||||
exp_resp = ['NMN', 'OS-DCF']
|
||||
self._set_get_extensions(self.Service, exp_resp, self.FAKE_EXTENSIONS)
|
||||
|
||||
def test_tempest_network_id_not_found(self):
|
||||
return_mock = mock.Mock(return_value={"networks": []})
|
||||
self.Service.client.list_networks = return_mock
|
||||
self.Service._public_network_id = "doesn't_exist"
|
||||
self.assertRaises(ValueError,
|
||||
self.Service._supplied_network)
|
||||
|
||||
def test_create_network_id_supplied_by_user(self):
|
||||
return_mock = mock.Mock(return_value=self.FAKE_NETWORK_LIST)
|
||||
self.Service.client.list_networks = return_mock
|
||||
self.Service._public_network_id = '1ea533d7-4c65-4f25'
|
||||
self.Service._supplied_network()
|
||||
self.assertEqual(self.Service._public_network_name, 'tempest-network')
|
||||
|
||||
def test_create_network_auto_discover(self):
|
||||
return_mock = mock.Mock(return_value=self.FAKE_NETWORK_LIST)
|
||||
self.Service.client.list_networks = return_mock
|
||||
self.Service._discover_network()
|
||||
self.assertEqual(self.Service._public_network_id, '1ea533d7-4c65-4f25')
|
||||
self.assertEqual(self.Service._public_network_name, 'tempest-network')
|
||||
|
||||
@mock.patch('config_tempest.services.network.LOG')
|
||||
def test_create_network_auto_discover_not_found(self, mock_logging):
|
||||
# delete subnets => network will not be found
|
||||
self.FAKE_NETWORK_LIST['networks'][0]['subnets'] = []
|
||||
return_mock = mock.Mock(return_value=self.FAKE_NETWORK_LIST)
|
||||
self.Service.client.list_networks = return_mock
|
||||
self.Service._discover_network()
|
||||
# check if LOG.error was called
|
||||
self.assertTrue(mock_logging.error.called)
|
||||
|
||||
def test_network_not_discovered(self):
|
||||
FAKE_NETWORK_LIST = {
|
||||
'networks': [{
|
||||
'label': ""
|
||||
}]
|
||||
}
|
||||
exception = Exception
|
||||
self.Service.client.return_value = FAKE_NETWORK_LIST
|
||||
self.assertRaises(exception,
|
||||
self.Service.create_tempest_networks_nova,
|
||||
conf=self.conf)
|
||||
|
||||
def test_create_fixed_network(self):
|
||||
self.Service.client.return_value = self.FAKE_NETWORK_LIST
|
||||
self.Service.create_tempest_networks_nova(conf=self.conf)
|
||||
self.assertEqual(self.conf.get('compute', 'fixed_network_name'),
|
||||
'my_fake_label')
|
37
config_tempest/tests/services/test_object_storage.py
Normal file
37
config_tempest/tests/services/test_object_storage.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright 2018 Red Hat, 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.
|
||||
|
||||
from config_tempest.services.object_storage import ObjectStorageService
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestObjectStorageService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestObjectStorageService, self).setUp()
|
||||
self.Service = ObjectStorageService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_set_get_extensions(self):
|
||||
expected_resp = ['formpost', 'ratelimit',
|
||||
'methods', 'account_quotas']
|
||||
self._fake_service_do_get_method(self.FAKE_STORAGE_EXTENSIONS)
|
||||
self.Service.set_extensions(object_store_discovery=True)
|
||||
self.assertItemsEqual(self.Service.extensions, expected_resp)
|
||||
self.assertItemsEqual(self.Service.get_extensions(), expected_resp)
|
||||
self.Service.set_extensions()
|
||||
self.assertItemsEqual(self.Service.extensions, [])
|
||||
self.assertItemsEqual(self.Service.get_extensions(), [])
|
120
config_tempest/tests/services/test_services.py
Normal file
120
config_tempest/tests/services/test_services.py
Normal file
@ -0,0 +1,120 @@
|
||||
# Copyright 2018 Red Hat, 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.
|
||||
|
||||
from config_tempest.services.services import Services
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
import mock
|
||||
|
||||
|
||||
class TestServices(BaseConfigTempestTest):
|
||||
|
||||
FAKE_ENTRY = {
|
||||
'endpoints': [
|
||||
{
|
||||
'region': 'my_region',
|
||||
'interface': 'public'
|
||||
},
|
||||
{
|
||||
'region': 'other_region',
|
||||
'interface': 'private'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestServices, self).setUp()
|
||||
|
||||
@mock.patch('config_tempest.services.services.Services.discover')
|
||||
def _create_services_instance(self, mock_discover):
|
||||
conf = self._get_conf('v2', 'v3')
|
||||
creds = self._get_creds(conf)
|
||||
clients = mock.Mock()
|
||||
services = Services(clients, conf, creds)
|
||||
return services
|
||||
|
||||
def test_get_endpoints_api_2(self):
|
||||
services = self._create_services_instance()
|
||||
services._region = 'my_region'
|
||||
resp = services.get_endpoints(self.FAKE_ENTRY)
|
||||
self.assertEqual(resp, self.FAKE_ENTRY['endpoints'][0])
|
||||
services._region = 'doesnt_exist_region'
|
||||
resp = services.get_endpoints(self.FAKE_ENTRY)
|
||||
self.assertEqual(resp, self.FAKE_ENTRY['endpoints'][0])
|
||||
services._region = 'other_region'
|
||||
resp = services.get_endpoints(self.FAKE_ENTRY)
|
||||
self.assertEqual(resp, self.FAKE_ENTRY['endpoints'][1])
|
||||
|
||||
def test_get_endpoints_api_3(self):
|
||||
services = self._create_services_instance()
|
||||
services._creds.api_version = 3
|
||||
services._region = 'my_region'
|
||||
resp = services.get_endpoints(self.FAKE_ENTRY)
|
||||
self.assertEqual(resp, self.FAKE_ENTRY['endpoints'][0])
|
||||
services._region = 'other_region'
|
||||
resp = services.get_endpoints(self.FAKE_ENTRY)
|
||||
self.assertEqual(resp, self.FAKE_ENTRY['endpoints'][0])
|
||||
|
||||
def test_get_endpoints_no_endpoints(self):
|
||||
services = self._create_services_instance()
|
||||
resp = services.get_endpoints({'endpoints': []})
|
||||
self.assertEqual(resp, [])
|
||||
|
||||
def test_set_catalog_and_url(self):
|
||||
services = self._create_services_instance()
|
||||
# api version = 2
|
||||
services.set_catalog_and_url()
|
||||
self.assertEqual(services.service_catalog, 'serviceCatalog')
|
||||
self.assertEqual(services.public_url, 'publicURL')
|
||||
# api version = 3
|
||||
services._creds.api_version = 3
|
||||
services.set_catalog_and_url()
|
||||
self.assertEqual(services.service_catalog, 'catalog')
|
||||
self.assertEqual(services.public_url, 'url')
|
||||
|
||||
def test_edit_identity_url(self):
|
||||
services = self._create_services_instance()
|
||||
url_port = 'https://10.0.0.101:13000/v2.0'
|
||||
identity_url = 'https://10.0.0.101/identity'
|
||||
url_no_port = 'https://10.0.0.101/v2.0'
|
||||
services._clients.auth_provider.auth_url = url_port
|
||||
url = services.edit_identity_url(url_port)
|
||||
self.assertEqual(url_port, url)
|
||||
url = services.edit_identity_url(identity_url)
|
||||
self.assertEqual("https://10.0.0.101:13000/identity/v2", url)
|
||||
url = services.edit_identity_url(url_no_port)
|
||||
self.assertEqual(url_no_port, url)
|
||||
services._clients.auth_provider.auth_url = url_no_port
|
||||
url = services.edit_identity_url(identity_url)
|
||||
self.assertEqual(identity_url + "/v2", url)
|
||||
|
||||
def test_get_service(self):
|
||||
services = self._create_services_instance()
|
||||
exp_resp = mock.Mock()
|
||||
exp_resp.name = 'my_service'
|
||||
services._services = [exp_resp]
|
||||
resp = services.get_service('my_service')
|
||||
self.assertEqual(resp, exp_resp)
|
||||
resp = services.get_service('my')
|
||||
self.assertEqual(resp, None)
|
||||
|
||||
def test_is_service(self):
|
||||
services = self._create_services_instance()
|
||||
service = mock.Mock()
|
||||
service.name = 'my_service'
|
||||
services._services = [service]
|
||||
resp = services.is_service('my_service')
|
||||
self.assertEqual(resp, True)
|
||||
resp = services.is_service('other_service')
|
||||
self.assertEqual(resp, False)
|
65
config_tempest/tests/services/test_volume.py
Normal file
65
config_tempest/tests/services/test_volume.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Copyright 2018 Red Hat, 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 mock
|
||||
|
||||
from config_tempest.services import volume
|
||||
from config_tempest.tempest_conf import TempestConf
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
|
||||
|
||||
class TestVolumeService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestVolumeService, self).setUp()
|
||||
self.Service = volume.VolumeService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
self.conf = TempestConf()
|
||||
|
||||
def test_set_get_extensions(self):
|
||||
exp_resp = ['NMN', 'OS-DCF']
|
||||
self._set_get_extensions(self.Service, exp_resp, self.FAKE_EXTENSIONS)
|
||||
|
||||
def test_set_get_versions(self):
|
||||
exp_resp = ['v2.0', 'v2.1']
|
||||
self._set_get_versions(self.Service, exp_resp, self.FAKE_VERSIONS)
|
||||
|
||||
@mock.patch('config_tempest.services.volume.C.LOG')
|
||||
def test_check_volume_backup_service_no_volume(self, mock_logging):
|
||||
volume.check_volume_backup_service(self.conf, None, False)
|
||||
self.assertTrue(mock_logging.info.called)
|
||||
|
||||
def test_check_volume_backup_service_state_down(self):
|
||||
client_service_mock = self.FakeServiceClient(services={
|
||||
'services': [
|
||||
{
|
||||
"state": "down"
|
||||
}
|
||||
]
|
||||
})
|
||||
volume.check_volume_backup_service(self.conf,
|
||||
client_service_mock, True)
|
||||
self.assertEqual(self.conf.get('volume-feature-enabled',
|
||||
'backup'), 'False')
|
||||
|
||||
def test_check_volume_backup_service_no_service(self):
|
||||
client_service_mock = self.FakeServiceClient(services={
|
||||
'services': []
|
||||
})
|
||||
volume.check_volume_backup_service(self.conf,
|
||||
client_service_mock, True)
|
||||
self.assertEqual(self.conf.get('volume-feature-enabled',
|
||||
'backup'), 'False')
|
@ -1,128 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 Red Hat, 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.
|
||||
|
||||
from config_tempest import api_discovery as api
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
from fixtures import MonkeyPatch
|
||||
from mock import Mock
|
||||
|
||||
|
||||
class TestApiDiscoveryMethods(BaseServiceTest):
|
||||
|
||||
FAKE_AUTH_DATA = (
|
||||
{
|
||||
'serviceCatalog': [
|
||||
{
|
||||
'endpoints_links': [],
|
||||
'endpoints': [{
|
||||
'adminURL': 'http://172.16.52.151:8080/v1/AUTH_402',
|
||||
'region': 'RegionOne',
|
||||
'publicURL': 'http://172.16.52.151:8080/v1/AUTH_402',
|
||||
'internalURL': 'http://172.16.52.151:8080/v1/AUTH_402',
|
||||
'id': '22c221db6ffd4236a3fd054c60aa8fd6'
|
||||
}],
|
||||
'type': 'object-store',
|
||||
'name': 'swift'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
EXPECTED_RESP = (
|
||||
{
|
||||
'object-store':
|
||||
{
|
||||
'extensions': 'discovered extensions',
|
||||
'url': 'http://172.16.52.151:8080/v1/AUTH_402',
|
||||
'versions': []
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
FAKE_AUTH_DATA_V3 = (
|
||||
{
|
||||
'serviceCatalog': [
|
||||
{
|
||||
'endpoints_links': [],
|
||||
'endpoints': [{
|
||||
'adminURL': 'http://172.16.52.151:8774/v3/402',
|
||||
'region': 'RegionOne',
|
||||
'publicURL': 'http://172.16.52.151:8774/v3/402',
|
||||
'internalURL': 'http://172.16.52.151:8774/v3/402',
|
||||
'id': '01bbde4f9fb54d35badf0561a53b2bdb'
|
||||
}],
|
||||
'type': 'compute',
|
||||
'name': 'nova'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
EXPECTED_RESP_V3 = (
|
||||
{
|
||||
'compute':
|
||||
{
|
||||
'url': 'http://172.16.52.151:8774/v3/402',
|
||||
'versions': 'discovered versions'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(TestApiDiscoveryMethods, self).setUp()
|
||||
|
||||
class FakeAuthProvider(object):
|
||||
def __init__(self, auth_url, auth_data):
|
||||
self.auth_url = auth_url # 'http://172.16.52.151:5000/v2.0'
|
||||
self.auth_data = auth_data
|
||||
|
||||
def get_auth(self):
|
||||
token = 'AAAAABYkehvCCJOsO2GWGqBbxk0mhH7VulICOW'
|
||||
return (token, self.auth_data)
|
||||
|
||||
def _test_discover(self, url, data, function2mock,
|
||||
mock_ret_val, expected_resp):
|
||||
provider = self.FakeAuthProvider(url, data)
|
||||
mocked_function = Mock()
|
||||
mocked_function.return_value = mock_ret_val
|
||||
self.useFixture(MonkeyPatch(function2mock, mocked_function))
|
||||
resp = api.discover(provider, "RegionOne")
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
def test_get_identity_v3_extensions(self):
|
||||
expected_resp = ['OS-INHERIT', 'OS-OAUTH1',
|
||||
'OS-SIMPLE-CERT', 'OS-EP-FILTER']
|
||||
fake_resp = self.FakeRequestResponse()
|
||||
mocked_requests = Mock()
|
||||
mocked_requests.return_value = fake_resp
|
||||
self.useFixture(MonkeyPatch('requests.get', mocked_requests))
|
||||
resp = api.get_identity_v3_extensions(self.FAKE_URL + "v3")
|
||||
self.assertItemsEqual(resp, expected_resp)
|
||||
|
||||
def test_discover_with_v2_url(self):
|
||||
function2mock = ('config_tempest.api_discovery.'
|
||||
'ObjectStorageService.get_extensions')
|
||||
mock_ret_val = "discovered extensions"
|
||||
self._test_discover(self.FAKE_URL + "v2.0", self.FAKE_AUTH_DATA,
|
||||
function2mock, mock_ret_val, self.EXPECTED_RESP)
|
||||
|
||||
def test_discover_with_v3_url(self):
|
||||
function2mock = ('config_tempest.api_discovery.'
|
||||
'ComputeService.get_versions')
|
||||
mock_ret_val = "discovered versions"
|
||||
self._test_discover(self.FAKE_URL + "v3", self.FAKE_AUTH_DATA_V3,
|
||||
function2mock, mock_ret_val, self.EXPECTED_RESP_V3)
|
@ -1,173 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 Red Hat, 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.
|
||||
|
||||
from config_tempest import api_discovery as api
|
||||
from config_tempest.tests.base import BaseServiceTest
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
|
||||
class TestService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestService, self).setUp()
|
||||
self.Service = api.Service("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def _mocked_do_get(self, mock_urllib3):
|
||||
mock_http = mock_urllib3.PoolManager()
|
||||
expected_resp = mock_http.request('GET',
|
||||
self.FAKE_URL,
|
||||
self.FAKE_HEADERS)
|
||||
return expected_resp.data
|
||||
|
||||
@unittest.skip('Failing due to Storyboard: 2001245')
|
||||
@mock.patch('config_tempest.api_discovery.urllib3')
|
||||
def test_do_get(self, mock_urllib3):
|
||||
resp = self.Service.do_get(self.FAKE_URL)
|
||||
expected_resp = self._mocked_do_get(mock_urllib3)
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
def test_service_headers(self):
|
||||
self.assertEqual(self.Service.headers, self.FAKE_HEADERS)
|
||||
|
||||
|
||||
class TestVersionedService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestVersionedService, self).setUp()
|
||||
self.Service = api.VersionedService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_get_versions(self):
|
||||
expected_resp = ['v2.0', 'v2.1']
|
||||
self._fake_service_do_get_method(self.FAKE_VERSIONS)
|
||||
resp = self.Service.get_versions()
|
||||
self.assertItemsEqual(resp, expected_resp)
|
||||
|
||||
def test_deserialize_versions(self):
|
||||
expected_resp = ['v2.0', 'v2.1']
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_VERSIONS)
|
||||
|
||||
|
||||
class TestComputeService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestComputeService, self).setUp()
|
||||
self.Service = api.ComputeService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_get_extensions(self):
|
||||
expected_resp = ['NMN', 'OS-DCF']
|
||||
self._get_extensions(self.Service, expected_resp, self.FAKE_EXTENSIONS)
|
||||
|
||||
def test_get_service_class(self):
|
||||
self._test_get_service_class('compute', api.ComputeService)
|
||||
|
||||
|
||||
class TestImageService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestImageService, self).setUp()
|
||||
|
||||
def test_get_service_class(self):
|
||||
self._test_get_service_class('image', api.ImageService)
|
||||
|
||||
|
||||
class TestNetworkService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestNetworkService, self).setUp()
|
||||
self.Service = api.NetworkService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_get_extensions(self):
|
||||
expected_resp = ['NMN', 'OS-DCF']
|
||||
self._get_extensions(self.Service, expected_resp, self.FAKE_EXTENSIONS)
|
||||
|
||||
def test_get_service_class(self):
|
||||
self._test_get_service_class('network', api.NetworkService)
|
||||
|
||||
|
||||
class TestVolumeService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestVolumeService, self).setUp()
|
||||
self.Service = api.VolumeService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_get_extensions(self):
|
||||
expected_resp = ['NMN', 'OS-DCF']
|
||||
self._get_extensions(self.Service, expected_resp, self.FAKE_EXTENSIONS)
|
||||
|
||||
def test_get_service_class(self):
|
||||
self._test_get_service_class('volumev3', api.VolumeService)
|
||||
|
||||
|
||||
class TestIdentityService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestIdentityService, self).setUp()
|
||||
self.Service = api.IdentityService("ServiceName",
|
||||
self.FAKE_URL + 'v2.0/',
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_get_extensions(self):
|
||||
expected_resp = ['OS-DCF', 'NMN']
|
||||
self._get_extensions(self.Service, expected_resp,
|
||||
self.FAKE_IDENTITY_EXTENSIONS)
|
||||
|
||||
def test_deserialize_versions(self):
|
||||
expected_resp = ['v3.8']
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_IDENTITY_VERSIONS)
|
||||
expected_resp = ['v2.1', 'v3.8']
|
||||
# add not deprecated v2 version to FAKE_IDENTITY_VERSIONS
|
||||
v2 = {'status': 'stable', 'id': 'v2.1'}
|
||||
self.FAKE_IDENTITY_VERSIONS['versions']['values'].append(v2)
|
||||
self._test_deserialize_versions(self.Service,
|
||||
expected_resp,
|
||||
self.FAKE_IDENTITY_VERSIONS)
|
||||
|
||||
def test_get_service_class(self):
|
||||
self._test_get_service_class('identity', api.IdentityService)
|
||||
|
||||
|
||||
class TestObjectStorageService(BaseServiceTest):
|
||||
def setUp(self):
|
||||
super(TestObjectStorageService, self).setUp()
|
||||
self.Service = api.ObjectStorageService("ServiceName",
|
||||
self.FAKE_URL,
|
||||
self.FAKE_TOKEN,
|
||||
disable_ssl_validation=False)
|
||||
|
||||
def test_get_extensions(self):
|
||||
expected_resp = ['formpost', 'ratelimit',
|
||||
'methods', 'account_quotas']
|
||||
self._get_extensions(self.Service, expected_resp,
|
||||
self.FAKE_STORAGE_EXTENSIONS)
|
||||
|
||||
def test_get_service_class(self):
|
||||
self._test_get_service_class('object-store',
|
||||
api.ObjectStorageService)
|
@ -16,9 +16,86 @@
|
||||
# under the License.
|
||||
|
||||
from fixtures import MonkeyPatch
|
||||
import logging
|
||||
import mock
|
||||
|
||||
from config_tempest.clients import ClientManager
|
||||
from config_tempest.clients import ProjectsClient
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
from tempest.lib import exceptions
|
||||
|
||||
# disable logging when running unit tests
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
class TestProjectsClient(BaseConfigTempestTest):
|
||||
LIST_PROJECTS = [{'name': 'my_name'}]
|
||||
|
||||
def setUp(self):
|
||||
super(TestProjectsClient, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.creds = self._get_creds(self.conf)
|
||||
# self.projects_manager = ProjectsClient(self.conf, self.creds)
|
||||
self.client_manager = ClientManager(self.conf, self.creds)
|
||||
|
||||
def _get_projects_client(self, identity_version):
|
||||
return ProjectsClient(
|
||||
self.client_manager.auth_provider,
|
||||
self.conf.get_defaulted('identity', 'catalog_type'),
|
||||
self.client_manager.identity_region,
|
||||
'publicURL',
|
||||
identity_version,
|
||||
**self.client_manager._get_default_params(self.conf))
|
||||
|
||||
def test_init(self):
|
||||
resp = self._get_projects_client('v2')
|
||||
self.assertEqual(type(resp.client).__name__, 'TenantsClient')
|
||||
# use v3 identity version and check if ProjectClient is instantiated
|
||||
resp = self._get_projects_client('v3')
|
||||
self.assertEqual(type(resp.client).__name__, 'ProjectsClient')
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v2.tenants_client.'
|
||||
'TenantsClient.list_tenants')
|
||||
def test_get_project_by_name_v2(self, mock_list_tenant):
|
||||
client = self._get_projects_client('v2')
|
||||
mock_list_tenant.return_value = {'tenants': self.LIST_PROJECTS}
|
||||
resp = client.get_project_by_name('my_name')
|
||||
self.assertEqual(resp['name'], 'my_name')
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v3.projects_client.'
|
||||
'ProjectsClient.list_projects')
|
||||
def test_get_project_by_name_v3(self, mock_list_project):
|
||||
client = self._get_projects_client('v3')
|
||||
mock_list_project.return_value = {'projects': self.LIST_PROJECTS}
|
||||
resp = client.get_project_by_name('my_name')
|
||||
self.assertEqual(resp['name'], 'my_name')
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v3.projects_client.'
|
||||
'ProjectsClient.list_projects')
|
||||
def test_get_project_by_name_not_found(self, mock_list_project):
|
||||
client = self._get_projects_client('v3')
|
||||
mock_list_project.return_value = {'projects': self.LIST_PROJECTS}
|
||||
try:
|
||||
client.get_project_by_name('doesnt_exist')
|
||||
except exceptions.NotFound:
|
||||
# expected behaviour
|
||||
pass
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v2.tenants_client.'
|
||||
'TenantsClient.create_tenant')
|
||||
def test_create_project_v2(self, mock_create_tenant):
|
||||
client = self._get_projects_client('v2')
|
||||
client.create_project('name', 'description')
|
||||
mock_create_tenant.assert_called_with(
|
||||
name='name', description='description')
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v3.projects_client.'
|
||||
'ProjectsClient.create_project')
|
||||
def test_create_project_v3(self, mock_create_project):
|
||||
client = self._get_projects_client('v3')
|
||||
client.create_project('name', 'description')
|
||||
mock_create_project.assert_called_with(
|
||||
name='name', description='description')
|
||||
|
||||
|
||||
class TestClientManager(BaseConfigTempestTest):
|
||||
@ -26,41 +103,49 @@ class TestClientManager(BaseConfigTempestTest):
|
||||
def setUp(self):
|
||||
super(TestClientManager, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(self.conf)
|
||||
self.creds = self._get_creds(self.conf)
|
||||
# self.client = self._get_clients(self.conf) # ?
|
||||
self.client_manager = ClientManager(self.conf, self.creds)
|
||||
|
||||
def test_init_manager_as_admin(self):
|
||||
self.creds = self._get_creds(self.conf, True)
|
||||
mock_function = mock.Mock(return_value={"id": "my_fake_id"})
|
||||
func2mock = ('config_tempest.clients.ProjectsClient.'
|
||||
'get_project_by_name')
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
self._get_clients(self.conf, self._get_creds(self.conf, admin=True))
|
||||
# check if admin credentials were set
|
||||
admin_tenant = self.conf.get("identity", "admin_tenant_name")
|
||||
admin_password = self.conf.get("identity", "admin_password")
|
||||
self.assertEqual(self.conf.get("identity", "admin_username"), "admin")
|
||||
self.assertEqual(admin_tenant, "adminTenant")
|
||||
self.assertEqual(admin_password, "adminPass")
|
||||
ClientManager(self.conf, self.creds)
|
||||
# check if admin tenant id was set
|
||||
admin_tenant_id = self.conf.get("identity", "admin_tenant_id")
|
||||
self.assertEqual(admin_tenant_id, "my_fake_id")
|
||||
|
||||
def test_init_manager_as_admin_using_new_auth(self):
|
||||
self.conf = self._get_alt_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(self.conf)
|
||||
mock_function = mock.Mock(return_value={"id": "my_fake_id"})
|
||||
func2mock = ('config_tempest.clients.ProjectsClient'
|
||||
'.get_project_by_name')
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
self._get_clients(self.conf, self._get_creds(self.conf, admin=True))
|
||||
# check if admin credentials were set
|
||||
admin_tenant = self.conf.get("auth", "admin_project_name")
|
||||
admin_password = self.conf.get("auth", "admin_password")
|
||||
self.assertEqual(self.conf.get("auth", "admin_username"), "admin")
|
||||
self.assertEqual(admin_tenant, "adminTenant")
|
||||
self.assertEqual(admin_password, "adminPass")
|
||||
# check if admin tenant id was set
|
||||
admin_tenant_id = self.conf.get("identity", "admin_tenant_id")
|
||||
self.assertEqual(admin_tenant_id, "my_fake_id")
|
||||
use_dynamic_creds_bool = self.conf.get("auth",
|
||||
"use_dynamic_credentials")
|
||||
self.assertEqual(use_dynamic_creds_bool, "True")
|
||||
def test_get_service_client(self):
|
||||
resp = self.client_manager.get_service_client('image')
|
||||
self.assertEqual(resp, self.client_manager.images)
|
||||
resp = self.client_manager.get_service_client('network')
|
||||
self.assertEqual(resp, self.client_manager)
|
||||
resp = self.client_manager.get_service_client('doesnt_exist')
|
||||
self.assertEqual(resp, None)
|
||||
|
||||
def test_set_users_client(self):
|
||||
self.client_manager.users = None
|
||||
self.client_manager.set_users_client(
|
||||
self.client_manager.auth_provider,
|
||||
self.creds.identity_version,
|
||||
self.conf.get_defaulted('identity', 'catalog_type'),
|
||||
'publicURL',
|
||||
self.client_manager._get_default_params(self.conf))
|
||||
self.assertEqual(
|
||||
type(self.client_manager.users).__name__,
|
||||
'UsersClient')
|
||||
|
||||
def test_set_roles_client(self):
|
||||
self.client_manager.roles = None
|
||||
self.client_manager.set_roles_client(
|
||||
self.client_manager.auth_provider,
|
||||
self.creds.identity_version,
|
||||
self.conf.get_defaulted('identity', 'catalog_type'),
|
||||
'publicURL',
|
||||
self.client_manager._get_default_params(self.conf))
|
||||
self.assertEqual(
|
||||
type(self.client_manager.roles).__name__,
|
||||
'RolesClient')
|
||||
|
@ -120,250 +120,3 @@ class TestOsClientConfigSupport(BaseConfigTempestTest):
|
||||
self.conf.get('identity', 'admin_username'),
|
||||
self.conf.get('identity', 'admin_password'),
|
||||
self.conf.get('identity', 'admin_tenant_name'))
|
||||
|
||||
|
||||
class TestConfigTempest(BaseConfigTempestTest):
|
||||
|
||||
FAKE_SERVICES = {
|
||||
'compute': {
|
||||
'url': 'http://172.16.52.151:8774/v2.1/402486',
|
||||
'extensions': ['NMN', 'OS-DCF', 'OS-EXT-AZ', 'OS-EXT-IMG-SIZE'],
|
||||
'versions': ['v2.0', 'v2.1']
|
||||
},
|
||||
'network': {
|
||||
'url': 'http://172.16.52.151:9696',
|
||||
'extensions': ['default-subnetpools', 'network-ip-availability'],
|
||||
'versions': ['v2.0']
|
||||
},
|
||||
'image': {
|
||||
'url': 'http://172.16.52.151:9292',
|
||||
'extensions': [],
|
||||
'versions': ['v2.4', 'v2.3', 'v2.2']
|
||||
},
|
||||
'volumev3': {
|
||||
'url': 'http://172.16.52.151:8776/v3/402486',
|
||||
'extensions': ['OS-SCH-HNT', 'os-hosts'],
|
||||
'versions': ['v2.0', 'v3.0']
|
||||
},
|
||||
'volume': {
|
||||
'url': 'http://172.16.52.151:8776/v1/402486',
|
||||
'extensions': [],
|
||||
'versions': []
|
||||
},
|
||||
'identity': {
|
||||
'url': 'http://172.16.52.151:5000/v3',
|
||||
'versions': ['v3.8', 'v2.0']
|
||||
},
|
||||
'ec2': {
|
||||
'url': 'http://172.16.52.151:5000'
|
||||
},
|
||||
's3': {
|
||||
'url': 'http://172.16.52.151:5000'
|
||||
}
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestConfigTempest, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
|
||||
def _mock_get_identity_v3_extensions(self):
|
||||
mock_function = mock.Mock(return_value=['FAKE-EXTENSIONS'])
|
||||
func2mock = 'config_tempest.api_discovery.get_identity_v3_extensions'
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
|
||||
def test_check_volume_backup_service(self):
|
||||
client = self._get_clients(self.conf).volume_service
|
||||
CLIENT_MOCK = ('tempest.lib.services.volume.v2.'
|
||||
'services_client.ServicesClient')
|
||||
func2mock = '.list_services'
|
||||
mock_function = mock.Mock(return_value={'services': []})
|
||||
self.useFixture(MonkeyPatch(CLIENT_MOCK + func2mock, mock_function))
|
||||
tool.check_volume_backup_service(client, self.conf, self.FAKE_SERVICES)
|
||||
self.assertEqual(self.conf.get('volume-feature-enabled', 'backup'),
|
||||
'False')
|
||||
|
||||
def test_check_ceilometer_service(self):
|
||||
client = self._get_clients(self.conf).service_client
|
||||
CLIENT_MOCK = ('tempest.lib.services.identity.v3.'
|
||||
'services_client.ServicesClient')
|
||||
func2mock = '.list_services'
|
||||
mock_function = mock.Mock(return_value={'services': [
|
||||
{'name': 'ceilometer', 'enabled': True, 'type': 'metering'}]})
|
||||
|
||||
self.useFixture(MonkeyPatch(CLIENT_MOCK + func2mock, mock_function))
|
||||
tool.check_ceilometer_service(client, self.conf, self.FAKE_SERVICES)
|
||||
self.assertEqual(self.conf.get('service_available', 'ceilometer'),
|
||||
'True')
|
||||
|
||||
def test_configure_keystone_feature_flags(self):
|
||||
tool.configure_keystone_feature_flags(self.conf, self.FAKE_SERVICES)
|
||||
self.assertEqual(
|
||||
self.conf.get('identity-feature-enabled',
|
||||
'forbid_global_implied_dsr'), 'True')
|
||||
|
||||
def test_configure_boto(self):
|
||||
tool.configure_boto(self.conf, self.FAKE_SERVICES)
|
||||
expected_url = "http://172.16.52.151:5000"
|
||||
self.assertEqual(self.conf.get("boto", "ec2_url"), expected_url)
|
||||
self.assertEqual(self.conf.get("boto", "s3_url"), expected_url)
|
||||
|
||||
def test_configure_horizon_ipv4(self):
|
||||
mock_function = mock.Mock(return_value=True)
|
||||
self.useFixture(MonkeyPatch('urllib2.urlopen', mock_function))
|
||||
tool.configure_horizon(self.conf)
|
||||
self.assertEqual(self.conf.get('service_available', 'horizon'), "True")
|
||||
self.assertEqual(self.conf.get('dashboard', 'dashboard_url'),
|
||||
"http://172.16.52.151/dashboard/")
|
||||
self.assertEqual(self.conf.get('dashboard', 'login_url'),
|
||||
"http://172.16.52.151/dashboard/auth/login/")
|
||||
|
||||
def test_configure_horizon_ipv6(self):
|
||||
mock_function = mock.Mock(return_value=True)
|
||||
self.useFixture(MonkeyPatch('urllib2.urlopen', mock_function))
|
||||
self.conf.set('identity', 'uri', 'http://[::1]:5000/v3', priority=True)
|
||||
tool.configure_horizon(self.conf)
|
||||
self.assertEqual(self.conf.get('service_available', 'horizon'), "True")
|
||||
self.assertEqual(self.conf.get('dashboard', 'dashboard_url'),
|
||||
"http://[::1]/dashboard/")
|
||||
self.assertEqual(self.conf.get('dashboard', 'login_url'),
|
||||
"http://[::1]/dashboard/auth/login/")
|
||||
|
||||
def test_discovered_services(self):
|
||||
self._mock_get_identity_v3_extensions()
|
||||
tool.configure_discovered_services(self.conf, self.FAKE_SERVICES)
|
||||
# check enabled services
|
||||
enabled_services = ["image", "volume", "compute", "network"]
|
||||
# iterating through tuples = (service_name, codename)
|
||||
for service in tool.SERVICE_NAMES.iteritems():
|
||||
if service[0] in enabled_services:
|
||||
enabled = "True"
|
||||
else:
|
||||
enabled = "False"
|
||||
self.assertEqual(self.conf.get("service_available", service[1]),
|
||||
enabled)
|
||||
|
||||
# check versions
|
||||
for service, service_info in tool.SERVICE_VERSIONS.iteritems():
|
||||
section = service + '-feature-enabled'
|
||||
for version in service_info['supported_versions']:
|
||||
# only image v1 is expected to be False
|
||||
exp_support = str(not(service == "image" and version == "v1"))
|
||||
self.assertEqual(self.conf.get(section, 'api_' + version),
|
||||
exp_support)
|
||||
|
||||
# check extensions
|
||||
for service, ext_key in tool.SERVICE_EXTENSION_KEY.iteritems():
|
||||
if service in self.FAKE_SERVICES:
|
||||
section = service + '-feature-enabled'
|
||||
if service == "identity":
|
||||
exp_ext = ",FAKE-EXTENSIONS"
|
||||
else:
|
||||
extensions = self.FAKE_SERVICES[service]['extensions']
|
||||
exp_ext = ','.join(extensions)
|
||||
self.assertEqual(self.conf.get(section, 'api_extensions'),
|
||||
exp_ext)
|
||||
|
||||
def test_discovered_services_volume_service_disabled(self):
|
||||
self.conf.set("services", "volume", "False")
|
||||
self._mock_get_identity_v3_extensions()
|
||||
tool.configure_discovered_services(self.conf, self.FAKE_SERVICES)
|
||||
self.assertFalse(self.conf.has_option("service_available", "cinder"))
|
||||
self.assertFalse(self.conf.has_option("volume-feature-enabled",
|
||||
"api_v1"))
|
||||
self.assertFalse(self.conf.has_option("volume-feature-enabled",
|
||||
"api_v2"))
|
||||
|
||||
|
||||
class TestFlavors(BaseConfigTempestTest):
|
||||
"""Flavors test class
|
||||
|
||||
Tests for create_tempest_flavors and find_or_create_flavor methods.
|
||||
"""
|
||||
|
||||
CLIENT_MOCK = 'tempest.lib.services.compute.flavors_client.FlavorsClient'
|
||||
FLAVORS_LIST = [
|
||||
{"id": "Fakeid", "name": "Name"},
|
||||
{"id": "MyFakeID", "name": "MyID"}
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestFlavors, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(self.conf).flavors
|
||||
|
||||
def _mock_create_tempest_flavor(self, mock_function):
|
||||
func2mock = 'config_tempest.main.find_or_create_flavor'
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
tool.create_tempest_flavors(client=self.client,
|
||||
conf=self.conf,
|
||||
allow_creation=True)
|
||||
|
||||
def _mock_find_or_create_flavor(self, return_value, func2mock, flavor_name,
|
||||
expected_resp, allow_creation=False,
|
||||
flavor_id=None):
|
||||
mock_function = mock.Mock(return_value=return_value)
|
||||
self.useFixture(MonkeyPatch(self.CLIENT_MOCK + func2mock,
|
||||
mock_function))
|
||||
resp = tool.find_or_create_flavor(self.client,
|
||||
flavor_id=flavor_id,
|
||||
flavor_name=flavor_name,
|
||||
allow_creation=allow_creation)
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
def test_create_tempest_flavors(self):
|
||||
mock_function = mock.Mock(return_value="FakeID")
|
||||
self._mock_create_tempest_flavor(mock_function)
|
||||
self.assertEqual(self.conf.get('compute', 'flavor_ref'), "FakeID")
|
||||
self.assertEqual(self.conf.get('compute', 'flavor_ref_alt'), "FakeID")
|
||||
calls = [mock.call(self.client, None, 'm1.nano', True, ram=64),
|
||||
mock.call(self.client, None, 'm1.micro', True, ram=128)]
|
||||
mock_function.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_create_tempest_flavors_overwrite(self):
|
||||
mock_function = mock.Mock(return_value="FakeID")
|
||||
self.conf.set('compute', 'flavor_ref', "FAKE_ID")
|
||||
self.conf.set('compute', 'flavor_ref_alt', "FAKE_ID")
|
||||
self._mock_create_tempest_flavor(mock_function)
|
||||
calls = [mock.call(self.client, "FAKE_ID", 'm1.nano', True, ram=64),
|
||||
mock.call(self.client, "FAKE_ID", 'm1.micro', True, ram=128)]
|
||||
mock_function.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_create_flavor_not_allowed(self):
|
||||
# mock list_flavors() to return empty list
|
||||
mock_function = mock.Mock(return_value={"flavors": []})
|
||||
self.useFixture(MonkeyPatch(self.CLIENT_MOCK + '.list_flavors',
|
||||
mock_function))
|
||||
exc = Exception
|
||||
self.assertRaises(exc,
|
||||
tool.find_or_create_flavor,
|
||||
client=self.client,
|
||||
flavor_id="id",
|
||||
flavor_name="name",
|
||||
allow_creation=False)
|
||||
|
||||
def test_create_flavor(self):
|
||||
return_value = {"flavor": {"id": "MyFakeID", "name": "MyID"}}
|
||||
# mock list_flavors() to return empty list
|
||||
mock_function = mock.Mock(return_value={"flavors": []})
|
||||
self.useFixture(MonkeyPatch(self.CLIENT_MOCK + '.list_flavors',
|
||||
mock_function))
|
||||
self._mock_find_or_create_flavor(return_value=return_value,
|
||||
func2mock='.create_flavor',
|
||||
flavor_name="MyID",
|
||||
expected_resp="MyFakeID",
|
||||
allow_creation=True)
|
||||
|
||||
def test_find_flavor_by_id(self):
|
||||
return_value = {"flavors": self.FLAVORS_LIST}
|
||||
self._mock_find_or_create_flavor(return_value=return_value,
|
||||
func2mock='.list_flavors',
|
||||
flavor_id="MyFakeID",
|
||||
flavor_name=None,
|
||||
expected_resp="MyFakeID")
|
||||
|
||||
def test_find_flavor_by_name(self):
|
||||
return_value = {"flavors": self.FLAVORS_LIST}
|
||||
self._mock_find_or_create_flavor(return_value=return_value,
|
||||
func2mock='.list_flavors',
|
||||
flavor_name="MyID",
|
||||
expected_resp="MyFakeID")
|
||||
|
@ -1,134 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 Red Hat, 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.
|
||||
|
||||
from fixtures import MonkeyPatch
|
||||
import mock
|
||||
|
||||
from config_tempest import main as tool
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
|
||||
|
||||
class TestCreateTempestNetworks(BaseConfigTempestTest):
|
||||
|
||||
FAKE_NETWORK_LIST = {
|
||||
'networks': [{
|
||||
'provider:physical_network': None,
|
||||
'id': '1ea533d7-4c65-4f25',
|
||||
'router:external': True,
|
||||
'availability_zone_hints': [],
|
||||
'availability_zones': [],
|
||||
'ipv4_address_scope': None,
|
||||
'status': 'ACTIVE',
|
||||
'subnets': ['fake_subnet'],
|
||||
'label': 'my_fake_label',
|
||||
'name': 'tempest-network',
|
||||
'admin_state_up': True,
|
||||
}]
|
||||
}
|
||||
|
||||
NEUTRON_CLIENT_MOCK = ('tempest.lib.services.network.'
|
||||
'networks_client.NetworksClient')
|
||||
NOVA_CLIENT_MOCK = ('tempest.lib.services.compute.'
|
||||
'networks_client.NetworksClient')
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateTempestNetworks, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.clients = self._get_clients(self.conf)
|
||||
|
||||
def _mock_list_networks(self, client, client_mock, return_value):
|
||||
mock_function = mock.Mock(return_value=return_value)
|
||||
client_mock = client_mock + '.list_networks'
|
||||
self.useFixture(MonkeyPatch(client_mock, mock_function))
|
||||
|
||||
def test_tempest_network_id_not_found(self):
|
||||
neutron_client = self.clients.get_neutron_client()
|
||||
self._mock_list_networks(neutron_client,
|
||||
self.NEUTRON_CLIENT_MOCK,
|
||||
return_value={"networks": []})
|
||||
self.assertRaises(ValueError,
|
||||
tool.create_tempest_networks,
|
||||
clients=self.clients,
|
||||
conf=self.conf,
|
||||
has_neutron=True,
|
||||
public_network_id="doesn't_exist")
|
||||
|
||||
def test_create_network_id_supplied_by_user(self):
|
||||
neutron_client = self.clients.get_neutron_client()
|
||||
self._mock_list_networks(neutron_client,
|
||||
self.NEUTRON_CLIENT_MOCK,
|
||||
return_value=self.FAKE_NETWORK_LIST)
|
||||
tool.create_tempest_networks(clients=self.clients,
|
||||
conf=self.conf,
|
||||
has_neutron=True,
|
||||
public_network_id='1ea533d7-4c65-4f25')
|
||||
self.assertEqual(self.conf.get('network', 'public_network_id'),
|
||||
'1ea533d7-4c65-4f25')
|
||||
|
||||
def test_create_network_auto_discover(self):
|
||||
neutron_client = self.clients.get_neutron_client()
|
||||
self._mock_list_networks(neutron_client,
|
||||
self.NEUTRON_CLIENT_MOCK,
|
||||
return_value=self.FAKE_NETWORK_LIST)
|
||||
tool.create_tempest_networks(clients=self.clients,
|
||||
conf=self.conf,
|
||||
has_neutron=True,
|
||||
public_network_id=None)
|
||||
self.assertEqual(self.conf.get('network', 'public_network_id'),
|
||||
'1ea533d7-4c65-4f25')
|
||||
self.assertEqual(self.conf.get('network', 'floating_network_name'),
|
||||
'tempest-network')
|
||||
|
||||
@mock.patch('config_tempest.main.LOG')
|
||||
def test_create_network_auto_discover_not_found(self, mock_logging):
|
||||
neutron_client = self.clients.get_neutron_client()
|
||||
# delete subnets => network will not be found
|
||||
self.FAKE_NETWORK_LIST['networks'][0]['subnets'] = []
|
||||
self._mock_list_networks(neutron_client,
|
||||
self.NEUTRON_CLIENT_MOCK,
|
||||
return_value=self.FAKE_NETWORK_LIST)
|
||||
tool.create_tempest_networks(clients=self.clients,
|
||||
conf=self.conf,
|
||||
has_neutron=True,
|
||||
public_network_id=None)
|
||||
# check if LOG.error was called
|
||||
self.assertTrue(mock_logging.error.called)
|
||||
|
||||
def test_network_not_discovered(self):
|
||||
exception = Exception
|
||||
nova_client = self.clients.get_nova_net_client()
|
||||
self._mock_list_networks(nova_client,
|
||||
self.NOVA_CLIENT_MOCK,
|
||||
return_value=False)
|
||||
self.assertRaises(exception,
|
||||
tool.create_tempest_networks,
|
||||
clients=self.clients,
|
||||
conf=self.conf,
|
||||
has_neutron=False,
|
||||
public_network_id=None)
|
||||
|
||||
def test_create_fixed_network(self):
|
||||
nova_client = self.clients.get_nova_net_client()
|
||||
self._mock_list_networks(nova_client,
|
||||
self.NOVA_CLIENT_MOCK,
|
||||
return_value=self.FAKE_NETWORK_LIST)
|
||||
tool.create_tempest_networks(clients=self.clients,
|
||||
conf=self.conf,
|
||||
has_neutron=False,
|
||||
public_network_id=None)
|
||||
self.assertEqual(self.conf.get('compute', 'fixed_network_name'),
|
||||
'my_fake_label')
|
@ -28,6 +28,28 @@ class TestCredentials(BaseConfigTempestTest):
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.creds = self._get_creds(self.conf)
|
||||
|
||||
def test_get_credential(self):
|
||||
# set conf containing the newer values (admin creds in auth section)
|
||||
self.creds._conf = self._get_alt_conf("v2.0", "v3")
|
||||
resp = self.creds.get_credential("username")
|
||||
self.assertEqual(resp, "demo")
|
||||
# set admin credentials
|
||||
self.creds.admin = True
|
||||
resp = self.creds.get_credential("username")
|
||||
self.assertEqual(resp, "admin")
|
||||
|
||||
def test_get_identity_credential(self):
|
||||
for i in range(0, 2):
|
||||
resp = self.creds.get_identity_credential("username")
|
||||
self.assertEqual(resp, "demo")
|
||||
# set admin credentials
|
||||
self.creds.admin = True
|
||||
resp = self.creds.get_identity_credential("admin_username")
|
||||
self.assertEqual(resp, "admin")
|
||||
# use conf which contains the newer values - (admin creds
|
||||
# in auth section)
|
||||
self.creds._conf = self._get_alt_conf("v2.0", "v3")
|
||||
|
||||
def test_get_identity_version_v2(self):
|
||||
resp = self.creds._get_identity_version()
|
||||
self.assertEqual(resp, 'v2')
|
||||
@ -38,6 +60,23 @@ class TestCredentials(BaseConfigTempestTest):
|
||||
resp = creds._get_identity_version()
|
||||
self.assertEqual(resp, 'v3')
|
||||
|
||||
def test_get_creds_kwargs(self):
|
||||
expected_resp = {
|
||||
'username': 'demo',
|
||||
'password': 'secret',
|
||||
'tenant_name': 'demo'
|
||||
}
|
||||
self.assertEqual(self.creds._get_creds_kwargs(), expected_resp)
|
||||
self.creds.identity_version = 'v3'
|
||||
expected_resp = {
|
||||
'username': 'demo',
|
||||
'password': 'secret',
|
||||
'project_name': 'demo',
|
||||
'domain_name': 'Default',
|
||||
'user_domain_name': 'Default'
|
||||
}
|
||||
self.assertEqual(self.creds._get_creds_kwargs(), expected_resp)
|
||||
|
||||
def test_set_credentials_v2(self):
|
||||
mock_function = mock.Mock()
|
||||
function2mock = 'config_tempest.credentials.auth.get_credentials'
|
||||
|
112
config_tempest/tests/test_flavors.py
Normal file
112
config_tempest/tests/test_flavors.py
Normal file
@ -0,0 +1,112 @@
|
||||
# Copyright 2018 Red Hat, 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.
|
||||
|
||||
from fixtures import MonkeyPatch
|
||||
import mock
|
||||
|
||||
from config_tempest.flavors import Flavors
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
|
||||
|
||||
class TestFlavors(BaseConfigTempestTest):
|
||||
"""Flavors test class
|
||||
|
||||
Tests for create_tempest_flavors and find_or_create_flavor methods.
|
||||
"""
|
||||
|
||||
CLIENT_MOCK = 'tempest.lib.services.compute.flavors_client.FlavorsClient'
|
||||
FLAVORS_LIST = [
|
||||
{"id": "Fakeid", "name": "Name"},
|
||||
{"id": "MyFakeID", "name": "MyID"}
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestFlavors, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.client = self._get_clients(self.conf).flavors
|
||||
self.Service = Flavors(self.client, True, self.conf)
|
||||
|
||||
def _mock_create_tempest_flavor(self, mock_function):
|
||||
func2mock = 'config_tempest.flavors.Flavors.find_or_create_flavor'
|
||||
self.useFixture(MonkeyPatch(func2mock, mock_function))
|
||||
self.Service.create_tempest_flavors()
|
||||
|
||||
def _mock_find_or_create_flavor(self, return_value, func2mock, flavor_name,
|
||||
expected_resp, allow_creation=False,
|
||||
flavor_id=None):
|
||||
self.Service.allow_creation = allow_creation
|
||||
mock_function = mock.Mock(return_value=return_value)
|
||||
self.useFixture(MonkeyPatch(self.CLIENT_MOCK + func2mock,
|
||||
mock_function))
|
||||
resp = self.Service.find_or_create_flavor(flavor_id=flavor_id,
|
||||
flavor_name=flavor_name)
|
||||
self.assertEqual(resp, expected_resp)
|
||||
|
||||
def test_create_tempest_flavors(self):
|
||||
mock_function = mock.Mock(return_value="FakeID")
|
||||
self._mock_create_tempest_flavor(mock_function)
|
||||
self.assertEqual(self.conf.get('compute', 'flavor_ref'), "FakeID")
|
||||
self.assertEqual(self.conf.get('compute', 'flavor_ref_alt'), "FakeID")
|
||||
calls = [mock.call(None, 'm1.nano', ram=64),
|
||||
mock.call(None, 'm1.micro', ram=128)]
|
||||
mock_function.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_create_tempest_flavors_overwrite(self):
|
||||
mock_function = mock.Mock(return_value="FakeID")
|
||||
self.conf.set('compute', 'flavor_ref', "FAKE_ID")
|
||||
self.conf.set('compute', 'flavor_ref_alt', "FAKE_ID")
|
||||
self._mock_create_tempest_flavor(mock_function)
|
||||
calls = [mock.call("FAKE_ID", 'm1.nano', ram=64),
|
||||
mock.call("FAKE_ID", 'm1.micro', ram=128)]
|
||||
mock_function.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_create_flavor_not_allowed(self):
|
||||
# mock list_flavors() to return empty list
|
||||
self.Service.allow_creation = False
|
||||
mock_function = mock.Mock(return_value={"flavors": []})
|
||||
self.useFixture(MonkeyPatch(self.CLIENT_MOCK + '.list_flavors',
|
||||
mock_function))
|
||||
exc = Exception
|
||||
self.assertRaises(exc,
|
||||
self.Service.find_or_create_flavor,
|
||||
flavor_id="id",
|
||||
flavor_name="name")
|
||||
|
||||
def test_create_flavor(self):
|
||||
return_value = {"flavor": {"id": "MyFakeID", "name": "MyID"}}
|
||||
# mock list_flavors() to return empty list
|
||||
mock_function = mock.Mock(return_value={"flavors": []})
|
||||
self.useFixture(MonkeyPatch(self.CLIENT_MOCK + '.list_flavors',
|
||||
mock_function))
|
||||
self._mock_find_or_create_flavor(return_value=return_value,
|
||||
func2mock='.create_flavor',
|
||||
flavor_name="MyID",
|
||||
expected_resp="MyFakeID",
|
||||
allow_creation=True)
|
||||
|
||||
def test_find_flavor_by_id(self):
|
||||
return_value = {"flavors": self.FLAVORS_LIST}
|
||||
self._mock_find_or_create_flavor(return_value=return_value,
|
||||
func2mock='.list_flavors',
|
||||
flavor_id="MyFakeID",
|
||||
flavor_name=None,
|
||||
expected_resp="MyFakeID")
|
||||
|
||||
def test_find_flavor_by_name(self):
|
||||
return_value = {"flavors": self.FLAVORS_LIST}
|
||||
self._mock_find_or_create_flavor(return_value=return_value,
|
||||
func2mock='.list_flavors',
|
||||
flavor_name="MyID",
|
||||
expected_resp="MyFakeID")
|
@ -1,6 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 Red Hat, Inc.
|
||||
# Copyright 2018 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -17,98 +15,80 @@
|
||||
|
||||
import mock
|
||||
|
||||
from config_tempest import main as tool
|
||||
from config_tempest.tests.base import BaseConfigTempestTest
|
||||
from config_tempest.users import Users
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
class TestCreateTempestUser(BaseConfigTempestTest):
|
||||
class TestUsers(BaseConfigTempestTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateTempestUser, self).setUp()
|
||||
super(TestUsers, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.tenants_client = self._get_clients(self.conf).tenants
|
||||
self.users_client = self._get_clients(self.conf).users
|
||||
self.roles_client = self._get_clients(self.conf).roles
|
||||
tenants_client = self._get_clients(self.conf).tenants
|
||||
users_client = self._get_clients(self.conf).users
|
||||
roles_client = self._get_clients(self.conf).roles
|
||||
self.Service = Users(tenants_client, roles_client,
|
||||
users_client, self.conf)
|
||||
self.username = "test_user"
|
||||
self.password = "cryptic"
|
||||
self.tenant_name = "project"
|
||||
self.tenant_description = "Tenant for Tempest %s user" % self.username
|
||||
self.role_name = "fake_role"
|
||||
self.email = "%s@test.com" % self.username
|
||||
self.users = {'users':
|
||||
[{'name': "test_user",
|
||||
'id': "fake_user_id"},
|
||||
{'name': "test_user2",
|
||||
'id': "fake_user_id2"}]}
|
||||
self.roles = {'roles':
|
||||
[{'name': "fake_role",
|
||||
'id': "fake_role_id"},
|
||||
{'name': "fake_role2",
|
||||
'id': "fake_role_id2"}]}
|
||||
|
||||
@mock.patch('config_tempest.main.create_user_with_tenant')
|
||||
@mock.patch('config_tempest.main.give_role_to_user')
|
||||
@mock.patch('config_tempest.users.Users.'
|
||||
'create_user_with_tenant')
|
||||
@mock.patch('config_tempest.users.Users.give_role_to_user')
|
||||
def _test_create_tempest_user(self,
|
||||
mock_give_role_to_user,
|
||||
mock_create_user_with_tenant,
|
||||
services):
|
||||
orchestration=False):
|
||||
alt_username = "my_user"
|
||||
alt_password = "my_pass"
|
||||
alt_tenant_name = "my_tenant"
|
||||
self.conf.set("identity", "alt_username", alt_username)
|
||||
self.conf.set("identity", "alt_password", alt_password)
|
||||
self.conf.set("identity", "alt_tenant_name", alt_tenant_name)
|
||||
tool.create_tempest_users(self.tenants_client,
|
||||
self.roles_client,
|
||||
self.users_client,
|
||||
self.conf,
|
||||
services=services)
|
||||
if 'orchestration' in services:
|
||||
self.Service.create_tempest_users(orchestration)
|
||||
if orchestration:
|
||||
self.assertEqual(mock_give_role_to_user.mock_calls, [
|
||||
mock.call(self.tenants_client,
|
||||
self.roles_client,
|
||||
self.users_client,
|
||||
self.conf.get('identity',
|
||||
mock.call(self.conf.get('identity',
|
||||
'admin_username'),
|
||||
self.conf.get('identity',
|
||||
'tenant_name'),
|
||||
role_name='admin'),
|
||||
mock.call(self.tenants_client,
|
||||
self.roles_client,
|
||||
self.users_client,
|
||||
self.conf.get('identity',
|
||||
mock.call(self.conf.get('identity',
|
||||
'username'),
|
||||
self.conf.get('identity',
|
||||
'tenant_name'),
|
||||
role_name='heat_stack_owner',
|
||||
role_required=False),
|
||||
])
|
||||
else:
|
||||
mock_give_role_to_user.assert_called_with(
|
||||
self.tenants_client, self.roles_client,
|
||||
self.users_client,
|
||||
self.conf.get('identity', 'admin_username'),
|
||||
self.conf.get('identity', 'tenant_name'),
|
||||
role_name='admin')
|
||||
self.assertEqual(mock_create_user_with_tenant.mock_calls, [
|
||||
mock.call(self.tenants_client,
|
||||
self.users_client,
|
||||
self.conf.get('identity', 'username'),
|
||||
mock.call(self.conf.get('identity', 'username'),
|
||||
self.conf.get('identity', 'password'),
|
||||
self.conf.get('identity', 'tenant_name')),
|
||||
mock.call(self.tenants_client,
|
||||
self.users_client,
|
||||
self.conf.get('identity', 'alt_username'),
|
||||
mock.call(self.conf.get('identity', 'alt_username'),
|
||||
self.conf.get('identity', 'alt_password'),
|
||||
self.conf.get('identity', 'alt_tenant_name')),
|
||||
])
|
||||
|
||||
def test_create_tempest_user(self):
|
||||
services = ['compute', 'network']
|
||||
self._test_create_tempest_user(services=services)
|
||||
self._test_create_tempest_user(orchestration=False)
|
||||
|
||||
def test_create_tempest_user_with_orchestration(self):
|
||||
services = ['compute', 'network', 'orchestration']
|
||||
self._test_create_tempest_user(services=services)
|
||||
|
||||
|
||||
class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateUserWithTenant, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.tenants_client = self._get_clients(self.conf).tenants
|
||||
self.users_client = self._get_clients(self.conf).users
|
||||
self.username = "test_user"
|
||||
self.password = "cryptic"
|
||||
self.tenant_name = "project"
|
||||
self.tenant_description = "Tenant for Tempest %s user" % self.username
|
||||
self.email = "%s@test.com" % self.username
|
||||
self._test_create_tempest_user(orchestration=True)
|
||||
|
||||
@mock.patch('config_tempest.clients.ProjectsClient'
|
||||
'.get_project_by_name')
|
||||
@ -120,9 +100,7 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
mock_create_project,
|
||||
mock_get_project_by_name):
|
||||
mock_get_project_by_name.return_value = {'id': "fake-id"}
|
||||
tool.create_user_with_tenant(
|
||||
tenants_client=self.tenants_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.create_user_with_tenant(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
tenant_name=self.tenant_name)
|
||||
@ -146,9 +124,7 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
mock_get_project_by_name.return_value = {'id': "fake-id"}
|
||||
exc = exceptions.Conflict
|
||||
mock_create_project.side_effect = exc
|
||||
tool.create_user_with_tenant(
|
||||
tenants_client=self.tenants_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.create_user_with_tenant(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
tenant_name=self.tenant_name)
|
||||
@ -160,8 +136,6 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
tenantId="fake-id",
|
||||
email=self.email)
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v2.'
|
||||
'users_client.UsersClient.update_user_password')
|
||||
@mock.patch('tempest.common.identity.get_user_by_username')
|
||||
@mock.patch('config_tempest.clients.ProjectsClient.'
|
||||
'get_project_by_name')
|
||||
@ -172,16 +146,13 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
def test_create_user_with_tenant_user_exists(
|
||||
self, mock_create_user, mock_create_tenant,
|
||||
mock_get_project_by_name,
|
||||
mock_get_user_by_username,
|
||||
mock_update_user_password):
|
||||
mock_get_user_by_username):
|
||||
mock_get_project_by_name.return_value = {'id': "fake-id"}
|
||||
exc = exceptions.Conflict
|
||||
mock_create_user.side_effect = exc
|
||||
fake_user = {'id': "fake_user_id"}
|
||||
mock_get_user_by_username.return_value = fake_user
|
||||
tool.create_user_with_tenant(
|
||||
tenants_client=self.tenants_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.create_user_with_tenant(
|
||||
username=self.username, password=self.password,
|
||||
tenant_name=self.tenant_name)
|
||||
mock_create_tenant.assert_called_with(
|
||||
@ -191,8 +162,6 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
tenantId="fake-id",
|
||||
email=self.email)
|
||||
|
||||
@mock.patch('tempest.lib.services.identity.v2.'
|
||||
'users_client.UsersClient.update_user_password')
|
||||
@mock.patch('tempest.common.identity.get_user_by_username')
|
||||
@mock.patch('config_tempest.clients.ProjectsClient.'
|
||||
'get_project_by_name')
|
||||
@ -202,19 +171,16 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
def test_create_user_with_tenant_exists_user_exists(
|
||||
self, mock_create_user, mock_create_project,
|
||||
mock_get_project_by_name,
|
||||
mock_get_user_by_username,
|
||||
mock_update_user_password):
|
||||
mock_get_user_by_username):
|
||||
mock_get_project_by_name.return_value = {'id': "fake-id"}
|
||||
exc = exceptions.Conflict
|
||||
mock_create_project.side_effects = exc
|
||||
mock_create_user.side_effect = exc
|
||||
fake_user = {'id': "fake_user_id"}
|
||||
mock_get_user_by_username.return_value = fake_user
|
||||
tool.create_user_with_tenant(tenants_client=self.tenants_client,
|
||||
users_client=self.users_client,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
tenant_name=self.tenant_name)
|
||||
self.Service.create_user_with_tenant(username=self.username,
|
||||
password=self.password,
|
||||
tenant_name=self.tenant_name)
|
||||
mock_create_project.assert_called_with(
|
||||
name=self.tenant_name, description=self.tenant_description)
|
||||
mock_create_user.assert_called_with(name=self.username,
|
||||
@ -222,29 +188,6 @@ class TestCreateUserWithTenant(BaseConfigTempestTest):
|
||||
tenantId="fake-id",
|
||||
email=self.email)
|
||||
|
||||
|
||||
class TestGiveRoleToUser(BaseConfigTempestTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestGiveRoleToUser, self).setUp()
|
||||
self.conf = self._get_conf("v2.0", "v3")
|
||||
self.tenants_client = self._get_clients(self.conf).tenants
|
||||
self.users_client = self._get_clients(self.conf).users
|
||||
self.roles_client = self._get_clients(self.conf).roles
|
||||
self.username = "test_user"
|
||||
self.tenant_name = "project"
|
||||
self.role_name = "fake_role"
|
||||
self.users = {'users':
|
||||
[{'name': "test_user",
|
||||
'id': "fake_user_id"},
|
||||
{'name': "test_user2",
|
||||
'id': "fake_user_id2"}]}
|
||||
self.roles = {'roles':
|
||||
[{'name': "fake_role",
|
||||
'id': "fake_role_id"},
|
||||
{'name': "fake_role2",
|
||||
'id': "fake_role_id2"}]}
|
||||
|
||||
@mock.patch('config_tempest.clients.ProjectsClient.'
|
||||
'get_project_by_name')
|
||||
@mock.patch('tempest.lib.services.identity.v2.'
|
||||
@ -266,12 +209,8 @@ class TestGiveRoleToUser(BaseConfigTempestTest):
|
||||
{'id': "fake_tenant_id"}
|
||||
mock_list_users.return_value = self.users
|
||||
mock_list_roles.return_value = self.roles
|
||||
tool.give_role_to_user(
|
||||
tenants_client=self.tenants_client,
|
||||
roles_client=self.roles_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.give_role_to_user(
|
||||
username=self.username,
|
||||
tenant_name=self.tenant_name,
|
||||
role_name=self.role_name)
|
||||
mock_create_user_role_on_project.assert_called_with(
|
||||
"fake_tenant_id", "fake_user_id", "fake_role_id")
|
||||
@ -300,12 +239,8 @@ class TestGiveRoleToUser(BaseConfigTempestTest):
|
||||
mock_list_roles.return_value = self.roles
|
||||
exc = Exception
|
||||
self.assertRaises(exc,
|
||||
tool.give_role_to_user,
|
||||
tenants_client=self.tenants_client,
|
||||
roles_client=self.roles_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.give_role_to_user,
|
||||
username=self.username,
|
||||
tenant_name=self.tenant_name,
|
||||
role_name=role_name)
|
||||
|
||||
@mock.patch('config_tempest.clients.ProjectsClient.'
|
||||
@ -330,12 +265,8 @@ class TestGiveRoleToUser(BaseConfigTempestTest):
|
||||
{'id': "fake_tenant_id"}
|
||||
mock_list_users.return_value = self.users
|
||||
mock_list_roles.return_value = self.roles
|
||||
tool.give_role_to_user(
|
||||
tenants_client=self.tenants_client,
|
||||
roles_client=self.roles_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.give_role_to_user(
|
||||
username=self.username,
|
||||
tenant_name=self.tenant_name,
|
||||
role_name=self.role_name,
|
||||
role_required=False)
|
||||
|
||||
@ -361,10 +292,6 @@ class TestGiveRoleToUser(BaseConfigTempestTest):
|
||||
mock_get_project_by_name.return_value = {'id': "fake_tenant_id"}
|
||||
mock_list_users.return_value = self.users
|
||||
mock_list_roles.return_value = self.roles
|
||||
tool.give_role_to_user(
|
||||
tenants_client=self.tenants_client,
|
||||
roles_client=self.roles_client,
|
||||
users_client=self.users_client,
|
||||
self.Service.give_role_to_user(
|
||||
username=self.username,
|
||||
tenant_name=self.tenant_name,
|
||||
role_name=self.role_name)
|
118
config_tempest/users.py
Normal file
118
config_tempest/users.py
Normal file
@ -0,0 +1,118 @@
|
||||
# Copyright 2016, 2018 Red Hat, 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.
|
||||
|
||||
from constants import LOG
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
class Users(object):
|
||||
def __init__(self, tenants_client, roles_client, users_client, conf):
|
||||
"""Init.
|
||||
|
||||
:type tenants_client: ProjectsClient object
|
||||
:type roles_client: RolesClient object from tempest lib
|
||||
:type users_client: UsersClient object from tempest lib
|
||||
:type conf: TempestConf object
|
||||
"""
|
||||
self.tenants_client = tenants_client
|
||||
self.roles_client = roles_client
|
||||
self.users_client = users_client
|
||||
self._conf = conf
|
||||
|
||||
def create_tempest_users(self, orchestration=False):
|
||||
"""Create users necessary for Tempest if they don't exist already.
|
||||
|
||||
:type orchestration: boolean
|
||||
"""
|
||||
sec = 'identity'
|
||||
self.create_user_with_tenant(self._conf.get(sec, 'username'),
|
||||
self._conf.get(sec, 'password'),
|
||||
self._conf.get(sec, 'tenant_name'))
|
||||
|
||||
self.create_user_with_tenant(self._conf.get(sec, 'alt_username'),
|
||||
self._conf.get(sec, 'alt_password'),
|
||||
self._conf.get(sec, 'alt_tenant_name'))
|
||||
|
||||
username = self._conf.get_defaulted('auth', 'admin_username')
|
||||
if username is None:
|
||||
username = self._conf.get_defaulted('identity', 'admin_username')
|
||||
|
||||
self.give_role_to_user(username, role_name='admin')
|
||||
|
||||
# Prior to juno, and with earlier juno defaults, users needed to have
|
||||
# the heat_stack_owner role to use heat stack apis. We assign that role
|
||||
# to the user if the role is present.
|
||||
if orchestration:
|
||||
self.give_role_to_user(self._conf.get('identity', 'username'),
|
||||
role_name='heat_stack_owner',
|
||||
role_required=False)
|
||||
|
||||
def give_role_to_user(self, username, role_name,
|
||||
role_required=True):
|
||||
"""Give the user a role in the project (tenant).
|
||||
|
||||
:type username: string
|
||||
:type role_name: string
|
||||
:type role_required: boolean
|
||||
"""
|
||||
tenant_name = self._conf.get('identity', 'tenant_name')
|
||||
tenant_id = self.tenants_client.get_project_by_name(tenant_name)['id']
|
||||
users = self.users_client.list_users()
|
||||
user_ids = [u['id'] for u in users['users'] if u['name'] == username]
|
||||
user_id = user_ids[0]
|
||||
roles = self.roles_client.list_roles()
|
||||
role_ids = [r['id'] for r in roles['roles'] if r['name'] == role_name]
|
||||
if not role_ids:
|
||||
if role_required:
|
||||
raise Exception("required role %s not found" % role_name)
|
||||
LOG.debug("%s role not required", role_name)
|
||||
return
|
||||
role_id = role_ids[0]
|
||||
try:
|
||||
self.roles_client.create_user_role_on_project(tenant_id, user_id,
|
||||
role_id)
|
||||
LOG.debug("User '%s' was given the '%s' role in project '%s'",
|
||||
username, role_name, tenant_name)
|
||||
except exceptions.Conflict:
|
||||
LOG.debug("(no change) User '%s' already has the '%s' role in"
|
||||
" project '%s'", username, role_name, tenant_name)
|
||||
|
||||
def create_user_with_tenant(self, username, password, tenant_name):
|
||||
"""Create a user and a tenant if it doesn't exist.
|
||||
|
||||
:type username: string
|
||||
:type password: string
|
||||
:type tenant_name: string
|
||||
"""
|
||||
LOG.info("Creating user '%s' with tenant '%s' and password '%s'",
|
||||
username, tenant_name, password)
|
||||
tenant_description = "Tenant for Tempest %s user" % username
|
||||
email = "%s@test.com" % username
|
||||
# create a tenant
|
||||
try:
|
||||
self.tenants_client.create_project(name=tenant_name,
|
||||
description=tenant_description)
|
||||
except exceptions.Conflict:
|
||||
LOG.info("(no change) Tenant '%s' already exists", tenant_name)
|
||||
|
||||
tenant_id = self.tenants_client.get_project_by_name(tenant_name)['id']
|
||||
|
||||
params = {'name': username, 'password': password,
|
||||
'tenantId': tenant_id, 'email': email}
|
||||
# create a user
|
||||
try:
|
||||
self.users_client.create_user(**params)
|
||||
except exceptions.Conflict:
|
||||
LOG.info("User '%s' already exists.", username)
|
Loading…
Reference in New Issue
Block a user