From f89c32d6982c01a512128dd9700263cc550dbfac Mon Sep 17 00:00:00 2001 From: Anton Studenov Date: Tue, 21 Mar 2017 12:49:11 +0300 Subject: [PATCH] [Core] Create new plugin based class for credentials * Moved credential class from rally.objects to rally.deployment. * Added OpenStackCredential plugin. * Simplified getting of osclients. * Enabled client caching on OpenStackCredential plugin. spec: deployment_type.rst Change-Id: I9ac8c4989c681885534683bd4ee4bc34fcfabaac --- devstack/lib/rally | 36 ++-- rally/plugins/openstack/cleanup/manager.py | 20 +- .../openstack/context/keystone/users.py | 12 +- rally/plugins/openstack/credential.py | 173 ++++++++++++++++++ rally/plugins/openstack/scenario.py | 4 +- .../openstack/verification/tempest/config.py | 20 +- .../openstack/verification/tempest/context.py | 4 +- tests/ci/osresources.py | 7 +- tests/ci/rally_gate_functions.sh | 2 +- tests/ci/rally_verify.py | 7 +- tests/unit/doc/test_jsonschemas.py | 11 +- tests/unit/fakes.py | 31 +++- .../plugins/openstack/cleanup/test_manager.py | 85 +-------- .../openstack/context/keystone/test_users.py | 18 +- .../sahara/test_sahara_output_data_sources.py | 6 +- .../unit/plugins/openstack/test_credential.py | 149 +++++++++++++++ tests/unit/plugins/openstack/test_scenario.py | 23 +-- .../verification/tempest/test_config.py | 49 +++-- .../verification/tempest/test_context.py | 42 ++--- 19 files changed, 479 insertions(+), 220 deletions(-) create mode 100644 rally/plugins/openstack/credential.py create mode 100644 tests/unit/plugins/openstack/test_credential.py diff --git a/devstack/lib/rally b/devstack/lib/rally index 309eba04..8c36c303 100644 --- a/devstack/lib/rally +++ b/devstack/lib/rally @@ -50,12 +50,16 @@ then cat >$1 <$1 < return all plugins If True -> return only admin plugins If False -> return only non admin plugins - :param admin: rally.common.objects.Credential that corresponds to OpenStack - admin. + :param admin: rally.deployment.credential.Credential that corresponds to + OpenStack admin. :param users: List of OpenStack users that was used during benchmarking. Every user has next structure: { "id": , "tenant_id": , - "credential": + "credential": } :param superclass: The plugin superclass to perform cleanup for. E.g., this could be diff --git a/rally/plugins/openstack/context/keystone/users.py b/rally/plugins/openstack/context/keystone/users.py index 98596c94..380ce7e1 100644 --- a/rally/plugins/openstack/context/keystone/users.py +++ b/rally/plugins/openstack/context/keystone/users.py @@ -21,11 +21,11 @@ from oslo_config import cfg from rally.common import broker from rally.common.i18n import _ from rally.common import logging -from rally.common import objects from rally.common import utils as rutils from rally import consts from rally import exceptions from rally import osclients +from rally.plugins.openstack import credential from rally.plugins.openstack.services.identity import identity from rally.plugins.openstack.wrappers import network from rally.task import context @@ -207,10 +207,12 @@ class UserGenerator(context.Context): project_id=tenant_id, domain_name=user_dom, default_role=default_role) - user_credential = objects.Credential( - self.credential.auth_url, user.name, password, - self.context["tenants"][tenant_id]["name"], - consts.EndpointPermission.USER, + user_credential = credential.OpenStackCredential( + auth_url=self.credential.auth_url, + username=user.name, + password=password, + tenant_name=self.context["tenants"][tenant_id]["name"], + permission=consts.EndpointPermission.USER, project_domain_name=project_dom, user_domain_name=user_dom, endpoint_type=self.credential.endpoint_type, diff --git a/rally/plugins/openstack/credential.py b/rally/plugins/openstack/credential.py new file mode 100644 index 00000000..1a34bf8f --- /dev/null +++ b/rally/plugins/openstack/credential.py @@ -0,0 +1,173 @@ +# Copyright 2017: 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. + + +from rally import consts +from rally.deployment import credential +from rally import osclients + + +@credential.configure("openstack") +class OpenStackCredential(credential.Credential): + """Credential for OpenStack.""" + + def __init__(self, auth_url, username, password, tenant_name=None, + project_name=None, + permission=consts.EndpointPermission.USER, + region_name=None, endpoint_type=None, + domain_name=None, endpoint=None, user_domain_name=None, + project_domain_name=None, + https_insecure=False, https_cacert=None): + self.auth_url = auth_url + self.username = username + self.password = password + self.tenant_name = tenant_name or project_name + self.permission = permission + self.region_name = region_name + self.endpoint_type = endpoint_type + self.domain_name = domain_name + self.user_domain_name = user_domain_name + self.project_domain_name = project_domain_name + self.endpoint = endpoint + self.https_insecure = https_insecure + self.https_cacert = https_cacert + + self._clients_cache = {} + + # backward compatibility + @property + def insecure(self): + return self.https_insecure + + # backward compatibility + @property + def cacert(self): + return self.https_cacert + + def to_dict(self): + return {"auth_url": self.auth_url, + "username": self.username, + "password": self.password, + "tenant_name": self.tenant_name, + "region_name": self.region_name, + "endpoint_type": self.endpoint_type, + "domain_name": self.domain_name, + "endpoint": self.endpoint, + "https_insecure": self.https_insecure, + "https_cacert": self.https_cacert, + "user_domain_name": self.user_domain_name, + "project_domain_name": self.project_domain_name, + "permission": self.permission} + + def verify_connection(self): + if self.permission == consts.EndpointPermission.ADMIN: + self.clients().verified_keystone() + else: + self.clients().keystone() + + def list_services(self): + return self.clients().services() + + def clients(self, api_info=None): + return osclients.Clients(self, api_info=api_info, + cache=self._clients_cache) + + +@credential.configure_builder("openstack") +class OpenStackCredentialBuilder(credential.CredentialBuilder): + """Builds credentials provided by ExistingCloud config.""" + + USER_SCHEMA = { + "type": "object", + "oneOf": [ + { + "description": "Keystone V2.0", + "properties": { + "username": {"type": "string"}, + "password": {"type": "string"}, + "tenant_name": {"type": "string"}, + }, + "required": ["username", "password", "tenant_name"], + "additionalProperties": False + }, + { + "description": "Keystone V3.0", + "properties": { + "username": {"type": "string"}, + "password": {"type": "string"}, + "domain_name": {"type": "string"}, + "user_domain_name": {"type": "string"}, + "project_name": {"type": "string"}, + "project_domain_name": {"type": "string"}, + }, + "required": ["username", "password", "project_name"], + "additionalProperties": False + } + ], + } + + CONFIG_SCHEMA = { + "type": "object", + "properties": { + "admin": USER_SCHEMA, + "users": {"type": "array", "items": USER_SCHEMA}, + "auth_url": {"type": "string"}, + "region_name": {"type": "string"}, + # NOTE(andreykurilin): it looks like we do not use endpoint + # var at all + "endpoint": {"type": ["string", "null"]}, + "endpoint_type": { + "enum": [consts.EndpointType.ADMIN, + consts.EndpointType.INTERNAL, + consts.EndpointType.PUBLIC, + None]}, + "https_insecure": {"type": "boolean"}, + "https_cacert": {"type": "string"}, + }, + "required": ["auth_url", "admin"], + "additionalProperties": False + } + + def _create_credential(self, common, user, permission): + cred = OpenStackCredential( + auth_url=common["auth_url"], + username=user["username"], + password=user["password"], + tenant_name=user.get("project_name", user.get("tenant_name")), + permission=permission, + region_name=common.get("region_name"), + endpoint_type=common.get("endpoint_type"), + endpoint=common.get("endpoint"), + domain_name=user.get("domain_name"), + user_domain_name=user.get("user_domain_name", None), + project_domain_name=user.get("project_domain_name", None), + https_insecure=common.get("https_insecure", False), + https_cacert=common.get("https_cacert")) + return cred.to_dict() + + def build_credentials(self): + permissions = consts.EndpointPermission + + users = [self._create_credential(self.config, user, permissions.USER) + for user in self.config.get("users", [])] + + admin = self._create_credential(self.config, + self.config.get("admin"), + permissions.ADMIN) + + return {"admin": admin, "users": users} + + +# NOTE(astudenov): Let's consider moving rally.osclients here diff --git a/rally/plugins/openstack/scenario.py b/rally/plugins/openstack/scenario.py index 3f54aae2..4389ff3a 100644 --- a/rally/plugins/openstack/scenario.py +++ b/rally/plugins/openstack/scenario.py @@ -108,9 +108,9 @@ class OpenStackScenario(scenario.Scenario): @classmethod def validate(cls, name, config, admin=None, users=None, deployment=None): if admin: - admin = osclients.Clients(admin) + admin = admin.clients() if users: - users = [osclients.Clients(user["credential"]) for user in users] + users = [user["credential"].clients() for user in users] super(OpenStackScenario, cls).validate( name=name, config=config, admin=admin, users=users, deployment=deployment) diff --git a/rally/plugins/openstack/verification/tempest/config.py b/rally/plugins/openstack/verification/tempest/config.py index dc861378..24f15ccd 100644 --- a/rally/plugins/openstack/verification/tempest/config.py +++ b/rally/plugins/openstack/verification/tempest/config.py @@ -21,8 +21,6 @@ import six from six.moves import configparser from six.moves.urllib import parse -from rally.common import objects -from rally import osclients from rally.verification import utils @@ -88,7 +86,7 @@ class TempestConfigfileManager(object): def __init__(self, deployment): self.credential = deployment.get_credentials_for("openstack")["admin"] - self.clients = osclients.Clients(objects.Credential(**self.credential)) + self.clients = self.credential.clients() self.available_services = self.clients.services().values() self.conf = configparser.ConfigParser() @@ -100,14 +98,14 @@ class TempestConfigfileManager(object): def _configure_auth(self, section_name="auth"): self.conf.set(section_name, "admin_username", - self.credential["username"]) + self.credential.username) self.conf.set(section_name, "admin_password", - self.credential["password"]) + self.credential.password) self.conf.set(section_name, "admin_project_name", - self.credential["tenant_name"]) + self.credential.tenant_name) # Keystone v3 related parameter self.conf.set(section_name, "admin_domain_name", - self.credential["user_domain_name"] or "Default") + self.credential.user_domain_name or "Default") # Sahara has two service types: 'data_processing' and 'data-processing'. # 'data_processing' is deprecated, but it can be used in previous OpenStack @@ -120,9 +118,9 @@ class TempestConfigfileManager(object): def _configure_identity(self, section_name="identity"): self.conf.set(section_name, "region", - self.credential["region_name"]) + self.credential.region_name) - auth_url = self.credential["auth_url"] + auth_url = self.credential.auth_url if "/v2" not in auth_url and "/v3" not in auth_url: auth_version = "v2" auth_url_v2 = parse.urljoin(auth_url, "/v2.0") @@ -136,9 +134,9 @@ class TempestConfigfileManager(object): auth_url_v2.replace("/v2.0", "/v3")) self.conf.set(section_name, "disable_ssl_certificate_validation", - str(self.credential["https_insecure"])) + str(self.credential.https_insecure)) self.conf.set(section_name, "ca_certificates_file", - self.credential["https_cacert"]) + self.credential.https_cacert) # The compute section is configured in context class for Tempest resources. # Options which are configured there: 'image_ref', 'image_ref_alt', diff --git a/rally/plugins/openstack/verification/tempest/context.py b/rally/plugins/openstack/verification/tempest/context.py index c8a1022c..9375d51e 100644 --- a/rally/plugins/openstack/verification/tempest/context.py +++ b/rally/plugins/openstack/verification/tempest/context.py @@ -21,9 +21,7 @@ from six.moves import configparser from rally.common.i18n import _ from rally.common import logging -from rally.common import objects from rally import exceptions -from rally import osclients from rally.plugins.openstack.verification.tempest import config as conf from rally.plugins.openstack.wrappers import glance from rally.plugins.openstack.wrappers import network @@ -45,7 +43,7 @@ class TempestContext(context.VerifierContext): super(TempestContext, self).__init__(ctx) creds = self.verifier.deployment.get_credentials_for("openstack") - self.clients = osclients.Clients(objects.Credential(**creds["admin"])) + self.clients = creds["admin"].clients() self.available_services = self.clients.services().values() self.conf = configparser.ConfigParser() diff --git a/tests/ci/osresources.py b/tests/ci/osresources.py index fea64029..0c403aeb 100755 --- a/tests/ci/osresources.py +++ b/tests/ci/osresources.py @@ -24,10 +24,9 @@ import sys import six from rally.cli import cliutils -from rally.common import objects from rally.common.plugin import discover from rally import consts -from rally import osclients +from rally.plugins.openstack import credential def skip_if_service(service): @@ -340,7 +339,7 @@ class CloudResources(object): """ def __init__(self, **kwargs): - self.clients = osclients.Clients(objects.Credential(**kwargs)) + self.clients = credential.OpenStackCredential(**kwargs).clients() def _deduplicate(self, lst): """Change list duplicates to make all items unique. @@ -426,8 +425,8 @@ def main(): out = subprocess.check_output(["rally", "deployment", "config"]) config = json.loads(out if six.PY2 else out.decode("utf-8")) + config = config["creds"]["openstack"] config.update(config.pop("admin")) - del config["type"] if "users" in config: del config["users"] diff --git a/tests/ci/rally_gate_functions.sh b/tests/ci/rally_gate_functions.sh index 783e1830..8bdd249c 100644 --- a/tests/ci/rally_gate_functions.sh +++ b/tests/ci/rally_gate_functions.sh @@ -64,7 +64,7 @@ function setUp () { DEPLOYMENT_CONFIG_FILE=~/.rally/with-existing-users-config rally deployment config > $DEPLOYMENT_CONFIG_FILE - sed -i '1a "users": [\ + sed -i '3a "users": [\ {\ "username": "rally-test-user-1",\ "password": "rally-test-password-1",\ diff --git a/tests/ci/rally_verify.py b/tests/ci/rally_verify.py index 879678c9..e85cf05e 100755 --- a/tests/ci/rally_verify.py +++ b/tests/ci/rally_verify.py @@ -23,8 +23,7 @@ import sys import uuid from rally.cli import envutils -from rally.common import objects -from rally import osclients +from rally.plugins.openstack import credential from rally.ui import utils LOG = logging.getLogger(__name__) @@ -172,9 +171,9 @@ def main(): config = json.loads( subprocess.check_output(["rally", "deployment", "config"])) + config = config["creds"]["openstack"] config.update(config.pop("admin")) - del config["type"] - clients = osclients.Clients(objects.Credential(**config)) + clients = credential.OpenStackCredential(**config).clients() if args.ctx_create_resources: # If the 'ctx-create-resources' arg is provided, delete images and diff --git a/tests/unit/doc/test_jsonschemas.py b/tests/unit/doc/test_jsonschemas.py index 9081766a..c9421596 100644 --- a/tests/unit/doc/test_jsonschemas.py +++ b/tests/unit/doc/test_jsonschemas.py @@ -115,11 +115,12 @@ class ConfigSchemasTestCase(test.TestCase): self.fail(p, schema, ("Found unexpected key(s) for integer/number " "type: %s." % ", ".join(unexpected_keys))) - def _check_simpliest_types(self, p, schema): + def _check_simpliest_types(self, p, schema, type_name): unexpected_keys = set(schema.keys()) - {"type", "description"} if unexpected_keys: - self.fail(p, schema, ("Found unexpected key(s) for boolean type: " - "%s." % ", ".join(unexpected_keys))) + self.fail(p, schema, ("Found unexpected key(s) for %s type: " + "%s." % (type_name, + ", ".join(unexpected_keys)))) def _check_item(self, p, schema, definitions): if "type" in schema or "anyOf" in schema or "oneOf" in schema: @@ -135,7 +136,9 @@ class ConfigSchemasTestCase(test.TestCase): elif schema["type"] in ("number", "integer"): self._check_number_type(p, schema) elif schema["type"] in ("boolean", "null"): - self._check_simpliest_types(p, schema) + self._check_simpliest_types(p, schema, schema["type"]) + elif isinstance(schema["type"], list): + self._check_simpliest_types(p, schema, "mixed") else: self.fail(p, schema, "Wrong type is used: %s" % schema["type"]) diff --git a/tests/unit/fakes.py b/tests/unit/fakes.py index 569d8113..18145d39 100644 --- a/tests/unit/fakes.py +++ b/tests/unit/fakes.py @@ -28,7 +28,6 @@ import six from swiftclient import exceptions as swift_exceptions from rally import api -from rally.common import objects from rally.common import utils as rally_utils from rally import consts from rally.task import context @@ -83,6 +82,14 @@ def setup_dict(data, required=None, defaults=None): return defaults +def fake_credential(**config): + m = mock.Mock() + m.to_dict.return_value = config + for key, value in config.items(): + setattr(m, key, value) + return m + + class FakeResource(object): def __init__(self, manager=None, name=None, status="ACTIVE", items=None, @@ -1594,11 +1601,11 @@ class FakeClients(object): self._ec2 = None self._senlin = None self._watcher = None - self._credential = credential_ or objects.Credential( - "http://fake.example.org:5000/v2.0/", - "fake_username", - "fake_password", - "fake_tenant_name") + self._credential = credential_ or fake_credential( + auth_url="http://fake.example.org:5000/v2.0/", + username="fake_username", + password="fake_password", + tenant_name="fake_tenant_name") def keystone(self, version=None): if not self._keystone: @@ -1806,11 +1813,19 @@ class FakeUserContext(FakeContext): admin = { "id": "adminuuid", - "credential": objects.Credential("aurl", "aname", "apwd", "atenant") + "credential": fake_credential( + auth_url="aurl", + username="aname", + password="apwd", + tenant_name="atenant") } user = { "id": "uuid", - "credential": objects.Credential("url", "name", "pwd", "tenant"), + "credential": fake_credential( + auth_url="url", + username="name", + password="pwd", + tenant_name="tenant"), "tenant_id": "uuid" } tenants = {"uuid": {"name": "tenant"}} diff --git a/tests/unit/plugins/openstack/cleanup/test_manager.py b/tests/unit/plugins/openstack/cleanup/test_manager.py index bfa7dd5a..129871bc 100644 --- a/tests/unit/plugins/openstack/cleanup/test_manager.py +++ b/tests/unit/plugins/openstack/cleanup/test_manager.py @@ -31,86 +31,19 @@ class SeekAndDestroyTestCase(test.TestCase): # clear out the client cache manager.SeekAndDestroy.cache = {} - @mock.patch("%s.osclients.Clients" % BASE, - side_effect=[mock.MagicMock(), mock.MagicMock()]) - def test__get_cached_client(self, mock_clients): - destroyer = manager.SeekAndDestroy(None, None, None) - - self.assertIsNone(destroyer._get_cached_client(None)) - - users = [{"credential": "a"}, {"credential": "b"}] - - self.assertEqual(destroyer._get_cached_client(users[0]), - destroyer._get_cached_client(users[0])) - # ensure that cache is used - self.assertItemsEqual(mock_clients.call_args_list, - [mock.call("a", api_info=None)]) - - mock_clients.reset_mock() - self.assertEqual(destroyer._get_cached_client(users[1]), - destroyer._get_cached_client(users[1])) - self.assertItemsEqual(mock_clients.call_args_list, - [mock.call("b", api_info=None)]) - - mock_clients.reset_mock() - self.assertNotEqual(destroyer._get_cached_client(users[0]), - destroyer._get_cached_client(users[1])) - self.assertFalse(mock_clients.called) - - @mock.patch("%s.osclients.Clients" % BASE, - side_effect=[mock.MagicMock(), mock.MagicMock()]) - def test__get_cached_client_shared_cache(self, mock_clients): - # ensure that cache is shared between SeekAndDestroy objects - destroyer1 = manager.SeekAndDestroy(None, None, None) - destroyer2 = manager.SeekAndDestroy(None, None, None) - - user = {"credential": "a"} - - self.assertEqual(destroyer1._get_cached_client(user), - destroyer2._get_cached_client(user)) - self.assertItemsEqual(mock_clients.call_args_list, - [mock.call("a", api_info=None)]) - - @mock.patch("%s.osclients.Clients" % BASE, - side_effect=[mock.MagicMock(), mock.MagicMock()]) - def test__get_cached_client_shared_cache_api_versions(self, mock_clients): - # ensure that cache is shared between SeekAndDestroy objects - # with matching api_versions dicts + def test__get_cached_client(self): api_versions = {"cinder": {"version": "1", "service_type": "volume"}} - destroyer1 = manager.SeekAndDestroy(None, None, None, - api_versions=api_versions) - destroyer2 = manager.SeekAndDestroy(None, None, None, - api_versions=api_versions) + destroyer = manager.SeekAndDestroy(None, None, None, + api_versions=api_versions) + cred = mock.Mock() + user = {"credential": cred} - user = {"credential": "a"} + clients = destroyer._get_cached_client(user) + self.assertIs(cred.clients.return_value, clients) + cred.clients.assert_called_once_with(api_info=api_versions) - self.assertEqual(destroyer1._get_cached_client(user), - destroyer2._get_cached_client(user)) - self.assertItemsEqual(mock_clients.call_args_list, - [mock.call("a", api_info=api_versions)]) - - @mock.patch("%s.osclients.Clients" % BASE, - side_effect=[mock.MagicMock(), mock.MagicMock()]) - def test__get_cached_client_no_cache_api_versions(self, mock_clients): - # ensure that cache is not shared between SeekAndDestroy - # objects with different api_versions dicts - api_versions = [ - {"cinder": {"version": "1", "service_type": "volume"}}, - {"cinder": {"version": "2", "service_type": "volumev2"}} - ] - - destroyer1 = manager.SeekAndDestroy(None, None, None, - api_versions=api_versions[0]) - destroyer2 = manager.SeekAndDestroy(None, None, None, - api_versions=api_versions[1]) - user = {"credential": "a"} - - self.assertNotEqual(destroyer1._get_cached_client(user), - destroyer2._get_cached_client(user)) - self.assertItemsEqual(mock_clients.call_args_list, - [mock.call("a", api_info=api_versions[0]), - mock.call("a", api_info=api_versions[1])]) + self.assertIsNone(destroyer._get_cached_client(None)) @mock.patch("%s.LOG" % BASE) def test__delete_single_resource(self, mock_log): diff --git a/tests/unit/plugins/openstack/context/keystone/test_users.py b/tests/unit/plugins/openstack/context/keystone/test_users.py index 59d687d7..3e68cf7d 100644 --- a/tests/unit/plugins/openstack/context/keystone/test_users.py +++ b/tests/unit/plugins/openstack/context/keystone/test_users.py @@ -15,10 +15,10 @@ import mock -from rally.common import objects from rally import consts from rally import exceptions from rally.plugins.openstack.context.keystone import users +from rally.plugins.openstack import credential as oscredential from tests.unit import test CTX = "rally.plugins.openstack.context.keystone.users" @@ -274,16 +274,17 @@ class UserGeneratorTestCase(test.ScenarioTestCase): def test_users_and_tenants_in_context(self, mock_identity): identity_service = mock_identity.Identity.return_value - credential = objects.Credential("foo_url", "foo", "foo_pass", - https_insecure=True, - https_cacert="cacert") + credential = oscredential.OpenStackCredential( + "foo_url", "foo", "foo_pass", + https_insecure=True, + https_cacert="cacert") tmp_context = dict(self.context) tmp_context["config"]["users"] = {"tenants": 1, "users_per_tenant": 2, "resource_management_workers": 1} tmp_context["admin"]["credential"] = credential - credential_dict = credential.to_dict(False) + credential_dict = credential.to_dict() user_list = [mock.MagicMock(id="id_%d" % i) for i in range(self.users_num)] identity_service.create_user.side_effect = user_list @@ -302,7 +303,7 @@ class UserGeneratorTestCase(test.ScenarioTestCase): self.assertEqual(set(["id", "credential", "tenant_id"]), set(user.keys())) - user_credential_dict = user["credential"].to_dict(False) + user_credential_dict = user["credential"].to_dict() excluded_keys = ["auth_url", "username", "password", "tenant_name", "region_name", @@ -323,7 +324,7 @@ class UserGeneratorTestCase(test.ScenarioTestCase): @mock.patch("%s.identity" % CTX) def test_users_contains_correct_endpoint_type(self, mock_identity): - credential = objects.Credential( + credential = oscredential.OpenStackCredential( "foo_url", "foo", "foo_pass", endpoint_type=consts.EndpointType.INTERNAL) config = { @@ -346,7 +347,8 @@ class UserGeneratorTestCase(test.ScenarioTestCase): @mock.patch("%s.identity" % CTX) def test_users_contains_default_endpoint_type(self, mock_identity): - credential = objects.Credential("foo_url", "foo", "foo_pass") + credential = oscredential.OpenStackCredential( + "foo_url", "foo", "foo_pass") config = { "config": { "users": { diff --git a/tests/unit/plugins/openstack/context/sahara/test_sahara_output_data_sources.py b/tests/unit/plugins/openstack/context/sahara/test_sahara_output_data_sources.py index 00e3f743..0659e154 100644 --- a/tests/unit/plugins/openstack/context/sahara/test_sahara_output_data_sources.py +++ b/tests/unit/plugins/openstack/context/sahara/test_sahara_output_data_sources.py @@ -14,8 +14,8 @@ import mock -from rally.common import objects from rally.plugins.openstack.context.sahara import sahara_output_data_sources +from rally.plugins.openstack import credential as oscredential from rally.plugins.openstack.scenarios.sahara import utils as sahara_utils from tests.unit import test @@ -26,8 +26,8 @@ class SaharaOutputDataSourcesTestCase(test.ScenarioTestCase): def setUp(self): super(SaharaOutputDataSourcesTestCase, self).setUp() - fake_dict = objects.Credential("http://fake.example.org:5000/v2.0/", - "user", "passwd") + fake_dict = oscredential.OpenStackCredential( + "http://fake.example.org:5000/v2.0/", "user", "passwd") self.tenants_num = 2 self.users_per_tenant = 2 self.users = self.tenants_num * self.users_per_tenant diff --git a/tests/unit/plugins/openstack/test_credential.py b/tests/unit/plugins/openstack/test_credential.py new file mode 100644 index 00000000..28e0d7f4 --- /dev/null +++ b/tests/unit/plugins/openstack/test_credential.py @@ -0,0 +1,149 @@ +# Copyright 2017: 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 jsonschema +import mock + +from rally import consts +from rally.deployment import credential +from tests.unit import test + + +class OpenStackCredentialTestCase(test.TestCase): + + def setUp(self): + super(OpenStackCredentialTestCase, self).setUp() + cred_cls = credential.get("openstack") + self.credential = cred_cls( + "foo_url", "foo_user", "foo_password", + tenant_name="foo_tenant", + permission=consts.EndpointPermission.ADMIN) + + def test_to_dict(self): + self.assertEqual({"auth_url": "foo_url", + "username": "foo_user", + "password": "foo_password", + "tenant_name": "foo_tenant", + "region_name": None, + "domain_name": None, + "endpoint": None, + "permission": consts.EndpointPermission.ADMIN, + "endpoint_type": None, + "https_insecure": False, + "https_cacert": None, + "project_domain_name": None, + "user_domain_name": None}, self.credential.to_dict()) + + @mock.patch("rally.osclients.Clients") + def test_verify_connection_admin(self, mock_clients): + self.credential.verify_connection() + mock_clients.assert_called_once_with( + self.credential, api_info=None, cache={}) + mock_clients.return_value.verified_keystone.assert_called_once_with() + + @mock.patch("rally.osclients.Clients") + def test_verify_connection_user(self, mock_clients): + self.credential.permission = consts.EndpointPermission.USER + self.credential.verify_connection() + mock_clients.assert_called_once_with( + self.credential, api_info=None, cache={}) + mock_clients.return_value.keystone.assert_called_once_with() + + @mock.patch("rally.osclients.Clients") + def test_list_services(self, mock_clients): + result = self.credential.list_services() + mock_clients.assert_called_once_with( + self.credential, api_info=None, cache={}) + mock_clients.return_value.services.assert_called_once_with() + self.assertIs(mock_clients.return_value.services.return_value, result) + + @mock.patch("rally.osclients.Clients") + def test_clients(self, mock_clients): + clients = self.credential.clients(api_info="fake_info") + mock_clients.assert_called_once_with( + self.credential, api_info="fake_info", cache={}) + self.assertIs(mock_clients.return_value, clients) + + +class OpenStackCredentialBuilderTestCase(test.TestCase): + + def setUp(self): + super(OpenStackCredentialBuilderTestCase, self).setUp() + self.config = { + "auth_url": "http://example.net:5000/v2.0/", + "region_name": "RegionOne", + "endpoint_type": consts.EndpointType.INTERNAL, + "https_insecure": False, + "https_cacert": "cacert", + "admin": { + "username": "admin", + "password": "myadminpass", + "tenant_name": "demo" + }, + "users": [ + { + "username": "user1", + "password": "userpass", + "tenant_name": "demo" + } + ] + } + self.cred_builder_cls = credential.get_builder("openstack") + + def test_validate(self): + self.cred_builder_cls.validate(self.config) + + def test_validate_error(self): + self.assertRaises(jsonschema.ValidationError, + self.cred_builder_cls.validate, + {"foo": "bar"}) + + def test_build_credentials(self): + creds_builder = self.cred_builder_cls(self.config) + creds = creds_builder.build_credentials() + self.assertEqual({ + "admin": { + "auth_url": "http://example.net:5000/v2.0/", + "username": "admin", + "password": "myadminpass", + "permission": consts.EndpointPermission.ADMIN, + "domain_name": None, + "endpoint": None, + "endpoint_type": consts.EndpointType.INTERNAL, + "https_cacert": "cacert", + "https_insecure": False, + "project_domain_name": None, + "region_name": "RegionOne", + "tenant_name": "demo", + "user_domain_name": None, + }, + "users": [ + { + "auth_url": "http://example.net:5000/v2.0/", + "username": "user1", + "password": "userpass", + "permission": consts.EndpointPermission.USER, + "domain_name": None, + "endpoint": None, + "endpoint_type": consts.EndpointType.INTERNAL, + "https_cacert": "cacert", + "https_insecure": False, + "project_domain_name": None, + "region_name": "RegionOne", + "tenant_name": "demo", + "user_domain_name": None, + } + ] + }, creds) diff --git a/tests/unit/plugins/openstack/test_scenario.py b/tests/unit/plugins/openstack/test_scenario.py index aae42e82..d1fd0aa4 100644 --- a/tests/unit/plugins/openstack/test_scenario.py +++ b/tests/unit/plugins/openstack/test_scenario.py @@ -18,6 +18,7 @@ import mock from oslotest import mockpatch from rally.plugins.openstack import scenario as base_scenario +from tests.unit import fakes from tests.unit import test @@ -130,27 +131,21 @@ class OpenStackScenarioTestCase(test.TestCase): @mock.patch("rally.task.scenario.Scenario.validate") def test_validate(self, mock_scenario_validate): - cred1 = mock.Mock() - cred2 = mock.Mock() - cred3 = mock.Mock() - self.osclients.mock.side_effect = [cred1, cred2, cred3] + cred1 = fakes.fake_credential(foo="bar1") + cred2 = fakes.fake_credential(foo="bar2") + cred3 = fakes.fake_credential(foo="bar3") base_scenario.OpenStackScenario.validate( name="foo_name", config="foo_config", - admin="foo_admin", - users=[{"credential": "foo_user1"}, - {"credential": "foo_user2"}], + admin=cred1, + users=[{"credential": cred2}, + {"credential": cred3}], deployment=None) mock_scenario_validate.assert_called_once_with( name="foo_name", config="foo_config", - admin=cred1, - users=[cred2, cred3], + admin=cred1.clients.return_value, + users=[cred2.clients.return_value, cred3.clients.return_value], deployment=None) - self.osclients.mock.assert_has_calls([ - mock.call("foo_admin"), - mock.call("foo_user1"), - mock.call("foo_user2"), - ]) diff --git a/tests/unit/plugins/openstack/verification/tempest/test_config.py b/tests/unit/plugins/openstack/verification/tempest/test_config.py index cdf64137..081b7392 100644 --- a/tests/unit/plugins/openstack/verification/tempest/test_config.py +++ b/tests/unit/plugins/openstack/verification/tempest/test_config.py @@ -25,20 +25,17 @@ from tests.unit import test CONF = cfg.CONF -CREDS = { - "admin": { - "username": "admin", - "tenant_name": "admin", - "password": "admin-12345", - "auth_url": "http://test:5000/v2.0/", - "permission": "admin", - "region_name": "test", - "https_insecure": False, - "https_cacert": "/path/to/cacert/file", - "user_domain_name": "admin", - "project_domain_name": "admin" - }, - "uuid": "fake_deployment" +CRED = { + "username": "admin", + "tenant_name": "admin", + "password": "admin-12345", + "auth_url": "http://test:5000/v2.0/", + "permission": "admin", + "region_name": "test", + "https_insecure": False, + "https_cacert": "/path/to/cacert/file", + "user_domain_name": "admin", + "project_domain_name": "admin" } PATH = "rally.plugins.openstack.verification.tempest.config" @@ -49,10 +46,8 @@ class TempestConfigfileManagerTestCase(test.TestCase): def setUp(self): super(TempestConfigfileManagerTestCase, self).setUp() - - mock.patch("rally.osclients.Clients").start() - - deployment = fakes.FakeDeployment(**CREDS) + deployment = fakes.FakeDeployment(uuid="fake_deployment", + admin=fakes.fake_credential(**CRED)) self.tempest = config.TempestConfigfileManager(deployment) def test__configure_auth(self): @@ -60,10 +55,10 @@ class TempestConfigfileManagerTestCase(test.TestCase): self.tempest._configure_auth() expected = ( - ("admin_username", CREDS["admin"]["username"]), - ("admin_password", CREDS["admin"]["password"]), - ("admin_project_name", CREDS["admin"]["tenant_name"]), - ("admin_domain_name", CREDS["admin"]["user_domain_name"])) + ("admin_username", CRED["username"]), + ("admin_password", CRED["password"]), + ("admin_project_name", CRED["tenant_name"]), + ("admin_domain_name", CRED["user_domain_name"])) result = self.tempest.conf.items("auth") for item in expected: self.assertIn(item, result) @@ -85,13 +80,13 @@ class TempestConfigfileManagerTestCase(test.TestCase): self.tempest._configure_identity() expected = ( - ("region", CREDS["admin"]["region_name"]), + ("region", CRED["region_name"]), ("auth_version", "v2"), - ("uri", CREDS["admin"]["auth_url"][:-1]), - ("uri_v3", CREDS["admin"]["auth_url"].replace("/v2.0/", "/v3")), + ("uri", CRED["auth_url"][:-1]), + ("uri_v3", CRED["auth_url"].replace("/v2.0/", "/v3")), ("disable_ssl_certificate_validation", - str(CREDS["admin"]["https_insecure"])), - ("ca_certificates_file", CREDS["admin"]["https_cacert"])) + str(CRED["https_insecure"])), + ("ca_certificates_file", CRED["https_cacert"])) result = self.tempest.conf.items("identity") for item in expected: self.assertIn(item, result) diff --git a/tests/unit/plugins/openstack/verification/tempest/test_context.py b/tests/unit/plugins/openstack/verification/tempest/test_context.py index 1aad9ac2..7e002110 100644 --- a/tests/unit/plugins/openstack/verification/tempest/test_context.py +++ b/tests/unit/plugins/openstack/verification/tempest/test_context.py @@ -30,20 +30,17 @@ from tests.unit import test CONF = cfg.CONF -CREDS = { - "admin": { - "username": "admin", - "tenant_name": "admin", - "password": "admin-12345", - "auth_url": "http://test:5000/v2.0/", - "permission": "admin", - "region_name": "test", - "https_insecure": False, - "https_cacert": "/path/to/cacert/file", - "user_domain_name": "admin", - "project_domain_name": "admin" - }, - "uuid": "fake_deployment" +CRED = { + "username": "admin", + "tenant_name": "admin", + "password": "admin-12345", + "auth_url": "http://test:5000/v2.0/", + "permission": "admin", + "region_name": "test", + "https_insecure": False, + "https_cacert": "/path/to/cacert/file", + "user_domain_name": "admin", + "project_domain_name": "admin" } PATH = "rally.plugins.openstack.verification.tempest.context" @@ -55,11 +52,12 @@ class TempestContextTestCase(test.TestCase): def setUp(self): super(TempestContextTestCase, self).setUp() - mock.patch("rally.osclients.Clients").start() self.mock_isfile = mock.patch("os.path.isfile", return_value=True).start() - self.deployment = fakes.FakeDeployment(**CREDS) + self.cred = fakes.fake_credential(**CRED) + self.deployment = fakes.FakeDeployment( + uuid="fake_deployment", admin=self.cred) cfg = {"verifier": mock.Mock(deployment=self.deployment), "verification": {"uuid": "uuid"}} cfg["verifier"].manager.home_dir = "/p/a/t/h" @@ -244,6 +242,7 @@ class TempestContextTestCase(test.TestCase): def test__discover_or_create_flavor(self): client = self.context.clients.nova() + client.flavors.list.return_value = [] client.flavors.create.side_effect = [fakes.FakeFlavor(id="id1")] flavor = self.context._discover_or_create_flavor(64) @@ -260,6 +259,7 @@ class TempestContextTestCase(test.TestCase): client.create_network.side_effect = [{"network": fake_network}] client.create_router.side_effect = [{"router": {"id": "rid1"}}] client.create_subnet.side_effect = [{"subnet": {"id": "subid1"}}] + client.list_networks.return_value = {"networks": []} network = self.context._create_network_resources() self.assertEqual("nid1", network["id"]) @@ -333,16 +333,14 @@ class TempestContextTestCase(test.TestCase): @mock.patch("%s.TempestContext._configure_option" % PATH) @mock.patch("%s.TempestContext._create_tempest_roles" % PATH) @mock.patch("rally.verification.utils.create_dir") - @mock.patch("%s.osclients.Clients" % PATH) - def test_setup(self, mock_clients, mock_create_dir, + def test_setup(self, mock_create_dir, mock__create_tempest_roles, mock__configure_option, mock_open): - self.deployment = fakes.FakeDeployment(**CREDS) verifier = mock.Mock(deployment=self.deployment) verifier.manager.home_dir = "/p/a/t/h" # case #1: no neutron and heat - mock_clients.return_value.services.return_value = {} + self.cred.clients.return_value.services.return_value = {} ctx = context.TempestContext({"verifier": verifier}) ctx.conf = mock.Mock() @@ -377,10 +375,12 @@ class TempestContextTestCase(test.TestCase): mock__configure_option.reset_mock() # case #2: neutron and heat are presented - mock_clients.return_value.services.return_value = { + self.cred.clients.return_value.services.return_value = { "network": "neutron", "orchestration": "heat"} ctx = context.TempestContext({"verifier": verifier}) + neutron = ctx.clients.neutron() + neutron.list_networks.return_value = {"networks": ["fake_net"]} ctx.conf = mock.Mock() ctx.setup()