From 0a73f8ae805c8bc671d88a6c885690e980e31752 Mon Sep 17 00:00:00 2001 From: chenhb Date: Mon, 6 Aug 2018 13:46:47 +0800 Subject: [PATCH] [api versions]Support to specify api versions in the spec of env Spec example: Specify cinder client version { "openstack": { "auth_url": "http://example.net:5000/v2.0/", "region_name": "RegionOne", "endpoint_type": "public", "admin": { "username": "admin", "password": "myadminpass", "tenant_name": "demo" }, "https_insecure": false, "https_cacert": "" "api_info": { "cinder": { "version": "1", "service_type": "volume" } } } } Change-Id: I74578e3b1c4e7b662ba66955ef9fb62cd4eeb788 --- CHANGELOG.rst | 1 + rally_openstack/contexts/keystone/users.py | 18 ++++- rally_openstack/credential.py | 6 +- rally_openstack/osclients.py | 13 +-- rally_openstack/platforms/existing.py | 31 ++++++- samples/deployments/README.rst | 6 ++ samples/deployments/existing-api.json | 20 +++++ .../functional/extra/fake_dir/fake_plugin.py | 39 +++++++++ tests/functional/test_cli_task.py | 81 +++++++++++++++++++ tests/unit/platforms/test_existing.py | 19 +++++ tests/unit/test_credential.py | 3 +- 11 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 samples/deployments/existing-api.json create mode 100644 tests/functional/extra/fake_dir/fake_plugin.py create mode 100644 tests/functional/test_cli_task.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b1233a72..b62c5793 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,7 @@ Added ``existing@openstack`` platform to represent client certificate bundle and key files. Also the support for appropriate system environment variables ( ``OS_CERT``, ``OS_KEY``) is added. +* Support client api option while deploying platform. Changed ~~~~~~~ diff --git a/rally_openstack/contexts/keystone/users.py b/rally_openstack/contexts/keystone/users.py index 6ff7bd41..27f1e168 100644 --- a/rally_openstack/contexts/keystone/users.py +++ b/rally_openstack/contexts/keystone/users.py @@ -14,6 +14,7 @@ # under the License. import collections +import copy import uuid from rally.common import broker @@ -106,8 +107,14 @@ class UserGenerator(context.Context): creds = self.env["platforms"]["openstack"] if creds.get("admin"): + admin_cred = copy.deepcopy(creds["admin"]) + api_info = copy.deepcopy(creds.get("api_info", {})) + if "api_info" in admin_cred: + api_info.update(creds["admin"]["api_info"]) + admin_cred["api_info"] = api_info context["admin"] = { - "credential": credential.OpenStackCredential(**creds["admin"])} + "credential": credential.OpenStackCredential(**admin_cred) + } if creds["users"] and not (set(self.config) - {"user_choice_method"}): self.existing_users = creds["users"] @@ -218,7 +225,8 @@ class UserGenerator(context.Context): https_cacert=self.credential["https_cacert"], region_name=self.credential["region_name"], profiler_hmac_key=self.credential["profiler_hmac_key"], - profiler_conn_str=self.credential["profiler_conn_str"]) + profiler_conn_str=self.credential["profiler_conn_str"], + api_info=self.credential["api_info"]) users.append({"id": user.id, "credential": user_credential, "tenant_id": tenant_id}) @@ -284,7 +292,13 @@ class UserGenerator(context.Context): def use_existing_users(self): LOG.debug("Using existing users for OpenStack platform.") + api_info = copy.deepcopy(self.env["platforms"]["openstack"].get( + "api_info", {})) for user_credential in self.existing_users: + user_credential = copy.deepcopy(user_credential) + if "api_info" in user_credential: + api_info.update(user_credential["api_info"]) + user_credential["api_info"] = api_info user_credential = credential.OpenStackCredential(**user_credential) user_clients = osclients.Clients(user_credential) user_id = user_clients.keystone.auth_ref.user_id diff --git a/rally_openstack/credential.py b/rally_openstack/credential.py index 5ece771c..63574177 100644 --- a/rally_openstack/credential.py +++ b/rally_openstack/credential.py @@ -28,7 +28,8 @@ class OpenStackCredential(dict): domain_name=None, endpoint=None, user_domain_name=None, project_domain_name=None, https_insecure=False, https_cacert=None, https_cert=None, - profiler_hmac_key=None, profiler_conn_str=None, **kwargs): + profiler_hmac_key=None, profiler_conn_str=None, + api_info=None, **kwargs): if kwargs: raise TypeError("%s" % kwargs) @@ -50,7 +51,8 @@ class OpenStackCredential(dict): ("https_cacert", https_cacert), ("https_cert", https_cert), ("profiler_hmac_key", profiler_hmac_key), - ("profiler_conn_str", profiler_conn_str) + ("profiler_conn_str", profiler_conn_str), + ("api_info", api_info or {}) ]) self._clients_cache = {} diff --git a/rally_openstack/osclients.py b/rally_openstack/osclients.py index 6c777b05..f360df8a 100644 --- a/rally_openstack/osclients.py +++ b/rally_openstack/osclients.py @@ -111,7 +111,8 @@ class OSClient(plugin.Plugin): self.credential = credential if not isinstance(self.credential, oscred.OpenStackCredential): self.credential = oscred.OpenStackCredential(**self.credential) - self.api_info = api_info + if api_info: + self.credential.api_info.update(api_info) self.cache = cache_obj def choose_version(self, version=None): @@ -138,8 +139,8 @@ class OSClient(plugin.Plugin): # 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")) + self.credential.api_info.get(self.get_name(), {}).get( + "version") or self._meta_get("default_version")) if version is not None: version = str(version) return version @@ -172,8 +173,8 @@ class OSClient(plugin.Plugin): 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")) + self.credential.api_info.get(self.get_name(), {}).get( + "service_type") or self._meta_get("default_service_type")) @classmethod def is_service_type_configurable(cls): @@ -184,7 +185,7 @@ class OSClient(plugin.Plugin): @property def keystone(self): - return OSClient.get("keystone")(self.credential, self.api_info, + return OSClient.get("keystone")(self.credential, None, self.cache) def _get_endpoint(self, service_type=None): diff --git a/rally_openstack/platforms/existing.py b/rally_openstack/platforms/existing.py index e272cb41..220bdfa4 100644 --- a/rally_openstack/platforms/existing.py +++ b/rally_openstack/platforms/existing.py @@ -33,6 +33,13 @@ class OpenStack(platform.Platform): It may be used to test any existing OpenStack API compatible cloud. """ + VERSION_SCHEMA = { + "anyOf": [ + {"type": "string", "description": "a string-like version."}, + {"type": "number", "description": "a number-like version."} + ] + } + CONFIG_SCHEMA = { "type": "object", "definitions": { @@ -63,6 +70,21 @@ class OpenStack(platform.Platform): "additionalProperties": False } ], + }, + "api_info": { + "type": "object", + "patternProperties": { + "^[a-z]+$": { + "type": "object", + "properties": { + "version": VERSION_SCHEMA, + "service_type": {"type": "string"} + }, + "minProperties": 1, + "additionalProperties": False + } + }, + "additionalProperties": False } }, "properties": { @@ -81,7 +103,8 @@ class OpenStack(platform.Platform): "type": "array", "items": {"$ref": "#/definitions/user"}, "minItems": 1 - } + }, + "api_info": {"$ref": "#/definitions/api_info"} }, "anyOf": [ { @@ -115,6 +138,7 @@ class OpenStack(platform.Platform): del new_data["endpoint"] admin = new_data.pop("admin", None) users = new_data.pop("users", []) + api_info = new_data.pop("api_info", None) if new_data.get("https_cert") and new_data.get("https_key"): new_data["https_cert"] = (new_data["https_cert"], @@ -132,7 +156,10 @@ class OpenStack(platform.Platform): user.update(new_data) for k, v in defaults.items(): user.setdefault(k, v) - return {"admin": admin, "users": users}, {} + platform_data = {"admin": admin, "users": users} + if api_info: + platform_data["api_info"] = api_info + return platform_data, {} def destroy(self): # NOTE(boris-42): No action need to be performed. diff --git a/samples/deployments/README.rst b/samples/deployments/README.rst index a7fa287a..c049a397 100644 --- a/samples/deployments/README.rst +++ b/samples/deployments/README.rst @@ -28,3 +28,9 @@ existing-with-predefined-users.json If you are using read-only backend in Keystone like LDAP, AD then you need this sample. If you don't specify "users" rally will use already existing users that you provide. + +existing-api.json +-------------------------------- + +If you expect to specify version of some clients, you could register existing +Openstack cluster like this sample. diff --git a/samples/deployments/existing-api.json b/samples/deployments/existing-api.json new file mode 100644 index 00000000..174983bd --- /dev/null +++ b/samples/deployments/existing-api.json @@ -0,0 +1,20 @@ +{ + "openstack": { + "auth_url": "http://example.net:5000/v2.0/", + "region_name": "RegionOne", + "endpoint_type": "public", + "admin": { + "username": "admin", + "password": "myadminpass", + "tenant_name": "demo" + }, + "https_insecure": false, + "https_cacert": "" + "api_info": { + "specified_client_name": { + "version": "version_number", + "service_type": "service_type" + } + } + } +} diff --git a/tests/functional/extra/fake_dir/fake_plugin.py b/tests/functional/extra/fake_dir/fake_plugin.py new file mode 100644 index 00000000..1995623d --- /dev/null +++ b/tests/functional/extra/fake_dir/fake_plugin.py @@ -0,0 +1,39 @@ +# 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_openstack import osclients +from rally_openstack import scenario + + +@osclients.configure("fakedummy", default_version="1", + default_service_type="dummy", + supported_versions=["1", "2"]) +class FakeDummy(osclients.OSClient): + def create_client(self, version=None, service_type=None): + version = self.choose_version(version) + service_type = self.choose_service_type(service_type) + return {"version": version, "service_type": service_type} + + +@scenario.configure(name="FakeDummy.openstack_api") +class FakeDummyOpenstackAPI(scenario.OpenStackScenario): + + def run(self): + admin_client = self.admin_clients("fakedummy") + self.assertEqual("dummyv2", admin_client["service_type"]) + self.assertEqual("2", admin_client["version"]) + + client = self.clients("fakedummy") + self.assertEqual("dummyv2", client["service_type"]) + self.assertEqual("2", client["version"]) diff --git a/tests/functional/test_cli_task.py b/tests/functional/test_cli_task.py new file mode 100644 index 00000000..e978c55a --- /dev/null +++ b/tests/functional/test_cli_task.py @@ -0,0 +1,81 @@ +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import unittest + +from tests.functional import utils + + +class TaskTestCase(unittest.TestCase): + + def test_specify_version_by_deployment(self): + rally = utils.Rally() + deployment = json.loads(rally("deployment config")) + deployment["openstack"]["api_info"] = { + "fakedummy": { + "version": "2", + "service_type": "dummyv2" + } + } + deployment = utils.JsonTempFile(deployment) + rally("deployment create --name t_create_with_api_info " + "--filename %s" % deployment.filename) + self.assertIn("t_create_with_api_info", rally("deployment list")) + + config = { + "FakeDummy.openstack_api": [ + { + "runner": { + "type": "constant", + "times": 1, + "concurrency": 1 + } + } + ] + } + config = utils.TaskConfig(config) + plugins = "tests/functional/extra/fake_dir/fake_plugin.py" + rally("--plugin-paths %s task start --task %s" % ( + plugins, config.filename)) + + def test_specify_version_by_deployment_with_existing_users(self): + rally = utils.Rally() + deployment = json.loads(rally("deployment config")) + deployment["openstack"]["users"] = [deployment["openstack"]["admin"]] + deployment["openstack"]["api_info"] = { + "fakedummy": { + "version": "2", + "service_type": "dummyv2" + } + } + deployment = utils.JsonTempFile(deployment) + rally("deployment create --name t_create_with_api_info " + "--filename %s" % deployment.filename) + self.assertIn("t_create_with_api_info", rally("deployment list")) + config = { + "FakeDummy.openstack_api": [ + { + "runner": { + "type": "constant", + "times": 1, + "concurrency": 1 + } + } + ] + } + config = utils.TaskConfig(config) + plugins = "tests/functional/extra/fake_dir/fake_plugin.py" + rally("--plugin-paths %s task start --task %s" % ( + plugins, config.filename)) diff --git a/tests/unit/platforms/test_existing.py b/tests/unit/platforms/test_existing.py index 04522832..20437d20 100644 --- a/tests/unit/platforms/test_existing.py +++ b/tests/unit/platforms/test_existing.py @@ -75,6 +75,25 @@ class ExistingPlatformTestCase(PlatformBaseTestCase): spec, spec["existing@openstack"]) self.assertNotEqual([], result) + def test_validate_spec_schema_with_api_info(self): + spec = { + "existing@openstack": { + "auth_url": "url", + "admin": { + "username": "admin", + "password": "password123", + "tenant_name": "admin" + }, + "api_info": { + "nova": {"version": 1}, + "cinder": {"version": 2, "service_type": "volumev2"} + } + } + } + result = platform.Platform.validate("existing@openstack", {}, + spec, spec["existing@openstack"]) + self.assertEqual([], result) + def test_create_users_only(self): spec = { diff --git a/tests/unit/test_credential.py b/tests/unit/test_credential.py index 6ff716a4..c5ba6712 100644 --- a/tests/unit/test_credential.py +++ b/tests/unit/test_credential.py @@ -43,7 +43,8 @@ class OpenStackCredentialTestCase(test.TestCase): "project_domain_name": None, "user_domain_name": None, "profiler_hmac_key": None, - "profiler_conn_str": None}, + "profiler_conn_str": None, + "api_info": {}}, self.credential.to_dict()) @mock.patch("rally_openstack.osclients.Clients")