Merge "[docs] Increase an understanding of api_versions context"
This commit is contained in:
commit
485310c16a
248
rally/plugins/openstack/context/api_versions.py
Normal file
248
rally/plugins/openstack/context/api_versions.py
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# 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 random
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from rally.common.i18n import _, _LE
|
||||||
|
from rally.common import logging
|
||||||
|
from rally import consts
|
||||||
|
from rally import exceptions
|
||||||
|
from rally import osclients
|
||||||
|
from rally.task import context
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@context.configure(name="api_versions", order=150)
|
||||||
|
class OpenStackAPIVersions(context.Context):
|
||||||
|
"""Context for specifying OpenStack clients versions and service types.
|
||||||
|
|
||||||
|
Some OpenStack services support several API versions. To recognize
|
||||||
|
the endpoints of each version, separate service types are provided in
|
||||||
|
Keystone service catalog.
|
||||||
|
|
||||||
|
Rally has the map of default service names - service types. But since
|
||||||
|
service type is an entity, which can be configured manually by admin(
|
||||||
|
via keystone api) without relation to service name, such map can be
|
||||||
|
insufficient.
|
||||||
|
|
||||||
|
Also, Keystone service catalog does not provide a map types to name
|
||||||
|
(this statement is true for keystone < 3.3 ).
|
||||||
|
|
||||||
|
This context was designed for not-default service types and not-default
|
||||||
|
API versions usage.
|
||||||
|
|
||||||
|
An example of specifying API version:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
# In this example we will launch NovaKeypair.create_and_list_keypairs
|
||||||
|
# scenario on 2.2 api version.
|
||||||
|
{
|
||||||
|
"NovaKeypair.create_and_list_keypairs": [
|
||||||
|
{
|
||||||
|
"args": {
|
||||||
|
"key_type": "x509"
|
||||||
|
},
|
||||||
|
"runner": {
|
||||||
|
"type": "constant",
|
||||||
|
"times": 10,
|
||||||
|
"concurrency": 2
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"users": {
|
||||||
|
"tenants": 3,
|
||||||
|
"users_per_tenant": 2
|
||||||
|
},
|
||||||
|
"api_versions": {
|
||||||
|
"nova": {
|
||||||
|
"version": 2.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
An example of specifying API version along with service type:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
# In this example we will launch CinderVolumes.create_and_attach_volume
|
||||||
|
# scenario on Cinder V2
|
||||||
|
{
|
||||||
|
"CinderVolumes.create_and_attach_volume": [
|
||||||
|
{
|
||||||
|
"args": {
|
||||||
|
"size": 10,
|
||||||
|
"image": {
|
||||||
|
"name": "^cirros.*uec$"
|
||||||
|
},
|
||||||
|
"flavor": {
|
||||||
|
"name": "m1.tiny"
|
||||||
|
},
|
||||||
|
"create_volume_params": {
|
||||||
|
"availability_zone": "nova"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runner": {
|
||||||
|
"type": "constant",
|
||||||
|
"times": 5,
|
||||||
|
"concurrency": 1
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"users": {
|
||||||
|
"tenants": 2,
|
||||||
|
"users_per_tenant": 2
|
||||||
|
},
|
||||||
|
"api_versions": {
|
||||||
|
"cinder": {
|
||||||
|
"version": 2,
|
||||||
|
"service_type": "volumev2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Also, it possible to use service name as an identifier of service endpoint,
|
||||||
|
but an admin user is required (Keystone can return map of service
|
||||||
|
names - types, but such API is permitted only for admin). An example:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
# Similar to the previous example, but `service_name` argument is used
|
||||||
|
# instead of `service_type`
|
||||||
|
{
|
||||||
|
"CinderVolumes.create_and_attach_volume": [
|
||||||
|
{
|
||||||
|
"args": {
|
||||||
|
"size": 10,
|
||||||
|
"image": {
|
||||||
|
"name": "^cirros.*uec$"
|
||||||
|
},
|
||||||
|
"flavor": {
|
||||||
|
"name": "m1.tiny"
|
||||||
|
},
|
||||||
|
"create_volume_params": {
|
||||||
|
"availability_zone": "nova"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runner": {
|
||||||
|
"type": "constant",
|
||||||
|
"times": 5,
|
||||||
|
"concurrency": 1
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"users": {
|
||||||
|
"tenants": 2,
|
||||||
|
"users_per_tenant": 2
|
||||||
|
},
|
||||||
|
"api_versions": {
|
||||||
|
"cinder": {
|
||||||
|
"version": 2,
|
||||||
|
"service_name": "cinderv2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = {
|
||||||
|
"type": "object",
|
||||||
|
"$schema": consts.JSON_SCHEMA,
|
||||||
|
"patternProperties": {
|
||||||
|
"^[a-z]+$": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "number"}]
|
||||||
|
},
|
||||||
|
"service_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"service_type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
# FIXME(andreykurilin): move all checks to validate method.
|
||||||
|
|
||||||
|
# use admin only when `service_name` is presented
|
||||||
|
admin_clients = osclients.Clients(
|
||||||
|
self.context.get("admin", {}).get("credential"))
|
||||||
|
clients = osclients.Clients(random.choice(
|
||||||
|
self.context["users"])["credential"])
|
||||||
|
services = clients.keystone().service_catalog.get_endpoints()
|
||||||
|
services_from_admin = None
|
||||||
|
for client_name, conf in six.iteritems(self.config):
|
||||||
|
if "service_type" in conf and conf["service_type"] not in services:
|
||||||
|
raise exceptions.ValidationError(_(
|
||||||
|
"There is no service with '%s' type in your environment.")
|
||||||
|
% conf["service_type"])
|
||||||
|
elif "service_name" in conf:
|
||||||
|
if not self.context.get("admin", {}).get("credential"):
|
||||||
|
raise exceptions.BenchmarkSetupFailure(_(
|
||||||
|
"Setting 'service_name' is allowed only for 'admin' "
|
||||||
|
"user."))
|
||||||
|
if not services_from_admin:
|
||||||
|
services_from_admin = dict(
|
||||||
|
[(s.name, s.type)
|
||||||
|
for s in admin_clients.keystone().services.list()])
|
||||||
|
if conf["service_name"] not in services_from_admin:
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
_("There is no '%s' service in your environment") %
|
||||||
|
conf["service_name"])
|
||||||
|
|
||||||
|
self.context["config"]["api_versions"][client_name][
|
||||||
|
"service_type"] = services_from_admin[conf["service_name"]]
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# nothing to do here
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, config, non_hidden=False):
|
||||||
|
super(OpenStackAPIVersions, cls).validate(config,
|
||||||
|
non_hidden=non_hidden)
|
||||||
|
for client in config:
|
||||||
|
client_cls = osclients.OSClient.get(client)
|
||||||
|
if ("service_type" in config[client] and
|
||||||
|
"service_name" in config[client]):
|
||||||
|
raise exceptions.ValidationError(_LE(
|
||||||
|
"Setting both 'service_type' and 'service_name' properties"
|
||||||
|
" is restricted."))
|
||||||
|
try:
|
||||||
|
if ("service_type" in config[client] or
|
||||||
|
"service_name" in config[client]):
|
||||||
|
client_cls.is_service_type_configurable()
|
||||||
|
|
||||||
|
if "version" in config[client]:
|
||||||
|
client_cls.validate_version(config[client]["version"])
|
||||||
|
|
||||||
|
except exceptions.RallyException as e:
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
_LE("Invalid settings for '%(client)s': %(error)s") % {
|
||||||
|
"client": client,
|
||||||
|
"error": e.format_message()})
|
127
tests/unit/plugins/openstack/context/test_api_versions.py
Normal file
127
tests/unit/plugins/openstack/context/test_api_versions.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# 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 copy
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from rally.common import utils
|
||||||
|
from rally import exceptions
|
||||||
|
from rally.plugins.openstack.context import api_versions
|
||||||
|
from tests.unit import test
|
||||||
|
|
||||||
|
|
||||||
|
class OpenStackServicesTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(OpenStackServicesTestCase, self).setUp()
|
||||||
|
self.mock_clients = mock.patch("rally.osclients.Clients").start()
|
||||||
|
self.mock_kc = self.mock_clients.return_value.keystone.return_value
|
||||||
|
self.mock_kc.service_catalog.get_endpoints.return_value = []
|
||||||
|
self.mock_kc.services.list.return_value = []
|
||||||
|
|
||||||
|
def test_validate_correct_config(self):
|
||||||
|
api_versions.OpenStackAPIVersions.validate({
|
||||||
|
"nova": {"service_type": "compute", "version": 2},
|
||||||
|
"cinder": {"service_name": "cinderv2", "version": 2},
|
||||||
|
"neutron": {"service_type": "network"},
|
||||||
|
"glance": {"service_name": "glance"},
|
||||||
|
"heat": {"version": 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_validate_wrong_configs(self):
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.PluginNotFound,
|
||||||
|
api_versions.OpenStackAPIVersions.validate,
|
||||||
|
{"invalid": {"service_type": "some_type"}},
|
||||||
|
"Non-existing clients should be caught.")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
jsonschema.ValidationError,
|
||||||
|
api_versions.OpenStackAPIVersions.validate,
|
||||||
|
{"nova": {"some_key": "some_value"}},
|
||||||
|
"Additional properties should be restricted.")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ValidationError,
|
||||||
|
api_versions.OpenStackAPIVersions.validate,
|
||||||
|
{"keystone": {"service_type": "identity"}},
|
||||||
|
"Setting service_type is allowed only for those clients, which "
|
||||||
|
"support it.")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ValidationError,
|
||||||
|
api_versions.OpenStackAPIVersions.validate,
|
||||||
|
{"keystone": {"service_name": "keystone"}},
|
||||||
|
"Setting service_name is allowed only for those clients, which "
|
||||||
|
"support it.")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ValidationError,
|
||||||
|
api_versions.OpenStackAPIVersions.validate,
|
||||||
|
{"keystone": {"version": 1}},
|
||||||
|
"Setting version is allowed only for those clients, which "
|
||||||
|
"support it.")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.ValidationError,
|
||||||
|
api_versions.OpenStackAPIVersions.validate,
|
||||||
|
{"nova": {"version": 666}},
|
||||||
|
"Unsupported version should be caught.")
|
||||||
|
|
||||||
|
def test_setup_with_wrong_service_name(self):
|
||||||
|
context = {
|
||||||
|
"config": {api_versions.OpenStackAPIVersions.get_name(): {
|
||||||
|
"nova": {"service_name": "service_name"}}},
|
||||||
|
"admin": {"credential": mock.MagicMock()},
|
||||||
|
"users": [{"credential": mock.MagicMock()}]}
|
||||||
|
ctx = api_versions.OpenStackAPIVersions(context)
|
||||||
|
self.assertRaises(exceptions.ValidationError, ctx.setup)
|
||||||
|
self.mock_kc.service_catalog.get_endpoints.assert_called_once_with()
|
||||||
|
self.mock_kc.services.list.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_setup_with_wrong_service_name_and_without_admin(self):
|
||||||
|
context = {
|
||||||
|
"config": {api_versions.OpenStackAPIVersions.get_name(): {
|
||||||
|
"nova": {"service_name": "service_name"}}},
|
||||||
|
"users": [{"credential": mock.MagicMock()}]}
|
||||||
|
ctx = api_versions.OpenStackAPIVersions(context)
|
||||||
|
self.assertRaises(exceptions.BenchmarkSetupFailure, ctx.setup)
|
||||||
|
self.mock_kc.service_catalog.get_endpoints.assert_called_once_with()
|
||||||
|
self.assertFalse(self.mock_kc.services.list.called)
|
||||||
|
|
||||||
|
def test_setup_with_wrong_service_type(self):
|
||||||
|
context = {
|
||||||
|
"config": {api_versions.OpenStackAPIVersions.get_name(): {
|
||||||
|
"nova": {"service_type": "service_type"}}},
|
||||||
|
"users": [{"credential": mock.MagicMock()}]}
|
||||||
|
ctx = api_versions.OpenStackAPIVersions(context)
|
||||||
|
self.assertRaises(exceptions.ValidationError, ctx.setup)
|
||||||
|
self.mock_kc.service_catalog.get_endpoints.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_setup_with_service_name(self):
|
||||||
|
self.mock_kc.services.list.return_value = [
|
||||||
|
utils.Struct(type="computev21", name="NovaV21")]
|
||||||
|
context = {
|
||||||
|
"config": {api_versions.OpenStackAPIVersions.get_name(): {
|
||||||
|
"nova": {"service_name": "NovaV21"}}},
|
||||||
|
"admin": {"credential": mock.MagicMock()},
|
||||||
|
"users": [{"credential": mock.MagicMock()}]}
|
||||||
|
ctx = api_versions.OpenStackAPIVersions(copy.deepcopy(context))
|
||||||
|
|
||||||
|
ctx.setup()
|
||||||
|
|
||||||
|
self.mock_kc.service_catalog.get_endpoints.assert_called_once_with()
|
||||||
|
self.mock_kc.services.list.assert_called_once_with()
|
||||||
|
|
||||||
|
self.assertEqual("computev21", ctx.config["nova"]["service_type"])
|
Loading…
x
Reference in New Issue
Block a user