[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
This commit is contained in:
chenhb 2018-08-06 13:46:47 +08:00
parent 467cc05559
commit 0a73f8ae80
11 changed files with 224 additions and 13 deletions

View File

@ -27,6 +27,7 @@ Added
``existing@openstack`` platform to represent client certificate bundle and ``existing@openstack`` platform to represent client certificate bundle and
key files. Also the support for appropriate system environment variables ( key files. Also the support for appropriate system environment variables (
``OS_CERT``, ``OS_KEY``) is added. ``OS_CERT``, ``OS_KEY``) is added.
* Support client api option while deploying platform.
Changed Changed
~~~~~~~ ~~~~~~~

View File

@ -14,6 +14,7 @@
# under the License. # under the License.
import collections import collections
import copy
import uuid import uuid
from rally.common import broker from rally.common import broker
@ -106,8 +107,14 @@ class UserGenerator(context.Context):
creds = self.env["platforms"]["openstack"] creds = self.env["platforms"]["openstack"]
if creds.get("admin"): 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"] = { context["admin"] = {
"credential": credential.OpenStackCredential(**creds["admin"])} "credential": credential.OpenStackCredential(**admin_cred)
}
if creds["users"] and not (set(self.config) - {"user_choice_method"}): if creds["users"] and not (set(self.config) - {"user_choice_method"}):
self.existing_users = creds["users"] self.existing_users = creds["users"]
@ -218,7 +225,8 @@ class UserGenerator(context.Context):
https_cacert=self.credential["https_cacert"], https_cacert=self.credential["https_cacert"],
region_name=self.credential["region_name"], region_name=self.credential["region_name"],
profiler_hmac_key=self.credential["profiler_hmac_key"], 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, users.append({"id": user.id,
"credential": user_credential, "credential": user_credential,
"tenant_id": tenant_id}) "tenant_id": tenant_id})
@ -284,7 +292,13 @@ class UserGenerator(context.Context):
def use_existing_users(self): def use_existing_users(self):
LOG.debug("Using existing users for OpenStack platform.") 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: 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_credential = credential.OpenStackCredential(**user_credential)
user_clients = osclients.Clients(user_credential) user_clients = osclients.Clients(user_credential)
user_id = user_clients.keystone.auth_ref.user_id user_id = user_clients.keystone.auth_ref.user_id

View File

@ -28,7 +28,8 @@ class OpenStackCredential(dict):
domain_name=None, endpoint=None, user_domain_name=None, domain_name=None, endpoint=None, user_domain_name=None,
project_domain_name=None, project_domain_name=None,
https_insecure=False, https_cacert=None, https_cert=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: if kwargs:
raise TypeError("%s" % kwargs) raise TypeError("%s" % kwargs)
@ -50,7 +51,8 @@ class OpenStackCredential(dict):
("https_cacert", https_cacert), ("https_cacert", https_cacert),
("https_cert", https_cert), ("https_cert", https_cert),
("profiler_hmac_key", profiler_hmac_key), ("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 = {} self._clients_cache = {}

View File

@ -111,7 +111,8 @@ class OSClient(plugin.Plugin):
self.credential = credential self.credential = credential
if not isinstance(self.credential, oscred.OpenStackCredential): if not isinstance(self.credential, oscred.OpenStackCredential):
self.credential = oscred.OpenStackCredential(**self.credential) 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 self.cache = cache_obj
def choose_version(self, version=None): 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 # For those clients which doesn't accept string value(for example
# zaqarclient), this method should be overridden. # zaqarclient), this method should be overridden.
version = (version or version = (version or
self.api_info.get(self.get_name(), {}).get("version") or self.credential.api_info.get(self.get_name(), {}).get(
self._meta_get("default_version")) "version") or self._meta_get("default_version"))
if version is not None: if version is not None:
version = str(version) version = str(version)
return version return version
@ -172,8 +173,8 @@ class OSClient(plugin.Plugin):
service type from api_info(configured from a context) and default. service type from api_info(configured from a context) and default.
""" """
return (service_type or return (service_type or
self.api_info.get(self.get_name(), {}).get("service_type") or self.credential.api_info.get(self.get_name(), {}).get(
self._meta_get("default_service_type")) "service_type") or self._meta_get("default_service_type"))
@classmethod @classmethod
def is_service_type_configurable(cls): def is_service_type_configurable(cls):
@ -184,7 +185,7 @@ class OSClient(plugin.Plugin):
@property @property
def keystone(self): def keystone(self):
return OSClient.get("keystone")(self.credential, self.api_info, return OSClient.get("keystone")(self.credential, None,
self.cache) self.cache)
def _get_endpoint(self, service_type=None): def _get_endpoint(self, service_type=None):

View File

@ -33,6 +33,13 @@ class OpenStack(platform.Platform):
It may be used to test any existing OpenStack API compatible cloud. 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 = { CONFIG_SCHEMA = {
"type": "object", "type": "object",
"definitions": { "definitions": {
@ -63,6 +70,21 @@ class OpenStack(platform.Platform):
"additionalProperties": False "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": { "properties": {
@ -81,7 +103,8 @@ class OpenStack(platform.Platform):
"type": "array", "type": "array",
"items": {"$ref": "#/definitions/user"}, "items": {"$ref": "#/definitions/user"},
"minItems": 1 "minItems": 1
} },
"api_info": {"$ref": "#/definitions/api_info"}
}, },
"anyOf": [ "anyOf": [
{ {
@ -115,6 +138,7 @@ class OpenStack(platform.Platform):
del new_data["endpoint"] del new_data["endpoint"]
admin = new_data.pop("admin", None) admin = new_data.pop("admin", None)
users = new_data.pop("users", []) 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"): if new_data.get("https_cert") and new_data.get("https_key"):
new_data["https_cert"] = (new_data["https_cert"], new_data["https_cert"] = (new_data["https_cert"],
@ -132,7 +156,10 @@ class OpenStack(platform.Platform):
user.update(new_data) user.update(new_data)
for k, v in defaults.items(): for k, v in defaults.items():
user.setdefault(k, v) 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): def destroy(self):
# NOTE(boris-42): No action need to be performed. # NOTE(boris-42): No action need to be performed.

View File

@ -28,3 +28,9 @@ existing-with-predefined-users.json
If you are using read-only backend in Keystone like LDAP, AD then 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 you need this sample. If you don't specify "users" rally will use already
existing users that you provide. 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.

View File

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

View File

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

View File

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

View File

@ -75,6 +75,25 @@ class ExistingPlatformTestCase(PlatformBaseTestCase):
spec, spec["existing@openstack"]) spec, spec["existing@openstack"])
self.assertNotEqual([], result) 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): def test_create_users_only(self):
spec = { spec = {

View File

@ -43,7 +43,8 @@ class OpenStackCredentialTestCase(test.TestCase):
"project_domain_name": None, "project_domain_name": None,
"user_domain_name": None, "user_domain_name": None,
"profiler_hmac_key": None, "profiler_hmac_key": None,
"profiler_conn_str": None}, "profiler_conn_str": None,
"api_info": {}},
self.credential.to_dict()) self.credential.to_dict())
@mock.patch("rally_openstack.osclients.Clients") @mock.patch("rally_openstack.osclients.Clients")