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:
Martin Kopec 2018-02-01 23:21:05 +00:00
parent c1951c379e
commit 0e085cdf85
41 changed files with 2493 additions and 1881 deletions

View File

@ -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

View File

@ -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)

View 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'
}

View File

@ -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
View 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']

View File

@ -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

View File

View 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)

View 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)

View 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')

View 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)

View 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/')

View 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')

View 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)

View 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')

View 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()

View 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)

View 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')

View File

@ -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

View File

@ -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)

View 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))

View 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)

View 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')

View 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)

View 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/")

View 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')

View File

@ -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")

View 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')

View 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(), [])

View 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)

View 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')

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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")

View File

@ -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')

View File

@ -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'

View 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")

View File

@ -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
View 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)