diff --git a/rally/plugins/openstack/cfg/opts.py b/rally/plugins/openstack/cfg/opts.py index 939051b8..edab940f 100644 --- a/rally/plugins/openstack/cfg/opts.py +++ b/rally/plugins/openstack/cfg/opts.py @@ -25,6 +25,7 @@ from rally.plugins.openstack.cfg import monasca from rally.plugins.openstack.cfg import murano from rally.plugins.openstack.cfg import neutron from rally.plugins.openstack.cfg import nova +from rally.plugins.openstack.cfg import osclients from rally.plugins.openstack.cfg import profiler from rally.plugins.openstack.cfg import sahara from rally.plugins.openstack.cfg import senlin @@ -44,10 +45,10 @@ def list_opts(): opts = {} for l_opts in (cinder.OPTS, ec2.OPTS, heat.OPTS, ironic.OPTS, magnum.OPTS, manila.OPTS, mistral.OPTS, monasca.OPTS, murano.OPTS, - nova.OPTS, profiler.OPTS, sahara.OPTS, vm.OPTS, glance.OPTS, - watcher.OPTS, tempest.OPTS, keystone_roles.OPTS, - keystone_users.OPTS, cleanup.OPTS, senlin.OPTS, - neutron.OPTS): + nova.OPTS, osclients.OPTS, profiler.OPTS, sahara.OPTS, + vm.OPTS, glance.OPTS, watcher.OPTS, tempest.OPTS, + keystone_roles.OPTS, keystone_users.OPTS, cleanup.OPTS, + senlin.OPTS, neutron.OPTS): for category, opt in l_opts.items(): opts.setdefault(category, []) opts[category].extend(opt) diff --git a/rally/plugins/openstack/cfg/osclients.py b/rally/plugins/openstack/cfg/osclients.py new file mode 100644 index 00000000..05b58862 --- /dev/null +++ b/rally/plugins/openstack/cfg/osclients.py @@ -0,0 +1,26 @@ +# Copyright 2017: GoDaddy 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 oslo_config import cfg + + +OPTS = { + "DEFAULT": [ + cfg.FloatOpt( + "openstack_client_http_timeout", + default=180.0, + help="HTTP timeout for any of OpenStack service in seconds") + ] +} diff --git a/rally/plugins/openstack/cleanup/base.py b/rally/plugins/openstack/cleanup/base.py index 5b198c00..173f185b 100644 --- a/rally/plugins/openstack/cleanup/base.py +++ b/rally/plugins/openstack/cleanup/base.py @@ -18,10 +18,6 @@ from oslo_config import cfg from rally.task import utils - -from rally.common import opts -opts.register() - CONF = cfg.CONF cleanup_group = cfg.OptGroup(name="cleanup", title="Cleanup Options") diff --git a/rally/plugins/openstack/cleanup/resources.py b/rally/plugins/openstack/cleanup/resources.py index 99d25e03..ea9996b9 100644 --- a/rally/plugins/openstack/cleanup/resources.py +++ b/rally/plugins/openstack/cleanup/resources.py @@ -28,12 +28,6 @@ from rally.plugins.openstack.services.image import image from rally.task import utils as task_utils CONF = cfg.CONF -CONF.import_opt("glance_image_delete_timeout", - "rally.plugins.openstack.scenarios.glance.utils", - "benchmark") -CONF.import_opt("glance_image_delete_poll_interval", - "rally.plugins.openstack.scenarios.glance.utils", - "benchmark") LOG = logging.getLogger(__name__) diff --git a/rally/plugins/openstack/context/glance/images.py b/rally/plugins/openstack/context/glance/images.py index 2686277d..03bb043d 100644 --- a/rally/plugins/openstack/context/glance/images.py +++ b/rally/plugins/openstack/context/glance/images.py @@ -24,12 +24,6 @@ from rally.plugins.openstack.services.image import image from rally.task import context CONF = cfg.CONF -CONF.import_opt("glance_image_delete_timeout", - "rally.plugins.openstack.scenarios.glance.utils", - "benchmark") -CONF.import_opt("glance_image_delete_poll_interval", - "rally.plugins.openstack.scenarios.glance.utils", - "benchmark") LOG = logging.getLogger(__name__) diff --git a/rally/plugins/openstack/context/keystone/users.py b/rally/plugins/openstack/context/keystone/users.py index b33c8fd7..a2eab5fb 100644 --- a/rally/plugins/openstack/context/keystone/users.py +++ b/rally/plugins/openstack/context/keystone/users.py @@ -31,9 +31,6 @@ from rally.plugins.openstack.services.identity import identity from rally.plugins.openstack.wrappers import network from rally.task import context -from rally.common import opts -opts.register() - LOG = logging.getLogger(__name__) diff --git a/rally/plugins/openstack/osclients.py b/rally/plugins/openstack/osclients.py new file mode 100644 index 00000000..e8cdd84a --- /dev/null +++ b/rally/plugins/openstack/osclients.py @@ -0,0 +1,844 @@ +# Copyright 2013: Mirantis 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 abc +import os + +from oslo_config import cfg +from six.moves.urllib import parse + +from rally.cli import envutils +from rally.common import logging +from rally.common.plugin import plugin +from rally import consts +from rally import exceptions + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + + +def configure(name, default_version=None, default_service_type=None, + supported_versions=None): + """OpenStack client class wrapper. + + Each client class has to be wrapped by configure() wrapper. It + sets essential configuration of client classes. + + :param name: Name of the client + :param default_version: Default version for client + :param default_service_type: Default service type of endpoint(If this + variable is not specified, validation will assume that your client + doesn't allow to specify service type. + :param supported_versions: List of supported versions(If this variable is + not specified, `OSClients.validate_version` method will raise an + exception that client doesn't support setting any versions. If this + logic is wrong for your client, you should override `validate_version` + in client object) + """ + def wrapper(cls): + cls = plugin.configure(name=name, platform="openstack")(cls) + cls._meta_set("default_version", default_version) + cls._meta_set("default_service_type", default_service_type) + cls._meta_set("supported_versions", supported_versions or []) + return cls + + return wrapper + + +@plugin.base() +class OSClient(plugin.Plugin): + """Base class for openstack clients""" + + def __init__(self, credential, api_info, cache_obj): + self.credential = credential + self.api_info = api_info + self.cache = cache_obj + + def choose_version(self, version=None): + """Return version string. + + Choose version between transmitted(preferable value if present), + version from api_info(configured from a context) and default. + """ + # NOTE(andreykurilin): The result of choose is converted to string, + # since most of clients contain map for versioned modules, where a key + # is a string value of version. Example of map and its usage: + # + # from oslo_utils import importutils + # ... + # version_map = {"1": "someclient.v1.client.Client", + # "2": "someclient.v2.client.Client"} + # + # def Client(version, *args, **kwargs): + # cls = importutils.import_class(version_map[version]) + # return cls(*args, **kwargs) + # + # That is why type of version so important and we should ensure that + # version is a string object. + # For those clients which doesn't accept string value(for example + # zaqarclient), this method should be overridden. + version = (version + or self.api_info.get(self.get_name(), {}).get("version") + or self._meta_get("default_version")) + if version is not None: + version = str(version) + return version + + @classmethod + def get_supported_versions(cls): + return cls._meta_get("supported_versions") + + @classmethod + def validate_version(cls, version): + supported_versions = cls.get_supported_versions() + if supported_versions: + if str(version) not in supported_versions: + raise exceptions.ValidationError( + "'%(vers)s' is not supported. Should be one of " + "'%(supported)s'" + % {"vers": version, "supported": supported_versions}) + else: + raise exceptions.RallyException("Setting version is not supported") + try: + float(version) + except ValueError: + raise exceptions.ValidationError( + "'%s' is invalid. Should be numeric value." % version) + + def choose_service_type(self, service_type=None): + """Return service_type string. + + Choose service type between transmitted(preferable value if present), + service type from api_info(configured from a context) and default. + """ + return (service_type + or self.api_info.get(self.get_name(), {}).get("service_type") + or self._meta_get("default_service_type")) + + @classmethod + def is_service_type_configurable(cls): + """Just checks that client supports setting service type.""" + if cls._meta_get("default_service_type") is None: + raise exceptions.RallyException( + "Setting service type is not supported.") + + @property + def keystone(self): + return OSClient.get("keystone")(self.credential, self.api_info, + self.cache) + + def _get_session(self, auth_url=None, version=None): + LOG.warning( + "Method `rally.osclient.OSClient._get_session` is deprecated since" + " Rally 0.6.0. Use " + "`rally.osclient.OSClient.keystone.get_session` instead.") + return self.keystone.get_session(version) + + def _get_endpoint(self, service_type=None): + kw = {"service_type": self.choose_service_type(service_type), + "region_name": self.credential.region_name} + if self.credential.endpoint_type: + kw["interface"] = self.credential.endpoint_type + api_url = self.keystone.service_catalog.url_for(**kw) + return api_url + + def _get_auth_info(self, user_key="username", + password_key="password", + auth_url_key="auth_url", + project_name_key="project_id", + domain_name_key="domain_name", + user_domain_name_key="user_domain_name", + project_domain_name_key="project_domain_name", + cacert_key="cacert", + endpoint_type="endpoint_type", + ): + kw = { + user_key: self.credential.username, + password_key: self.credential.password, + auth_url_key: self.credential.auth_url, + cacert_key: self.credential.https_cacert, + } + if project_name_key: + kw.update({project_name_key: self.credential.tenant_name}) + + if "v2.0" not in self.credential.auth_url: + kw.update({ + domain_name_key: self.credential.domain_name}) + kw.update({ + user_domain_name_key: + self.credential.user_domain_name or "Default"}) + kw.update({ + project_domain_name_key: + self.credential.project_domain_name or "Default"}) + if self.credential.endpoint_type: + kw[endpoint_type] = self.credential.endpoint_type + return kw + + @abc.abstractmethod + def create_client(self, *args, **kwargs): + """Create new instance of client.""" + + def __call__(self, *args, **kwargs): + """Return initialized client instance.""" + key = "{0}{1}{2}".format(self.get_name(), + str(args) if args else "", + str(kwargs) if kwargs else "") + if key not in self.cache: + self.cache[key] = self.create_client(*args, **kwargs) + return self.cache[key] + + @classmethod + def get(cls, name, **kwargs): + # NOTE(boris-42): Remove this after we finish rename refactoring. + kwargs.pop("platform", None) + kwargs.pop("namespace", None) + return super(OSClient, cls).get(name, platform="openstack", **kwargs) + + +@configure("keystone", supported_versions=("2", "3")) +class Keystone(OSClient): + """Wrapper for KeystoneClient which hides OpenStack auth details.""" + + @property + def keystone(self): + raise exceptions.RallyException( + "Method 'keystone' is restricted for keystoneclient. :)") + + @property + def service_catalog(self): + return self.auth_ref.service_catalog + + @property + def auth_ref(self): + try: + if "keystone_auth_ref" not in self.cache: + sess, plugin = self.get_session() + self.cache["keystone_auth_ref"] = plugin.get_access(sess) + except Exception as e: + if logging.is_debug(): + LOG.exception("Unable to authenticate for user" + " %(username)s in project" + " %(tenant_name)s" % + {"username": self.credential.username, + "tenant_name": self.credential.tenant_name}) + raise exceptions.AuthenticationFailed( + username=self.credential.username, + project=self.credential.tenant_name, + url=self.credential.auth_url, + etype=e.__class__.__name__, + error=str(e)) + return self.cache["keystone_auth_ref"] + + def get_session(self, version=None): + key = "keystone_session_and_plugin_%s" % version + if key not in self.cache: + from keystoneauth1 import discover + from keystoneauth1 import identity + from keystoneauth1 import session + + version = self.choose_version(version) + auth_url = self.credential.auth_url + if version is not None: + auth_url = self._remove_url_version() + + password_args = { + "auth_url": auth_url, + "username": self.credential.username, + "password": self.credential.password, + "tenant_name": self.credential.tenant_name + } + + if version is None: + # NOTE(rvasilets): If version not specified than we discover + # available version with the smallest number. To be able to + # discover versions we need session + temp_session = session.Session( + verify=(self.credential.https_cacert or + not self.credential.https_insecure), + timeout=CONF.openstack_client_http_timeout) + version = str(discover.Discover( + temp_session, + password_args["auth_url"]).version_data()[0]["version"][0]) + + if "v2.0" not in password_args["auth_url"] and ( + version != "2"): + password_args.update({ + "user_domain_name": self.credential.user_domain_name, + "domain_name": self.credential.domain_name, + "project_domain_name": self.credential.project_domain_name + }) + identity_plugin = identity.Password(**password_args) + sess = session.Session( + auth=identity_plugin, + verify=(self.credential.https_cacert or + not self.credential.https_insecure), + timeout=CONF.openstack_client_http_timeout) + self.cache[key] = (sess, identity_plugin) + return self.cache[key] + + def _remove_url_version(self): + """Remove any version from the auth_url. + + The keystone Client code requires that auth_url be the root url + if a version override is used. + """ + url = parse.urlparse(self.credential.auth_url) + path = url.path.rstrip("/") + if path.endswith("v2.0") or path.endswith("v3"): + path = os.path.join(*os.path.split(path)[:-1]) + parts = (url.scheme, url.netloc, path, url.params, url.query, + url.fragment) + return parse.urlunparse(parts) + return self.credential.auth_url + + def create_client(self, version=None): + """Return a keystone client. + + :param version: Keystone API version, can be one of: + ("2", "3") + + If this object was constructed with a version in the api_info + then that will be used unless the version parameter is passed. + """ + import keystoneclient + from keystoneclient import client + + # Use the version in the api_info if provided, otherwise fall + # back to the passed version (which may be None, in which case + # keystoneclient chooses). + version = self.choose_version(version) + + sess = self.get_session(version=version)[0] + + kw = {"version": version, "session": sess, + "timeout": CONF.openstack_client_http_timeout} + if keystoneclient.__version__[0] == "1": + # NOTE(andreykurilin): let's leave this hack for envs which uses + # old(<2.0.0) keystoneclient version. Upstream fix: + # https://github.com/openstack/python-keystoneclient/commit/d9031c252848d89270a543b67109a46f9c505c86 + from keystoneauth1 import plugin + kw["auth_url"] = sess.get_endpoint(interface=plugin.AUTH_INTERFACE) + if self.credential.endpoint_type: + kw["interface"] = self.credential.endpoint_type + + # NOTE(amyge): + # In auth_ref(), plugin.get_access(sess) only returns a auth_ref object + # and won't check the authentication access until it is actually being + # called. To catch the authentication failure in auth_ref(), we will + # have to call self.auth_ref.auth_token here to actually use auth_ref. + self.auth_ref # noqa + + return client.Client(**kw) + + +@configure("nova", default_version="2", default_service_type="compute") +class Nova(OSClient): + """Wrapper for NovaClient which returns a authenticated native client.""" + + @classmethod + def validate_version(cls, version): + from novaclient import api_versions + from novaclient import exceptions as nova_exc + + try: + api_versions.get_api_version(version) + except nova_exc.UnsupportedVersion: + raise exceptions.RallyException( + "Version string '%s' is unsupported." % version) + + def create_client(self, version=None, service_type=None): + """Return nova client.""" + from novaclient import client as nova + + client = nova.Client( + session=self.keystone.get_session()[0], + version=self.choose_version(version), + endpoint_override=self._get_endpoint(service_type)) + return client + + +@configure("neutron", default_version="2.0", default_service_type="network", + supported_versions=["2.0"]) +class Neutron(OSClient): + """Wrapper for NeutronClient which returns an authenticated native client. + + """ + + def create_client(self, version=None, service_type=None): + """Return neutron client.""" + from neutronclient.neutron import client as neutron + + kw_args = {} + if self.credential.endpoint_type: + kw_args["endpoint_type"] = self.credential.endpoint_type + + client = neutron.Client( + self.choose_version(version), + session=self.keystone.get_session()[0], + endpoint_override=self._get_endpoint(service_type), + **kw_args) + return client + + +@configure("glance", default_version="2", default_service_type="image", + supported_versions=["1", "2"]) +class Glance(OSClient): + """Wrapper for GlanceClient which returns an authenticated native client. + + """ + + def create_client(self, version=None, service_type=None): + """Return glance client.""" + import glanceclient as glance + + session = self.keystone.get_session()[0] + client = glance.Client( + version=self.choose_version(version), + endpoint_override=self._get_endpoint(service_type), + session=session) + return client + + +@configure("heat", default_version="1", default_service_type="orchestration", + supported_versions=["1"]) +class Heat(OSClient): + """Wrapper for HeatClient which returns an authenticated native client.""" + def create_client(self, version=None, service_type=None): + """Return heat client.""" + from heatclient import client as heat + + # ToDo: Remove explicit endpoint_type or interface initialization + # when heatclient no longer uses it. + kw_args = {} + if self.credential.endpoint_type: + kw_args["endpoint_type"] = self.credential.endpoint_type + kw_args["interface"] = self.credential.endpoint_type + + client = heat.Client( + self.choose_version(version), + session=self.keystone.get_session()[0], + # Remove endpoint once requirement is python-heatclient>=1.6 + endpoint=self._get_endpoint(service_type), + endpoint_override=self._get_endpoint(service_type), + **kw_args) + return client + + +@configure("cinder", default_version="2", default_service_type="volumev2", + supported_versions=["1", "2"]) +class Cinder(OSClient): + """Wrapper for CinderClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return cinder client.""" + from cinderclient import client as cinder + + client = cinder.Client( + self.choose_version(version), + session=self.keystone.get_session()[0], + endpoint_override=self._get_endpoint(service_type)) + return client + + +@configure("manila", default_version="1", default_service_type="share") +class Manila(OSClient): + """Wrapper for ManilaClient which returns an authenticated native client. + + """ + @classmethod + def validate_version(cls, version): + from manilaclient import api_versions + from manilaclient import exceptions as manila_exc + + try: + api_versions.get_api_version(version) + except manila_exc.UnsupportedVersion: + raise exceptions.RallyException( + "Version string '%s' is unsupported." % version) + + def create_client(self, version=None, service_type=None): + """Return manila client.""" + from manilaclient import client as manila + manila_client = manila.Client( + self.choose_version(version), + session=self.keystone.get_session()[0], + service_catalog_url=self._get_endpoint(service_type)) + return manila_client + + +@configure("ceilometer", default_version="2", default_service_type="metering", + supported_versions=["1", "2"]) +class Ceilometer(OSClient): + """Wrapper for CeilometerClient which returns authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return ceilometer client.""" + from ceilometerclient import client as ceilometer + + client = ceilometer.get_client( + self.choose_version(version), + session=self.keystone.get_session()[0], + endpoint_override=self._get_endpoint(service_type)) + return client + + +@configure("gnocchi", default_service_type="metric", default_version="1", + supported_versions=["1"]) +class Gnocchi(OSClient): + """Wrapper for GnocchiClient which returns an authenticated native client. + + """ + + def create_client(self, version=None, service_type=None): + """Return gnocchi client.""" + # NOTE(sumantmurke): gnocchiclient requires keystoneauth1 for + # authenticating and creating a session. + from gnocchiclient import client as gnocchi + + service_type = self.choose_service_type(service_type) + sess = self.keystone.get_session()[0] + gclient = gnocchi.Client(version=self.choose_version( + version), session=sess, service_type=service_type) + return gclient + + +@configure("ironic", default_version="1", default_service_type="baremetal", + supported_versions=["1"]) +class Ironic(OSClient): + """Wrapper for IronicClient which returns an authenticated native client. + + """ + + def create_client(self, version=None, service_type=None): + """Return Ironic client.""" + from ironicclient import client as ironic + + client = ironic.get_client( + self.choose_version(version), + session=self.keystone.get_session()[0], + endpoint=self._get_endpoint(service_type)) + return client + + +@configure("sahara", default_version="1.1", supported_versions=["1.0", "1.1"], + default_service_type="data-processing") +class Sahara(OSClient): + """Wrapper for SaharaClient which returns an authenticated native client. + + """ + + # NOTE(andreykurilin): saharaclient supports "1.0" version and doesn't + # support "1". `choose_version` and `validate_version` methods are written + # as a hack to covert 1 -> 1.0, which can simplify setting saharaclient + # for end-users. + def choose_version(self, version=None): + return float(super(Sahara, self).choose_version(version)) + + @classmethod + def validate_version(cls, version): + super(Sahara, cls).validate_version(float(version)) + + def create_client(self, version=None, service_type=None): + """Return Sahara client.""" + from saharaclient import client as sahara + + client = sahara.Client( + self.choose_version(version), + session=self.keystone.get_session()[0], + sahara_url=self._get_endpoint(service_type)) + + return client + + +@configure("zaqar", default_version="1.1", default_service_type="messaging", + supported_versions=["1", "1.1"]) +class Zaqar(OSClient): + """Wrapper for ZaqarClient which returns an authenticated native client. + + """ + def choose_version(self, version=None): + # zaqarclient accepts only int or float obj as version + return float(super(Zaqar, self).choose_version(version)) + + def create_client(self, version=None, service_type=None): + """Return Zaqar client.""" + from zaqarclient.queues import client as zaqar + client = zaqar.Client(url=self._get_endpoint(), + version=self.choose_version(version), + session=self.keystone.get_session()[0]) + return client + + +@configure("murano", default_version="1", + default_service_type="application-catalog", + supported_versions=["1"]) +class Murano(OSClient): + """Wrapper for MuranoClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return Murano client.""" + from muranoclient import client as murano + + client = murano.Client(self.choose_version(version), + endpoint=self._get_endpoint(service_type), + token=self.keystone.auth_ref.auth_token) + + return client + + +@configure("designate", default_version="1", default_service_type="dns", + supported_versions=["1", "2"]) +class Designate(OSClient): + """Wrapper for DesignateClient which returns authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return designate client.""" + from designateclient import client + + version = self.choose_version(version) + + api_url = self._get_endpoint(service_type) + api_url += "/v%s" % version + + session = self.keystone.get_session()[0] + if version == "2": + return client.Client(version, session=session, + endpoint_override=api_url) + return client.Client(version, session=session, + endpoint=api_url) + + +@configure("trove", default_version="1.0", supported_versions=["1.0"], + default_service_type="database") +class Trove(OSClient): + """Wrapper for TroveClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Returns trove client.""" + from troveclient import client as trove + + client = trove.Client(self.choose_version(version), + session=self.keystone.get_session()[0], + endpoint=self._get_endpoint(service_type)) + return client + + +@configure("mistral", default_service_type="workflowv2") +class Mistral(OSClient): + """Wrapper for MistralClient which returns an authenticated native client. + + """ + def create_client(self, service_type=None): + """Return Mistral client.""" + from mistralclient.api import client as mistral + + client = mistral.client( + mistral_url=self._get_endpoint(service_type), + service_type=self.choose_service_type(service_type), + auth_token=self.keystone.auth_ref.auth_token) + return client + + +@configure("swift", default_service_type="object-store") +class Swift(OSClient): + """Wrapper for SwiftClient which returns an authenticated native client. + + """ + def create_client(self, service_type=None): + """Return swift client.""" + from swiftclient import client as swift + + auth_token = self.keystone.auth_ref.auth_token + client = swift.Connection(retries=1, + preauthurl=self._get_endpoint(service_type), + preauthtoken=auth_token, + insecure=self.credential.https_insecure, + cacert=self.credential.https_cacert, + user=self.credential.username, + tenant_name=self.credential.tenant_name, + ) + return client + + +@configure("ec2") +class EC2(OSClient): + """Wrapper for EC2Client which returns an authenticated native client. + + """ + def create_client(self): + """Return ec2 client.""" + LOG.warning("rally.osclient.EC2 is deprecated since Rally 0.10.0.") + + import boto + + kc = self.keystone() + + if kc.version != "v2.0": + raise exceptions.RallyException( + "Rally EC2 scenario supports only Keystone version 2") + ec2_credential = kc.ec2.create(user_id=kc.auth_user_id, + tenant_id=kc.auth_tenant_id) + client = boto.connect_ec2_endpoint( + url=self._get_endpoint(), + aws_access_key_id=ec2_credential.access, + aws_secret_access_key=ec2_credential.secret, + is_secure=self.credential.https_insecure) + return client + + +@configure("monasca", default_version="2_0", + default_service_type="monitoring", supported_versions=["2_0"]) +class Monasca(OSClient): + """Wrapper for MonascaClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return monasca client.""" + from monascaclient import client as monasca + + # Change this to use session once it's supported by monascaclient + client = monasca.Client( + self.choose_version(version), + self._get_endpoint(service_type), + token=self.keystone.auth_ref.auth_token, + timeout=CONF.openstack_client_http_timeout, + insecure=self.credential.https_insecure, + **self._get_auth_info(project_name_key="tenant_name")) + return client + + +@configure("senlin", default_version="1", default_service_type="clustering", + supported_versions=["1"]) +class Senlin(OSClient): + """Wrapper for SenlinClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return senlin client.""" + from senlinclient import client as senlin + + return senlin.Client( + self.choose_version(version), + **self._get_auth_info(project_name_key="project_name", + cacert_key="cert", + endpoint_type="interface")) + + +@configure("magnum", default_version="1", supported_versions=["1"], + default_service_type="container-infra",) +class Magnum(OSClient): + """Wrapper for MagnumClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return magnum client.""" + from magnumclient import client as magnum + + api_url = self._get_endpoint(service_type) + session = self.keystone.get_session()[0] + + return magnum.Client( + session=session, + interface=self.credential.endpoint_type, + magnum_url=api_url) + + +@configure("watcher", default_version="1", default_service_type="infra-optim", + supported_versions=["1"]) +class Watcher(OSClient): + """Wrapper for WatcherClient which returns an authenticated native client. + + """ + def create_client(self, version=None, service_type=None): + """Return watcher client.""" + from watcherclient import client as watcher_client + watcher_api_url = self._get_endpoint( + self.choose_service_type(service_type)) + client = watcher_client.Client( + self.choose_version(version), + endpoint=watcher_api_url, + session=self.keystone.get_session()[0]) + return client + + +class Clients(object): + """This class simplify and unify work with OpenStack python clients.""" + + def __init__(self, credential, api_info=None, cache=None): + self.credential = credential + self.api_info = api_info or {} + self.cache = cache or {} + + def __getattr__(self, client_name): + """Lazy load of clients.""" + return OSClient.get(client_name)(self.credential, self.api_info, + self.cache) + + @classmethod + def create_from_env(cls): + creds = envutils.get_creds_from_env_vars() + from rally.plugins.openstack import credential + oscred = credential.OpenStackCredential( + auth_url=creds["auth_url"], + username=creds["admin"]["username"], + password=creds["admin"]["password"], + tenant_name=creds["admin"]["tenant_name"], + endpoint_type=creds["endpoint_type"], + user_domain_name=creds["admin"].get("user_domain_name"), + project_domain_name=creds["admin"].get("project_domain_name"), + endpoint=creds["endpoint"], + region_name=creds["region_name"], + https_cacert=creds["https_cacert"], + https_insecure=creds["https_insecure"]) + return cls(oscred) + + def clear(self): + """Remove all cached client handles.""" + self.cache = {} + + def verified_keystone(self): + """Ensure keystone endpoints are valid and then authenticate + + :returns: Keystone Client + """ + # Ensure that user is admin + if "admin" not in [role.lower() for role in + self.keystone.auth_ref.role_names]: + raise exceptions.InvalidAdminException( + username=self.credential.username) + return self.keystone() + + def services(self): + """Return available services names and types. + + :returns: dict, {"service_type": "service_name", ...} + """ + if "services_data" not in self.cache: + services_data = {} + available_services = self.keystone.service_catalog.get_endpoints() + for stype in available_services.keys(): + if stype in consts.ServiceType: + services_data[stype] = consts.ServiceType[stype] + else: + services_data[stype] = "__unknown__" + self.cache["services_data"] = services_data + + return self.cache["services_data"] diff --git a/tests/unit/plugins/openstack/test_osclients.py b/tests/unit/plugins/openstack/test_osclients.py new file mode 100644 index 00000000..bc3b199b --- /dev/null +++ b/tests/unit/plugins/openstack/test_osclients.py @@ -0,0 +1,946 @@ +# Copyright 2013: Mirantis 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 ddt +import mock +from oslo_config import cfg + +from rally import consts +from rally import exceptions +from rally import osclients +from rally.plugins.openstack import credential as oscredential +from tests.unit import fakes +from tests.unit import test + + +@osclients.configure("dummy", supported_versions=("0.1", "1"), + default_service_type="bar") +class DummyClient(osclients.OSClient): + def create_client(self, *args, **kwargs): + pass + + +class OSClientTestCaseUtils(object): + + def set_up_keystone_mocks(self): + self.ksc_module = mock.MagicMock(__version__="2.0.0") + self.ksc_client = mock.MagicMock() + self.ksa_identity_plugin = mock.MagicMock() + self.ksa_password = mock.MagicMock( + return_value=self.ksa_identity_plugin) + self.ksa_identity = mock.MagicMock(Password=self.ksa_password) + + self.ksa_auth = mock.MagicMock() + self.ksa_session = mock.MagicMock() + self.patcher = mock.patch.dict("sys.modules", + {"keystoneclient": self.ksc_module, + "keystoneauth1": self.ksa_auth}) + self.patcher.start() + self.addCleanup(self.patcher.stop) + self.ksc_module.client = self.ksc_client + self.ksa_auth.identity = self.ksa_identity + self.ksa_auth.session = self.ksa_session + + def make_auth_args(self): + auth_kwargs = { + "auth_url": "http://auth_url/", "username": "user", + "password": "password", "tenant_name": "tenant", + "domain_name": "domain", "project_name": "project_name", + "project_domain_name": "project_domain_name", + "user_domain_name": "user_domain_name", + } + kwargs = {"https_insecure": False, "https_cacert": None} + kwargs.update(auth_kwargs) + return auth_kwargs, kwargs + + +@ddt.ddt +class OSClientTestCase(test.TestCase, OSClientTestCaseUtils): + + @ddt.data((0.1, True), (1, True), ("0.1", True), ("1", True), + (0.2, False), ("foo", False)) + @ddt.unpack + def test_validate_version(self, version, valid): + if valid: + DummyClient.validate_version(version) + else: + self.assertRaises(exceptions.ValidationError, + DummyClient.validate_version, version) + + def test_choose_service_type(self): + default_service_type = "default_service_type" + + @osclients.configure("test_choose_service_type", + default_service_type=default_service_type) + class FakeClient(osclients.OSClient): + create_client = mock.MagicMock() + + fake_client = FakeClient(mock.MagicMock(), {}, {}) + self.assertEqual(default_service_type, + fake_client.choose_service_type()) + self.assertEqual("foo", + fake_client.choose_service_type("foo")) + + @mock.patch("rally.osclients.Keystone.service_catalog") + @ddt.data( + {"endpoint_type": None, "service_type": None, "region_name": None}, + {"endpoint_type": "et", "service_type": "st", "region_name": "rn"} + ) + @ddt.unpack + def test__get_endpoint(self, mock_keystone_service_catalog, endpoint_type, + service_type, region_name): + credential = oscredential.OpenStackCredential( + "http://auth_url/v2.0", "user", "pass", + endpoint_type=endpoint_type, + region_name=region_name) + mock_choose_service_type = mock.MagicMock() + osclient = osclients.OSClient(credential, {}, mock.MagicMock()) + osclient.choose_service_type = mock_choose_service_type + mock_url_for = mock_keystone_service_catalog.url_for + self.assertEqual(mock_url_for.return_value, + osclient._get_endpoint(service_type)) + call_args = { + "service_type": mock_choose_service_type.return_value, + "region_name": region_name} + if endpoint_type: + call_args["interface"] = endpoint_type + mock_url_for.assert_called_once_with(**call_args) + mock_choose_service_type.assert_called_once_with(service_type) + + @mock.patch("rally.osclients.Keystone.get_session") + def test__get_session(self, mock_keystone_get_session): + osclient = osclients.OSClient(None, None, None) + auth_url = "auth_url" + version = "version" + import warnings + with mock.patch.object(warnings, "warn") as mock_warn: + self.assertEqual(mock_keystone_get_session.return_value, + osclient._get_session(auth_url, version)) + self.assertFalse(mock_warn.called) + mock_keystone_get_session.assert_called_once_with(version) + + +class CachedTestCase(test.TestCase): + + def test_cached(self): + clients = osclients.Clients(mock.MagicMock()) + client_name = "CachedTestCase.test_cached" + fake_client = osclients.configure(client_name)(osclients.OSClient)( + clients.credential, clients.api_info, clients.cache) + fake_client.create_client = mock.MagicMock() + + self.assertEqual({}, clients.cache) + fake_client() + self.assertEqual( + {client_name: fake_client.create_client.return_value}, + clients.cache) + fake_client.create_client.assert_called_once_with() + fake_client() + fake_client.create_client.assert_called_once_with() + fake_client("2") + self.assertEqual( + {client_name: fake_client.create_client.return_value, + "%s('2',)" % client_name: fake_client.create_client.return_value}, + clients.cache) + clients.clear() + self.assertEqual({}, clients.cache) + + +@ddt.ddt +class TestCreateKeystoneClient(test.TestCase, OSClientTestCaseUtils): + + def setUp(self): + super(TestCreateKeystoneClient, self).setUp() + self.credential = oscredential.OpenStackCredential( + "http://auth_url/v2.0", "user", "pass", "tenant") + + def test_create_client(self): + # NOTE(bigjools): This is a very poor testing strategy as it + # tightly couples the test implementation to the tested + # function's implementation. Ideally, we'd use a fake keystone + # but all that's happening here is that it's checking the right + # parameters were passed to the various parts that create a + # client. Hopefully one day we'll get a real fake from the + # keystone guys. + self.set_up_keystone_mocks() + keystone = osclients.Keystone(self.credential, {}, mock.MagicMock()) + keystone.get_session = mock.Mock( + return_value=(self.ksa_session, self.ksa_identity_plugin,)) + client = keystone.create_client(version=3) + + kwargs_session = self.credential.to_dict() + kwargs_session.update({ + "auth_url": "http://auth_url/", + "session": self.ksa_session, + "timeout": 180.0}) + keystone.get_session.assert_called_with() + called_with = self.ksc_client.Client.call_args_list[0][1] + self.assertEqual( + {"session": self.ksa_session, "timeout": 180.0, "version": "3"}, + called_with) + self.ksc_client.Client.assert_called_once_with( + session=self.ksa_session, timeout=180.0, version="3") + self.assertIs(client, self.ksc_client.Client()) + + def test_create_client_removes_url_path_if_version_specified(self): + # If specifying a version on the client creation call, ensure + # the auth_url is versionless and the version required is passed + # into the Client() call. + self.set_up_keystone_mocks() + auth_kwargs, all_kwargs = self.make_auth_args() + keystone = osclients.Keystone( + self.credential, {}, mock.MagicMock()) + keystone.get_session = mock.Mock( + return_value=(self.ksa_session, self.ksa_identity_plugin,)) + client = keystone.create_client(version="3") + + self.assertIs(client, self.ksc_client.Client()) + called_with = self.ksc_client.Client.call_args_list[0][1] + self.assertEqual( + {"session": self.ksa_session, "timeout": 180.0, "version": "3"}, + called_with) + + @ddt.data({"original": "https://example.com/identity/foo/v3", + "cropped": "https://example.com/identity/foo"}, + {"original": "https://example.com/identity/foo/v3/", + "cropped": "https://example.com/identity/foo"}, + {"original": "https://example.com/identity/foo/v2.0", + "cropped": "https://example.com/identity/foo"}, + {"original": "https://example.com/identity/foo/v2.0/", + "cropped": "https://example.com/identity/foo"}, + {"original": "https://example.com/identity/foo", + "cropped": "https://example.com/identity/foo"}) + @ddt.unpack + def test__remove_url_version(self, original, cropped): + credential = oscredential.OpenStackCredential( + original, "user", "pass", "tenant") + keystone = osclients.Keystone(credential, {}, {}) + self.assertEqual(cropped, keystone._remove_url_version()) + + @ddt.data("http://auth_url/v2.0", "http://auth_url/v3", + "http://auth_url/", "auth_url") + def test_keystone_get_session(self, auth_url): + credential = oscredential.OpenStackCredential( + auth_url, "user", "pass", "tenant") + self.set_up_keystone_mocks() + keystone = osclients.Keystone(credential, {}, {}) + + version_data = mock.Mock(return_value=[{"version": (1, 0)}]) + self.ksa_auth.discover.Discover.return_value = ( + mock.Mock(version_data=version_data)) + + self.assertEqual((self.ksa_session.Session.return_value, + self.ksa_identity_plugin), + keystone.get_session()) + if auth_url.endswith("v2.0"): + self.ksa_password.assert_called_once_with( + auth_url=auth_url, password="pass", + tenant_name="tenant", username="user") + else: + self.ksa_password.assert_called_once_with( + auth_url=auth_url, password="pass", + tenant_name="tenant", username="user", + domain_name=None, project_domain_name=None, + user_domain_name=None) + self.ksa_session.Session.assert_has_calls( + [mock.call(timeout=180.0, verify=True), + mock.call(auth=self.ksa_identity_plugin, timeout=180.0, + verify=True)]) + + def test_keystone_property(self): + keystone = osclients.Keystone(None, None, None) + self.assertRaises(exceptions.RallyException, lambda: keystone.keystone) + + @mock.patch("rally.osclients.Keystone.get_session") + def test_auth_ref(self, mock_keystone_get_session): + session = mock.MagicMock() + auth_plugin = mock.MagicMock() + mock_keystone_get_session.return_value = (session, auth_plugin) + cache = {} + keystone = osclients.Keystone(None, None, cache) + + self.assertEqual(auth_plugin.get_access.return_value, + keystone.auth_ref) + self.assertEqual(auth_plugin.get_access.return_value, + cache["keystone_auth_ref"]) + + # check that auth_ref was cached. + keystone.auth_ref + mock_keystone_get_session.assert_called_once_with() + + @mock.patch("keystoneauth1.identity.base.BaseIdentityPlugin.get_access") + def test_auth_ref_fails(self, mock_get_access): + mock_get_access.side_effect = Exception + keystone = osclients.Keystone(self.credential, {}, {}) + + try: + keystone.auth_ref + except exceptions.AuthenticationFailed: + pass + else: + self.fail("keystone.auth_ref didn't raise" + " exceptions.AuthenticationFailed") + + @mock.patch("rally.osclients.LOG.exception") + @mock.patch("rally.osclients.logging.is_debug") + @mock.patch("keystoneauth1.identity.base.BaseIdentityPlugin.get_access") + def test_auth_ref_debug(self, mock_get_access, + mock_is_debug, mock_log_exception): + mock_is_debug.return_value = True + mock_get_access.side_effect = Exception + keystone = osclients.Keystone(self.credential, {}, {}) + + try: + keystone.auth_ref + except exceptions.AuthenticationFailed: + pass + else: + self.fail("keystone.auth_ref didn't raise" + " exceptions.AuthenticationFailed") + + mock_log_exception.assert_called_once_with(mock.ANY) + mock_is_debug.assert_called_once_with() + + +@ddt.ddt +class OSClientsTestCase(test.TestCase): + + def setUp(self): + super(OSClientsTestCase, self).setUp() + self.credential = oscredential.OpenStackCredential( + "http://auth_url/v2.0", "user", "pass", "tenant") + self.clients = osclients.Clients(self.credential, {}) + + self.fake_keystone = fakes.FakeKeystoneClient() + + keystone_patcher = mock.patch( + "rally.osclients.Keystone.create_client", + return_value=self.fake_keystone) + self.mock_create_keystone_client = keystone_patcher.start() + + self.auth_ref_patcher = mock.patch("rally.osclients.Keystone.auth_ref") + self.auth_ref = self.auth_ref_patcher.start() + + self.service_catalog = self.auth_ref.service_catalog + self.service_catalog.url_for = mock.MagicMock() + + def test_create_from_env(self): + with mock.patch.dict("os.environ", + {"OS_AUTH_URL": "foo_auth_url", + "OS_USERNAME": "foo_username", + "OS_PASSWORD": "foo_password", + "OS_TENANT_NAME": "foo_tenant_name", + "OS_REGION_NAME": "foo_region_name"}): + clients = osclients.Clients.create_from_env() + + self.assertEqual("foo_auth_url", clients.credential.auth_url) + self.assertEqual("foo_username", clients.credential.username) + self.assertEqual("foo_password", clients.credential.password) + self.assertEqual("foo_tenant_name", clients.credential.tenant_name) + self.assertEqual("foo_region_name", clients.credential.region_name) + + def test_keystone(self): + self.assertNotIn("keystone", self.clients.cache) + client = self.clients.keystone() + self.assertEqual(self.fake_keystone, client) + credential = {"timeout": cfg.CONF.openstack_client_http_timeout, + "insecure": False, "cacert": None} + kwargs = self.credential.to_dict() + kwargs.update(credential) + self.mock_create_keystone_client.assert_called_once_with() + self.assertEqual(self.fake_keystone, self.clients.cache["keystone"]) + + def test_keystone_versions(self): + self.clients.keystone.validate_version(2) + self.clients.keystone.validate_version(3) + + def test_keysonte_service_type(self): + self.assertRaises(exceptions.RallyException, + self.clients.keystone.is_service_type_configurable) + + def test_verified_keystone(self): + self.auth_ref.role_names = ["admin"] + self.assertEqual(self.mock_create_keystone_client.return_value, + self.clients.verified_keystone()) + + def test_verified_keystone_user_not_admin(self): + self.auth_ref.role_names = ["notadmin"] + self.assertRaises(exceptions.InvalidAdminException, + self.clients.verified_keystone) + + @mock.patch("rally.osclients.Keystone.get_session") + def test_verified_keystone_authentication_fails(self, + mock_keystone_get_session): + self.auth_ref_patcher.stop() + mock_keystone_get_session.side_effect = ( + exceptions.AuthenticationFailed( + username=self.credential.username, + project=self.credential.tenant_name, + url=self.credential.auth_url, + etype=KeyError, + error="oops") + ) + self.assertRaises(exceptions.AuthenticationFailed, + self.clients.verified_keystone) + + @mock.patch("rally.osclients.Nova._get_endpoint") + def test_nova(self, mock_nova__get_endpoint): + fake_nova = fakes.FakeNovaClient() + mock_nova__get_endpoint.return_value = "http://fake.to:2/fake" + mock_nova = mock.MagicMock() + mock_nova.client.Client.return_value = fake_nova + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("nova", self.clients.cache) + with mock.patch.dict("sys.modules", + {"novaclient": mock_nova, + "keystoneauth1": mock_keystoneauth1}): + mock_keystoneauth1.discover.Discover.return_value = ( + mock.Mock(version_data=mock.Mock(return_value=[ + {"version": (2, 0)}])) + ) + client = self.clients.nova() + self.assertEqual(fake_nova, client) + kw = { + "version": "2", + "session": mock_keystoneauth1.session.Session(), + "endpoint_override": mock_nova__get_endpoint.return_value} + mock_nova.client.Client.assert_called_once_with(**kw) + self.assertEqual(fake_nova, self.clients.cache["nova"]) + + def test_nova_validate_version(self): + osclients.Nova.validate_version("2") + self.assertRaises(exceptions.RallyException, + osclients.Nova.validate_version, "foo") + + def test_nova_service_type(self): + self.clients.nova.is_service_type_configurable() + + @mock.patch("rally.osclients.Neutron._get_endpoint") + def test_neutron(self, mock_neutron__get_endpoint): + fake_neutron = fakes.FakeNeutronClient() + mock_neutron__get_endpoint.return_value = "http://fake.to:2/fake" + mock_neutron = mock.MagicMock() + mock_keystoneauth1 = mock.MagicMock() + mock_neutron.client.Client.return_value = fake_neutron + self.assertNotIn("neutron", self.clients.cache) + with mock.patch.dict("sys.modules", + {"neutronclient.neutron": mock_neutron, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.neutron() + self.assertEqual(fake_neutron, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint_override": mock_neutron__get_endpoint.return_value} + mock_neutron.client.Client.assert_called_once_with("2.0", **kw) + self.assertEqual(fake_neutron, self.clients.cache["neutron"]) + + @mock.patch("rally.osclients.Neutron._get_endpoint") + def test_neutron_endpoint_type(self, mock_neutron__get_endpoint): + fake_neutron = fakes.FakeNeutronClient() + mock_neutron__get_endpoint.return_value = "http://fake.to:2/fake" + mock_neutron = mock.MagicMock() + mock_keystoneauth1 = mock.MagicMock() + mock_neutron.client.Client.return_value = fake_neutron + self.assertNotIn("neutron", self.clients.cache) + self.credential.endpoint_type = "internal" + with mock.patch.dict("sys.modules", + {"neutronclient.neutron": mock_neutron, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.neutron() + self.assertEqual(fake_neutron, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint_override": mock_neutron__get_endpoint.return_value, + "endpoint_type": "internal"} + mock_neutron.client.Client.assert_called_once_with("2.0", **kw) + self.assertEqual(fake_neutron, self.clients.cache["neutron"]) + + @mock.patch("rally.osclients.Heat._get_endpoint") + def test_heat(self, mock_heat__get_endpoint): + fake_heat = fakes.FakeHeatClient() + mock_heat__get_endpoint.return_value = "http://fake.to:2/fake" + mock_heat = mock.MagicMock() + mock_keystoneauth1 = mock.MagicMock() + mock_heat.client.Client.return_value = fake_heat + self.assertNotIn("heat", self.clients.cache) + with mock.patch.dict("sys.modules", + {"heatclient": mock_heat, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.heat() + self.assertEqual(fake_heat, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint": mock_heat__get_endpoint.return_value, + "endpoint_override": mock_heat__get_endpoint.return_value} + mock_heat.client.Client.assert_called_once_with("1", **kw) + self.assertEqual(fake_heat, self.clients.cache["heat"]) + + @mock.patch("rally.osclients.Heat._get_endpoint") + def test_heat_endpoint_type_interface(self, mock_heat__get_endpoint): + fake_heat = fakes.FakeHeatClient() + mock_heat__get_endpoint.return_value = "http://fake.to:2/fake" + mock_heat = mock.MagicMock() + mock_keystoneauth1 = mock.MagicMock() + mock_heat.client.Client.return_value = fake_heat + self.assertNotIn("heat", self.clients.cache) + self.credential.endpoint_type = "internal" + self.credential.interface = "internal" + with mock.patch.dict("sys.modules", + {"heatclient": mock_heat, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.heat() + self.assertEqual(fake_heat, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint": mock_heat__get_endpoint.return_value, + "endpoint_override": mock_heat__get_endpoint.return_value, + "endpoint_type": "internal", + "interface": "internal"} + mock_heat.client.Client.assert_called_once_with("1", **kw) + self.assertEqual(fake_heat, self.clients.cache["heat"]) + + @mock.patch("rally.osclients.Glance._get_endpoint") + def test_glance(self, mock_glance__get_endpoint): + fake_glance = fakes.FakeGlanceClient() + mock_glance = mock.MagicMock() + mock_glance__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + mock_glance.Client = mock.MagicMock(return_value=fake_glance) + with mock.patch.dict("sys.modules", + {"glanceclient": mock_glance, + "keystoneauth1": mock_keystoneauth1}): + self.assertNotIn("glance", self.clients.cache) + client = self.clients.glance() + self.assertEqual(fake_glance, client) + kw = { + "version": "2", + "session": mock_keystoneauth1.session.Session(), + "endpoint_override": mock_glance__get_endpoint.return_value} + mock_glance.Client.assert_called_once_with(**kw) + self.assertEqual(fake_glance, self.clients.cache["glance"]) + + @mock.patch("rally.osclients.Cinder._get_endpoint") + def test_cinder(self, mock_cinder__get_endpoint): + fake_cinder = mock.MagicMock(client=fakes.FakeCinderClient()) + mock_cinder = mock.MagicMock() + mock_cinder.client.Client.return_value = fake_cinder + mock_cinder__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("cinder", self.clients.cache) + with mock.patch.dict("sys.modules", + {"cinderclient": mock_cinder, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.cinder() + self.assertEqual(fake_cinder, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint_override": mock_cinder__get_endpoint.return_value} + mock_cinder.client.Client.assert_called_once_with( + "2", **kw) + self.assertEqual(fake_cinder, self.clients.cache["cinder"]) + + @mock.patch("rally.osclients.Manila._get_endpoint") + def test_manila(self, mock_manila__get_endpoint): + mock_manila = mock.MagicMock() + mock_manila__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("manila", self.clients.cache) + with mock.patch.dict("sys.modules", + {"manilaclient": mock_manila, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.manila() + self.assertEqual(mock_manila.client.Client.return_value, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "service_catalog_url": mock_manila__get_endpoint.return_value + } + mock_manila.client.Client.assert_called_once_with("1", **kw) + self.assertEqual( + mock_manila.client.Client.return_value, + self.clients.cache["manila"]) + + def test_manila_validate_version(self): + osclients.Manila.validate_version("2.0") + osclients.Manila.validate_version("2.32") + self.assertRaises(exceptions.RallyException, + osclients.Manila.validate_version, "foo") + + @mock.patch("rally.osclients.Ceilometer._get_endpoint") + def test_ceilometer(self, mock_ceilometer__get_endpoint): + fake_ceilometer = fakes.FakeCeilometerClient() + mock_ceilometer = mock.MagicMock() + mock_ceilometer__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + mock_ceilometer.client.get_client = mock.MagicMock( + return_value=fake_ceilometer) + self.assertNotIn("ceilometer", self.clients.cache) + with mock.patch.dict("sys.modules", + {"ceilometerclient": mock_ceilometer, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.ceilometer() + self.assertEqual(fake_ceilometer, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint_override": mock_ceilometer__get_endpoint.return_value + } + mock_ceilometer.client.get_client.assert_called_once_with("2", + **kw) + self.assertEqual(fake_ceilometer, + self.clients.cache["ceilometer"]) + + def test_gnocchi(self): + fake_gnocchi = fakes.FakeGnocchiClient() + mock_gnocchi = mock.MagicMock() + mock_gnocchi.client.Client.return_value = fake_gnocchi + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("gnocchi", self.clients.cache) + with mock.patch.dict("sys.modules", + {"gnocchiclient": mock_gnocchi, + "keystoneauth1": mock_keystoneauth1}): + mock_keystoneauth1.discover.Discover.return_value = ( + mock.Mock(version_data=mock.Mock(return_value=[ + {"version": (1, 0)}])) + ) + client = self.clients.gnocchi() + + self.assertEqual(fake_gnocchi, client) + kw = {"version": "1", + "session": mock_keystoneauth1.session.Session(), + "service_type": "metric"} + mock_gnocchi.client.Client.assert_called_once_with(**kw) + self.assertEqual(fake_gnocchi, self.clients.cache["gnocchi"]) + + def test_monasca(self): + fake_monasca = fakes.FakeMonascaClient() + mock_monasca = mock.MagicMock() + mock_monasca.client.Client.return_value = fake_monasca + self.assertNotIn("monasca", self.clients.cache) + with mock.patch.dict("sys.modules", + {"monascaclient": mock_monasca}): + client = self.clients.monasca() + self.assertEqual(fake_monasca, client) + self.service_catalog.url_for.assert_called_once_with( + service_type="monitoring", + region_name=self.credential.region_name) + os_endpoint = self.service_catalog.url_for.return_value + kw = {"token": self.auth_ref.auth_token, + "timeout": cfg.CONF.openstack_client_http_timeout, + "insecure": False, "cacert": None, + "username": self.credential.username, + "password": self.credential.password, + "tenant_name": self.credential.tenant_name, + "auth_url": self.credential.auth_url + } + mock_monasca.client.Client.assert_called_once_with("2_0", + os_endpoint, + **kw) + self.assertEqual(mock_monasca.client.Client.return_value, + self.clients.cache["monasca"]) + + @mock.patch("rally.osclients.Ironic._get_endpoint") + def test_ironic(self, mock_ironic__get_endpoint): + fake_ironic = fakes.FakeIronicClient() + mock_ironic = mock.MagicMock() + mock_ironic.client.get_client = mock.MagicMock( + return_value=fake_ironic) + mock_ironic__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("ironic", self.clients.cache) + with mock.patch.dict("sys.modules", + {"ironicclient": mock_ironic, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.ironic() + self.assertEqual(fake_ironic, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint": mock_ironic__get_endpoint.return_value} + mock_ironic.client.get_client.assert_called_once_with("1", **kw) + self.assertEqual(fake_ironic, self.clients.cache["ironic"]) + + @mock.patch("rally.osclients.Sahara._get_endpoint") + def test_sahara(self, mock_sahara__get_endpoint): + fake_sahara = fakes.FakeSaharaClient() + mock_sahara = mock.MagicMock() + mock_sahara.client.Client = mock.MagicMock(return_value=fake_sahara) + mock_sahara__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("sahara", self.clients.cache) + with mock.patch.dict("sys.modules", + {"saharaclient": mock_sahara, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.sahara() + self.assertEqual(fake_sahara, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "sahara_url": mock_sahara__get_endpoint.return_value} + mock_sahara.client.Client.assert_called_once_with(1.1, **kw) + self.assertEqual(fake_sahara, self.clients.cache["sahara"]) + + def test_zaqar(self): + fake_zaqar = fakes.FakeZaqarClient() + mock_zaqar = mock.MagicMock() + mock_zaqar.client.Client = mock.MagicMock(return_value=fake_zaqar) + self.assertNotIn("zaqar", self.clients.cache) + mock_keystoneauth1 = mock.MagicMock() + with mock.patch.dict("sys.modules", {"zaqarclient.queues": + mock_zaqar, + "keystoneauth1": + mock_keystoneauth1}): + client = self.clients.zaqar() + self.assertEqual(fake_zaqar, client) + self.service_catalog.url_for.assert_called_once_with( + service_type="messaging", + region_name=self.credential.region_name) + fake_zaqar_url = self.service_catalog.url_for.return_value + mock_zaqar.client.Client.assert_called_once_with( + url=fake_zaqar_url, version=1.1, + session=mock_keystoneauth1.session.Session()) + self.assertEqual(fake_zaqar, self.clients.cache["zaqar"], + mock_keystoneauth1.session.Session()) + + @mock.patch("rally.osclients.Trove._get_endpoint") + def test_trove(self, mock_trove__get_endpoint): + fake_trove = fakes.FakeTroveClient() + mock_trove = mock.MagicMock() + mock_trove.client.Client = mock.MagicMock(return_value=fake_trove) + mock_trove__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + self.assertNotIn("trove", self.clients.cache) + with mock.patch.dict("sys.modules", + {"troveclient": mock_trove, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.trove() + self.assertEqual(fake_trove, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint": mock_trove__get_endpoint.return_value} + mock_trove.client.Client.assert_called_once_with("1.0", **kw) + self.assertEqual(fake_trove, self.clients.cache["trove"]) + + def test_mistral(self): + fake_mistral = fakes.FakeMistralClient() + mock_mistral = mock.Mock() + mock_mistral.client.client.return_value = fake_mistral + + self.assertNotIn("mistral", self.clients.cache) + with mock.patch.dict( + "sys.modules", {"mistralclient": mock_mistral, + "mistralclient.api": mock_mistral}): + client = self.clients.mistral() + self.assertEqual(fake_mistral, client) + self.service_catalog.url_for.assert_called_once_with( + service_type="workflowv2", + region_name=self.credential.region_name + ) + fake_mistral_url = self.service_catalog.url_for.return_value + mock_mistral.client.client.assert_called_once_with( + mistral_url=fake_mistral_url, + service_type="workflowv2", + auth_token=self.auth_ref.auth_token + ) + self.assertEqual(fake_mistral, self.clients.cache["mistral"]) + + def test_swift(self): + fake_swift = fakes.FakeSwiftClient() + mock_swift = mock.MagicMock() + mock_swift.client.Connection = mock.MagicMock(return_value=fake_swift) + self.assertNotIn("swift", self.clients.cache) + with mock.patch.dict("sys.modules", {"swiftclient": mock_swift}): + client = self.clients.swift() + self.assertEqual(fake_swift, client) + self.service_catalog.url_for.assert_called_once_with( + service_type="object-store", + region_name=self.credential.region_name) + kw = {"retries": 1, + "preauthurl": self.service_catalog.url_for.return_value, + "preauthtoken": self.auth_ref.auth_token, + "insecure": False, + "cacert": None, + "user": self.credential.username, + "tenant_name": self.credential.tenant_name, + } + mock_swift.client.Connection.assert_called_once_with(**kw) + self.assertEqual(fake_swift, self.clients.cache["swift"]) + + @mock.patch("rally.osclients.EC2._get_endpoint") + def test_ec2(self, mock_ec2__get_endpoint): + mock_boto = mock.Mock() + self.fake_keystone.ec2 = mock.Mock() + self.fake_keystone.ec2.create.return_value = mock.Mock( + access="fake_access", secret="fake_secret") + mock_ec2__get_endpoint.return_value = "http://fake.to:1/fake" + fake_ec2 = fakes.FakeEC2Client() + mock_boto.connect_ec2_endpoint.return_value = fake_ec2 + + self.assertNotIn("ec2", self.clients.cache) + with mock.patch.dict("sys.modules", {"boto": mock_boto}): + client = self.clients.ec2() + + self.assertEqual(fake_ec2, client) + kw = { + "url": "http://fake.to:1/fake", + "aws_access_key_id": "fake_access", + "aws_secret_access_key": "fake_secret", + "is_secure": self.credential.insecure, + } + mock_boto.connect_ec2_endpoint.assert_called_once_with(**kw) + self.assertEqual(fake_ec2, self.clients.cache["ec2"]) + + @mock.patch("rally.osclients.Keystone.service_catalog") + def test_services(self, mock_keystone_service_catalog): + available_services = {consts.ServiceType.IDENTITY: {}, + consts.ServiceType.COMPUTE: {}, + "some_service": {}} + mock_get_endpoints = mock_keystone_service_catalog.get_endpoints + mock_get_endpoints.return_value = available_services + clients = osclients.Clients(self.credential) + + self.assertEqual( + {consts.ServiceType.IDENTITY: consts.Service.KEYSTONE, + consts.ServiceType.COMPUTE: consts.Service.NOVA, + "some_service": "__unknown__"}, + clients.services()) + + def test_murano(self): + fake_murano = fakes.FakeMuranoClient() + mock_murano = mock.Mock() + mock_murano.client.Client.return_value = fake_murano + self.assertNotIn("murano", self.clients.cache) + with mock.patch.dict("sys.modules", {"muranoclient": mock_murano}): + client = self.clients.murano() + self.assertEqual(fake_murano, client) + self.service_catalog.url_for.assert_called_once_with( + service_type="application-catalog", + region_name=self.credential.region_name + ) + kw = {"endpoint": self.service_catalog.url_for.return_value, + "token": self.auth_ref.auth_token} + mock_murano.client.Client.assert_called_once_with("1", **kw) + self.assertEqual(fake_murano, self.clients.cache["murano"]) + + @mock.patch("rally.osclients.Keystone.get_session") + @ddt.data( + {}, + {"version": "2"}, + {"version": "1"}, + {"version": None} + ) + @ddt.unpack + def test_designate(self, mock_keystone_get_session, version=None): + fake_designate = fakes.FakeDesignateClient() + mock_designate = mock.Mock() + mock_designate.client.Client.return_value = fake_designate + + mock_keystone_get_session.return_value = ("fake_session", + "fake_auth_plugin") + + self.assertNotIn("designate", self.clients.cache) + with mock.patch.dict("sys.modules", + {"designateclient": mock_designate}): + if version is not None: + client = self.clients.designate(version=version) + else: + client = self.clients.designate() + self.assertEqual(fake_designate, client) + self.service_catalog.url_for.assert_called_once_with( + service_type="dns", + region_name=self.credential.region_name + ) + + default = version or "1" + + # Check that we append /v + url = self.service_catalog.url_for.return_value + url.__iadd__.assert_called_once_with("/v%s" % default) + + mock_keystone_get_session.assert_called_once_with() + + if version == "2": + mock_designate.client.Client.assert_called_once_with( + version, + endpoint_override=url.__iadd__.return_value, + session="fake_session") + elif version == "1": + mock_designate.client.Client.assert_called_once_with( + version, + endpoint=url.__iadd__.return_value, + session="fake_session") + + key = "designate" + if version is not None: + key += "%s" % {"version": version} + self.assertEqual(fake_designate, self.clients.cache[key]) + + def test_senlin(self): + mock_senlin = mock.MagicMock() + self.assertNotIn("senlin", self.clients.cache) + with mock.patch.dict("sys.modules", {"senlinclient": mock_senlin}): + client = self.clients.senlin() + self.assertEqual(mock_senlin.client.Client.return_value, client) + mock_senlin.client.Client.assert_called_once_with( + "1", + username=self.credential.username, + password=self.credential.password, + project_name=self.credential.tenant_name, + cert=self.credential.cacert, + auth_url=self.credential.auth_url) + self.assertEqual( + mock_senlin.client.Client.return_value, + self.clients.cache["senlin"]) + + @mock.patch("rally.osclients.Magnum._get_endpoint") + def test_magnum(self, mock_magnum__get_endpoint): + fake_magnum = fakes.FakeMagnumClient() + mock_magnum = mock.MagicMock() + mock_magnum.client.Client.return_value = fake_magnum + + mock_magnum__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + + self.assertNotIn("magnum", self.clients.cache) + with mock.patch.dict("sys.modules", + {"magnumclient": mock_magnum, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.magnum() + + self.assertEqual(fake_magnum, client) + kw = { + "interface": self.credential.endpoint_type, + "session": mock_keystoneauth1.session.Session(), + "magnum_url": mock_magnum__get_endpoint.return_value} + + mock_magnum.client.Client.assert_called_once_with(**kw) + self.assertEqual(fake_magnum, self.clients.cache["magnum"]) + + @mock.patch("rally.osclients.Watcher._get_endpoint") + def test_watcher(self, mock_watcher__get_endpoint): + fake_watcher = fakes.FakeWatcherClient() + mock_watcher = mock.MagicMock() + mock_watcher__get_endpoint.return_value = "http://fake.to:2/fake" + mock_keystoneauth1 = mock.MagicMock() + mock_watcher.client.Client.return_value = fake_watcher + self.assertNotIn("watcher", self.clients.cache) + with mock.patch.dict("sys.modules", + {"watcherclient": mock_watcher, + "keystoneauth1": mock_keystoneauth1}): + client = self.clients.watcher() + + self.assertEqual(fake_watcher, client) + kw = { + "session": mock_keystoneauth1.session.Session(), + "endpoint": mock_watcher__get_endpoint.return_value} + + mock_watcher.client.Client.assert_called_once_with("1", **kw) + self.assertEqual(fake_watcher, self.clients.cache["watcher"])