Add support for Keystone API v3

bp keystone-v3-support
Change-Id: Iffdff6ee2e79b7052f7a58af933e398090879224
This commit is contained in:
Anton Frolov 2014-07-08 15:53:29 +04:00
parent d139166104
commit ed571ece7a
18 changed files with 733 additions and 182 deletions

View File

@ -19,6 +19,7 @@ from neutronclient.common import exceptions as neutron_exceptions
from rally.benchmark.scenarios.keystone import utils as kutils
from rally.benchmark import utils as bench_utils
from rally.benchmark.wrappers import keystone as keystone_wrapper
LOG = logging.getLogger(__name__)
@ -39,14 +40,15 @@ def delete_heat_resources(heat):
def delete_keystone_resources(keystone):
for resource in ["users", "tenants", "services", "roles"]:
keystone = keystone_wrapper.wrap(keystone)
for resource in ["user", "project", "service", "role"]:
_delete_single_keystone_resource_type(keystone, resource)
def _delete_single_keystone_resource_type(keystone, resource_name):
for resource in getattr(keystone, resource_name).list():
for resource in getattr(keystone, "list_%ss" % resource_name)():
if kutils.is_temporary(resource):
resource.delete()
getattr(keystone, "delete_%s" % resource_name)(resource.id)
def delete_images(glance, project_uuid):

View File

@ -17,6 +17,7 @@ from oslo.config import cfg
from rally.benchmark.context import base
from rally.benchmark import utils
from rally.benchmark.wrappers import keystone
from rally import consts
from rally.objects import endpoint
from rally.openstack.common.gettextutils import _
@ -31,7 +32,13 @@ context_opts = [
cfg.IntOpt("concurrent",
default=30,
help="How many concurrent threads use for serving users "
"context")
"context"),
cfg.StrOpt("project_domain",
default="default",
help="ID of domain in which projects will be created."),
cfg.StrOpt("user_domain",
default="default",
help="ID of domain in which users will be created."),
]
CONF = cfg.CONF
@ -63,6 +70,12 @@ class UserGenerator(base.Context):
"type": "integer",
"minimum": 1
},
"project_domain": {
"type": "string",
},
"user_domain": {
"type": "string",
},
},
"additionalProperties": False
}
@ -75,6 +88,10 @@ class UserGenerator(base.Context):
self.config.setdefault("users_per_tenant", 1)
self.config.setdefault("concurrent",
cfg.CONF.users_context.concurrent)
self.config.setdefault("project_domain",
cfg.CONF.users_context.project_domain)
self.config.setdefault("user_domain",
cfg.CONF.users_context.user_domain)
self.context["users"] = []
self.context["tenants"] = []
self.endpoint = self.context["admin"]["endpoint"]
@ -93,24 +110,27 @@ class UserGenerator(base.Context):
:returns: tuple (dict tenant, list users)
"""
admin_endpoint, users_num, task_id, i = args
admin_endpoint, users_num, project_dom, user_dom, task_id, i = args
users = []
client = osclients.Clients(admin_endpoint).keystone()
tenant = client.tenants.create(
cls.PATTERN_TENANT % {"task_id": task_id, "iter": i})
client = keystone.wrap(osclients.Clients(admin_endpoint).keystone())
tenant = client.create_project(
cls.PATTERN_TENANT % {"task_id": task_id, "iter": i}, project_dom)
LOG.debug("Creating %d users for tenant %s" % (users_num, tenant.id))
for user_id in range(users_num):
username = cls.PATTERN_USER % {"tenant_id": tenant.id,
"uid": user_id}
user = client.users.create(username, "password",
"%s@email.me" % username, tenant.id)
user = client.create_user(username, "password",
"%s@email.me" % username, tenant.id,
user_dom)
user_endpoint = endpoint.Endpoint(client.auth_url, user.name,
"password", tenant.name,
consts.EndpointPermission.USER,
client.region_name)
client.region_name,
project_domain_name=project_dom,
user_domain_name=user_dom)
users.append({"id": user.id,
"endpoint": user_endpoint,
"tenant_id": tenant.id})
@ -124,11 +144,11 @@ class UserGenerator(base.Context):
:param args: tuple arguments, for Pool.imap()
"""
admin_endpoint, tenants = args
client = osclients.Clients(admin_endpoint).keystone()
client = keystone.wrap(osclients.Clients(admin_endpoint).keystone())
for tenant in tenants:
try:
client.tenants.delete(tenant["id"])
client.delete_project(tenant["id"])
except Exception as ex:
LOG.warning("Failed to delete tenant: %(tenant_id)s. "
"Exception: %(ex)s" %
@ -141,11 +161,11 @@ class UserGenerator(base.Context):
:param args: tuple arguments, for Pool.imap()
"""
admin_endpoint, users = args
client = osclients.Clients(admin_endpoint).keystone()
client = keystone.wrap(osclients.Clients(admin_endpoint).keystone())
for user in users:
try:
client.users.delete(user["id"])
client.delete_user(user["id"])
except Exception as ex:
LOG.warning("Failed to delete user: %(user_id)s. "
"Exception: %(ex)s" %
@ -157,11 +177,12 @@ class UserGenerator(base.Context):
users_num = self.config["users_per_tenant"]
args = [(self.endpoint, users_num, self.task["uuid"], i)
args = [(self.endpoint, users_num, self.config["project_domain"],
self.config["user_domain"], self.task["uuid"], i)
for i in range(self.config["tenants"])]
LOG.debug("Creating %d users using %s threads" % (
users_num * self.config["tenants"], self.config["concurrent"]))
users_num * self.config["tenants"], self.config["concurrent"]))
for tenant, users in utils.run_concurrent(
self.config["concurrent"],

View File

View File

@ -0,0 +1,215 @@
# Copyright 2014: 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 collections
from keystoneclient import exceptions
import six
Project = collections.namedtuple('Project', ['id', 'name', 'domain_id'])
User = collections.namedtuple('User',
['id', 'name', 'project_id', 'domain_id'])
Service = collections.namedtuple('Service', ['id', 'name'])
Role = collections.namedtuple('Role', ['id', 'name'])
@six.add_metaclass(abc.ABCMeta)
class KeystoneWrapper(object):
def __init__(self, client):
self.client = client
def __getattr__(self, attr_name):
return getattr(self.client, attr_name)
@abc.abstractmethod
def create_project(self, project_name, domain_name='Default'):
"""Creates new project/tenant and return project object.
:param project_name: Name of project to be created.
:param domain_name: Name or id of domain where to create project, for
implementations that don't support domains this
argument must be None or 'Default'.
"""
@abc.abstractmethod
def delete_project(self, project_id):
"""Deletes project."""
@abc.abstractmethod
def create_user(self, username, password, email=None, project_id=None,
domain_name='Default'):
"""Creates user that have Mamber role in given project.
:param username: name of user
:param password: user password
:param project: user's default project
:param domain_name: Name or id of domain where to create project, for
implementations that don't support domains this
argument must be None or 'Default'.
"""
@abc.abstractmethod
def delete_user(self, user_id):
"""Deletes user."""
@abc.abstractmethod
def list_users(self):
"""List all users."""
@abc.abstractmethod
def list_projects(self):
"""List all projects/tenants."""
def delete_service(self, service_id):
"""Deletes service."""
self.client.services.delete(service_id)
def list_services(self):
"""List all services."""
return map(KeystoneWrapper._wrap_service, self.client.services.list())
def delete_role(self, role_id):
"""Deletes role."""
self.client.roles.delete(role_id)
def list_roles(self):
"""List all roles."""
return map(KeystoneWrapper._wrap_role, self.client.roles.list())
@staticmethod
def _wrap_service(service):
return Service(id=service.id, name=service.name)
@staticmethod
def _wrap_role(role):
return Role(id=role.id, name=role.name)
class KeystoneV2Wrapper(KeystoneWrapper):
def _check_domain(self, domain_name):
if domain_name.lower() != 'default':
raise NotImplementedError('Domain functionality not implemented '
'in Keystone v2')
@staticmethod
def _wrap_v2_tenant(tenant):
return Project(id=tenant.id, name=tenant.name, domain_id='default')
@staticmethod
def _wrap_v2_user(user):
return User(id=user.id, name=user.name, project_id=user.tenantId,
domain_id='default')
def create_project(self, project_name, domain_name='Default'):
self._check_domain(domain_name)
tenant = self.client.tenants.create(project_name)
return KeystoneV2Wrapper._wrap_v2_tenant(tenant)
def delete_project(self, project_id):
self.client.tenants.delete(project_id)
def create_user(self, username, password, email=None, project_id=None,
domain_name='Default'):
self._check_domain(domain_name)
user = self.client.users.create(username, password, email, project_id)
return KeystoneV2Wrapper._wrap_v2_user(user)
def delete_user(self, user_id):
self.client.users.delete(user_id)
def list_users(self):
return map(KeystoneV2Wrapper._wrap_v2_user, self.client.users.list())
def list_projects(self):
return map(KeystoneV2Wrapper._wrap_v2_tenant,
self.client.tenants.list())
class KeystoneV3Wrapper(KeystoneWrapper):
def _get_domain_id(self, domain_name_or_id):
try:
# First try to find domain by ID
return self.client.domains.get(domain_name_or_id).id
except exceptions.NotFound:
# Domain not found by ID, try to find it by name
domains = self.client.domains.list(name=domain_name_or_id)
if domains:
return domains[0].id
# Domain not found by name, raise original NotFound exception
raise
@staticmethod
def _wrap_v3_project(project):
return Project(id=project.id, name=project.name,
domain_id=project.domain_id)
@staticmethod
def _wrap_v3_user(user):
# When user has default_project_id that is None user.default_project_id
# will raise AttributeError
project_id = getattr(user, 'default_project_id', None)
return User(id=user.id, name=user.name, project_id=project_id,
domain_id=user.domain_id)
def create_project(self, project_name, domain_name='Default'):
domain_id = self._get_domain_id(domain_name)
project = self.client.projects.create(
name=project_name, domain=domain_id)
return KeystoneV3Wrapper._wrap_v3_project(project)
def delete_project(self, project_id):
self.client.projects.delete(project_id)
def create_user(self, username, password, email=None, project_id=None,
domain_name='Default'):
domain_id = self._get_domain_id(domain_name)
client = self.client
# Create user
user = client.users.create(name=username, password=password,
email=email, default_project=project_id,
domain=domain_id)
# Grant member role to user in project or domain
# TODO(Anton Frolov): replace hard-coded 'Member' role with role name
# gained via deployment
member_role = client.roles.list(name='Member')[0].id
client.roles.grant(member_role, user=user.id, project=project_id)
return KeystoneV3Wrapper._wrap_v3_user(user)
def delete_user(self, user_id):
self.client.users.delete(user_id)
def list_users(self):
return map(KeystoneV3Wrapper._wrap_v3_user, self.client.users.list())
def list_projects(self):
return map(KeystoneV3Wrapper._wrap_v3_project,
self.client.projects.list())
def wrap(client):
"""Returns keystone wrapper based on keystone client version."""
if client.version == 'v2.0':
return KeystoneV2Wrapper(client)
elif client.version == 'v3':
return KeystoneV3Wrapper(client)
else:
raise NotImplemented(
'Wrapper for version %s is not implemented.' % client.version)

View File

@ -181,8 +181,8 @@ class DeploymentCommands(object):
client = clients.verified_keystone()
print("keystone endpoints are valid and following "
"services are available:")
for service in client.service_catalog.get_data():
data = [service['name'], service['type'], 'Available']
for service in client.services.list():
data = [service.name, service.type, 'Available']
table_rows.append(utils.Struct(**dict(zip(headers, data))))
except exceptions.InvalidArgumentsException:
data = ['keystone', 'identity', 'Error']

View File

@ -36,6 +36,22 @@ class ExistingCloud(engine.EngineFactory):
}
}
Or using keystone v3 API endpoint:
{
"type": "ExistingCloud",
"endpoint": {
"auth_url": "http://localhost:5000/v3/",
"username": "engineer1",
"user_domain_name": "qa",
"project_name": "qa_admin_project",
"project_domain_name": "qa",
"password": "password",
"region_name": "RegionOne",
"use_public_urls": False,
"admin_port": 35357,
}
}
"""
CONFIG_SCHEMA = {
@ -48,17 +64,34 @@ class ExistingCloud(engine.EngineFactory):
'auth_url': {'type': 'string'},
'username': {'type': 'string'},
'password': {'type': 'string'},
'tenant_name': {'type': 'string'},
'region_name': {'type': 'string'},
'use_public_urls': {'type': 'boolean'},
'admin_port': {
'type': 'integer',
'minimum': 2,
'maximum': 65535
}
},
},
'required': ['auth_url', 'username', 'password',
'tenant_name'],
'oneOf': [
{
# v2.0 authentication
'properties': {
'tenant_name': {'type': 'string'},
},
'required': ['auth_url', 'username', 'password',
'tenant_name'],
},
{
# Authentication in project scope
'properties': {
'user_domain_name': {'type': 'string'},
'project_name': {'type': 'string'},
'project_domain_name': {'type': 'string'},
},
'required': ['auth_url', 'username', 'password',
'project_name'],
},
]
},
},
'required': ['type', 'endpoint'],
@ -66,16 +99,23 @@ class ExistingCloud(engine.EngineFactory):
def deploy(self):
endpoint_dict = self.deployment['config']['endpoint']
admin_endpoint = objects.Endpoint(endpoint_dict['auth_url'],
endpoint_dict['username'],
endpoint_dict['password'],
endpoint_dict['tenant_name'],
consts.EndpointPermission.ADMIN,
endpoint_dict.get('region_name'),
endpoint_dict.get('use_public_urls',
True),
endpoint_dict.get('admin_port',
35357))
project_name = endpoint_dict.get('project_name',
endpoint_dict.get('tenant_name'))
admin_endpoint = objects.Endpoint(
endpoint_dict['auth_url'], endpoint_dict['username'],
endpoint_dict['password'],
tenant_name=project_name,
permission=consts.EndpointPermission.ADMIN,
region_name=endpoint_dict.get('region_name'),
use_public_urls=endpoint_dict.get('use_public_urls', False),
admin_port=endpoint_dict.get('admin_port', 35357),
domain_name=endpoint_dict.get('domain_name'),
user_domain_name=endpoint_dict.get('user_domain_name',
'Default'),
project_domain_name=endpoint_dict.get('project_domain_name',
'Default')
)
return [admin_endpoint]
def cleanup(self):

View File

@ -18,9 +18,11 @@ from rally import consts
class Endpoint(object):
def __init__(self, auth_url, username, password, tenant_name,
def __init__(self, auth_url, username, password, tenant_name=None,
permission=consts.EndpointPermission.USER,
region_name=None, use_public_urls=False, admin_port=35357):
region_name=None, use_public_urls=False, admin_port=35357,
domain_name=None, user_domain_name='Default',
project_domain_name='Default'):
self.auth_url = auth_url
self.username = username
self.password = password
@ -29,13 +31,19 @@ class Endpoint(object):
self.region_name = region_name
self.use_public_urls = use_public_urls
self.admin_port = admin_port
self.domain_name = domain_name
self.user_domain_name = user_domain_name
self.project_domain_name = project_domain_name
def to_dict(self, include_permission=False):
dct = {"auth_url": self.auth_url, "username": self.username,
"password": self.password, "tenant_name": self.tenant_name,
"region_name": self.region_name,
"use_public_urls": self.use_public_urls,
"admin_port": self.admin_port}
"admin_port": self.admin_port,
"domain_name": self.domain_name,
"user_domain_name": self.user_domain_name,
"project_domain_name": self.project_domain_name}
if include_permission:
dct["permission"] = self.permission
return dct

View File

@ -20,8 +20,10 @@ from cinderclient import client as cinder
import glanceclient as glance
from heatclient import client as heat
from ironicclient import client as ironic
from keystoneclient import discover as keystone_discover
from keystoneclient import exceptions as keystone_exceptions
from keystoneclient.v2_0 import client as keystone
from keystoneclient.v2_0 import client as keystone_v2
from keystoneclient.v3 import client as keystone_v3
from neutronclient.neutron import client as neutron
from novaclient import client as nova
from oslo.config import cfg
@ -60,6 +62,18 @@ def cached(func):
return wrapper
def create_keystone_client(args):
discover = keystone_discover.Discover(**args)
for version_data in discover.version_data():
version = version_data['version']
if version[0] <= 2:
return keystone_v2.Client(**args)
elif version[0] == 3:
return keystone_v3.Client(**args)
raise exceptions.RallyException(
'Failed to discover keystone version for url %(auth_url)s.', **args)
class Clients(object):
"""This class simplify and unify work with openstack python clients."""
@ -90,7 +104,7 @@ class Clients(object):
)
else:
kw["endpoint"] = kw["auth_url"]
client = keystone.Client(**kw)
client = create_keystone_client(kw)
client.authenticate()
return client
@ -102,8 +116,7 @@ class Clients(object):
try:
# Ensure that user is admin
client = self.keystone()
roles = client.auth_ref['user']['roles']
if not any('admin' == role['name'] for role in roles):
if 'admin' not in client.auth_ref.role_names:
raise exceptions.InvalidAdminException(
username=self.endpoint.username)
except keystone_exceptions.Unauthorized:
@ -116,28 +129,29 @@ class Clients(object):
@cached
def nova(self, version='2'):
"""Return nova client."""
kc = self.keystone()
compute_api_url = kc.service_catalog.url_for(
service_type='compute', endpoint_type='public',
region_name=self.endpoint.region_name)
client = nova.Client(version,
self.endpoint.username,
self.endpoint.password,
self.endpoint.tenant_name,
auth_url=self.endpoint.auth_url,
region_name=self.endpoint.region_name,
service_type='compute',
auth_token=kc.auth_token,
http_log_debug=CONF.debug,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
client.set_management_url(compute_api_url)
return client
@cached
def neutron(self, version='2.0'):
"""Return neutron client."""
kc = self.keystone()
network_api_url = kc.service_catalog.url_for(
service_type='network', endpoint_type='public',
region_name=self.endpoint.region_name)
client = neutron.Client(version,
username=self.endpoint.username,
password=self.endpoint.password,
tenant_name=self.endpoint.tenant_name,
auth_url=self.endpoint.auth_url,
region_name=self.endpoint.region_name,
token=kc.auth_token,
endpoint_url=network_api_url,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
@ -147,11 +161,12 @@ class Clients(object):
def glance(self, version='1'):
"""Return glance client."""
kc = self.keystone()
endpoint = kc.service_catalog.get_endpoints()['image'][0]
image_api_url = kc.service_catalog.url_for(
service_type='image', endpoint_type='public',
region_name=self.endpoint.region_name)
client = glance.Client(version,
endpoint=endpoint['publicURL'],
endpoint=image_api_url,
token=kc.auth_token,
region_name=self.endpoint.region_name,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
@ -161,12 +176,12 @@ class Clients(object):
def heat(self, version='1'):
"""Return heat client."""
kc = self.keystone()
endpoint = kc.service_catalog.get_endpoints()['orchestration'][0]
orchestration_api_url = kc.service_catalog.url_for(
service_type='orchestration', endpoint_type='public',
region_name=self.endpoint.region_name)
client = heat.Client(version,
endpoint=endpoint['publicURL'],
endpoint=orchestration_api_url,
token=kc.auth_token,
region_name=self.endpoint.region_name,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
@ -175,33 +190,34 @@ class Clients(object):
@cached
def cinder(self, version='1'):
"""Return cinder client."""
client = cinder.Client(version,
self.endpoint.username,
self.endpoint.password,
self.endpoint.tenant_name,
auth_url=self.endpoint.auth_url,
region_name=self.endpoint.region_name,
service_type='volume',
client = cinder.Client(version, None, None,
http_log_debug=CONF.debug,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
kc = self.keystone()
volume_api_url = kc.service_catalog.url_for(
service_type='volume', endpoint_type='public',
region_name=self.endpoint.region_name)
client.client.management_url = volume_api_url
client.client.auth_token = kc.auth_token
return client
@cached
def ceilometer(self, version='2'):
"""Return ceilometer client."""
kc = self.keystone()
endpoint = kc.service_catalog.get_endpoints()['metering'][0]
metering_api_url = kc.service_catalog.url_for(
service_type='metering', endpoint_type='public',
region_name=self.endpoint.region_name)
auth_token = kc.auth_token
if not hasattr(auth_token, '__call__'):
# python-ceilometerclient requires auth_token to be a callable
auth_token = lambda: kc.auth_token
client = ceilometer.Client(version,
endpoint=endpoint['publicURL'],
endpoint=metering_api_url,
token=auth_token,
region_name=self.endpoint.region_name,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
@ -210,14 +226,16 @@ class Clients(object):
@cached
def ironic(self, version='1.0'):
"""Return Ironic client."""
client = ironic.Client(version,
username=self.endpoint.username,
password=self.endpoint.password,
tenant_name=self.endpoint.tenant_name,
auth_url=self.endpoint.auth_url,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
kc = self.keystone()
baremetal_api_url = kc.service_catalog.url_for(
service_type='baremetal', endpoint_type='public',
region_name=self.endpoint.region_name)
client = ironic.get_client(version,
os_auth_token=kc.auth_token,
ironic_url=baremetal_api_url,
timeout=CONF.openstack_client_http_timeout,
insecure=CONF.https_insecure,
cacert=CONF.https_cacert)
return client
@cached

View File

@ -19,7 +19,6 @@ import mock
from rally.benchmark.context import users
from rally.benchmark import utils
from tests import fakes
from tests import test
@ -49,12 +48,26 @@ class UserGeneratorTestCase(test.TestCase):
"task": mock.MagicMock()
}
@mock.patch("rally.benchmark.context.users.osclients")
def test_create_tenant_users(self, mock_osclients):
users_num = 5
args = (mock.MagicMock(), users_num,
'ad325aec-f7b4-4a62-832a-bb718e465bb7', 1)
def setUp(self):
super(UserGeneratorTestCase, self).setUp()
self.osclients_patcher = mock.patch(
"rally.benchmark.context.users.osclients")
self.osclients = self.osclients_patcher.start()
self.keystone_wrapper_patcher = mock.patch(
"rally.benchmark.context.users.keystone")
self.keystone_wrapper = self.keystone_wrapper_patcher.start()
self.wrapped_keystone = self.keystone_wrapper.wrap.return_value
def tearDown(self):
self.keystone_wrapper_patcher.stop()
self.osclients_patcher.stop()
super(UserGeneratorTestCase, self).tearDown()
def test_create_tenant_users(self):
users_num = 5
args = (mock.MagicMock(), users_num, 'default', 'default',
'ad325aec-f7b4-4a62-832a-bb718e465bb7', 1)
result = users.UserGenerator._create_tenant_users(args)
self.assertEqual(len(result), 2)
@ -66,55 +79,55 @@ class UserGeneratorTestCase(test.TestCase):
self.assertIn("id", user)
self.assertIn("endpoint", user)
@mock.patch("rally.benchmark.context.users.osclients")
def test_delete_tenants(self, mock_osclients):
def test_delete_tenants(self):
tenant1 = mock.MagicMock()
tenant2 = mock.MagicMock()
args = (mock.MagicMock(), [tenant1, tenant2])
users.UserGenerator._delete_tenants(args)
mock_osclients.Clients().keystone().tenants.delete.assert_has_calls([
self.keystone_wrapper.wrap.assert_called_once()
self.wrapped_keystone.delete_project.assert_has_calls([
mock.call(tenant1["id"]),
mock.call(tenant2["id"])])
@mock.patch("rally.benchmark.context.users.osclients")
def test_delete_users(self, mock_osclients):
def test_delete_users(self):
user1 = mock.MagicMock()
user2 = mock.MagicMock()
args = (mock.MagicMock(), [user1, user2])
users.UserGenerator._delete_users(args)
mock_osclients.Clients().keystone().users.delete.assert_has_calls([
self.wrapped_keystone.delete_user.assert_has_calls([
mock.call(user1["id"]),
mock.call(user2["id"])])
@mock.patch("rally.benchmark.context.users.osclients")
def test_setup_and_cleanup(self, mock_osclients):
fc = fakes.FakeClients()
mock_osclients.Clients.return_value = fc
def test_setup_and_cleanup(self):
with users.UserGenerator(self.context) as ctx:
self.assertEqual(len(fc.keystone().users.list()), 0)
self.assertEqual(len(fc.keystone().tenants.list()), 0)
self.assertEqual(self.wrapped_keystone.create_user.call_count, 0)
self.assertEqual(self.wrapped_keystone.create_project.call_count,
0)
ctx.setup()
self.assertEqual(len(ctx.context["users"]),
self.users_num)
self.assertEqual(len(fc.keystone().users.list()),
self.assertEqual(self.wrapped_keystone.create_user.call_count,
self.users_num)
self.assertEqual(len(ctx.context["tenants"]),
self.tenants_num)
self.assertEqual(len(fc.keystone().tenants.list()),
self.assertEqual(self.wrapped_keystone.create_project.call_count,
self.tenants_num)
# Assert nothing is deleted yet
self.assertEqual(self.wrapped_keystone.delete_user.call_count,
0)
self.assertEqual(self.wrapped_keystone.delete_project.call_count,
0)
# Cleanup (called by content manager)
self.assertEqual(len(fc.keystone().users.list()), 0)
self.assertEqual(len(fc.keystone().tenants.list()), 0)
@mock.patch("rally.benchmark.context.users.osclients")
def test_users_and_tenants_in_context(self, mock_osclients):
fc = fakes.FakeClients()
mock_osclients.Clients.return_value = fc
self.assertEqual(self.wrapped_keystone.delete_user.call_count,
self.users_num)
self.assertEqual(self.wrapped_keystone.delete_project.call_count,
self.tenants_num)
def test_users_and_tenants_in_context(self):
task = {"uuid": "abcdef"}
config = {
@ -129,30 +142,32 @@ class UserGeneratorTestCase(test.TestCase):
"task": task
}
user_list = [mock.MagicMock(id='id_%d' % i)
for i in range(self.users_num)]
self.wrapped_keystone.create_user.side_effect = user_list
with users.UserGenerator(config) as ctx:
ctx.setup()
tenants = []
for i, t in enumerate(fc.keystone().tenants.list()):
create_tenant_calls = []
for i, t in enumerate(ctx.context["tenants"]):
pattern = users.UserGenerator.PATTERN_TENANT
tenants.append({
"id": t.id,
"name": pattern % {"task_id": task["uuid"], "iter": i}
})
create_tenant_calls.append(
mock.call(pattern % {"task_id": task["uuid"], "iter": i},
ctx.config["project_domain"]))
self.assertEqual(ctx.context["tenants"], tenants)
self.wrapped_keystone.create_project.assert_has_calls(
create_tenant_calls, any_order=True)
for user in ctx.context["users"]:
self.assertEqual(set(["id", "endpoint", "tenant_id"]),
set(user.keys()))
tenants_ids = []
for t in tenants:
for t in ctx.context["tenants"]:
tenants_ids.extend([t["id"], t["id"]])
users_ids = [user.id for user in fc.keystone().users.list()]
for (user, tenant_id, user_id) in zip(ctx.context["users"],
tenants_ids, users_ids):
self.assertEqual(user["id"], user_id)
for (user, tenant_id, orig_user) in zip(ctx.context["users"],
tenants_ids, user_list):
self.assertEqual(user["id"], orig_user.id)
self.assertEqual(user["tenant_id"], tenant_id)

View File

View File

@ -0,0 +1,204 @@
# Copyright 2014: 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 keystoneclient import exceptions
import mock
from rally.benchmark.wrappers import keystone
from tests import test
class KeystoneWrapperTestBase(object):
def test_list_services(self):
service = mock.MagicMock()
service.id = 'fake_id'
service.name = 'Foobar'
service.extra_field = 'extra_field'
self.client.services.list.return_value = [service]
result = self.wrapped_client.list_services()
self.assertEqual([('fake_id', 'Foobar')], result)
self.assertEqual('fake_id', result[0].id)
self.assertEqual('Foobar', result[0].name)
self.assertFalse(hasattr(result[0], 'extra_field'))
def test_delete_service(self):
self.wrapped_client.delete_service('fake_id')
self.client.services.delete.assert_called_once_with('fake_id')
def test_list_roles(self):
role = mock.MagicMock()
role.id = 'fake_id'
role.name = 'Foobar'
role.extra_field = 'extra_field'
self.client.roles.list.return_value = [role]
result = self.wrapped_client.list_roles()
self.assertEqual([('fake_id', 'Foobar')], result)
self.assertEqual('fake_id', result[0].id)
self.assertEqual('Foobar', result[0].name)
self.assertFalse(hasattr(result[0], 'extra_field'))
def test_delete_role(self):
self.wrapped_client.delete_role('fake_id')
self.client.roles.delete.assert_called_once_with('fake_id')
class KeystoneV2WrapperTestCase(test.TestCase, KeystoneWrapperTestBase):
def setUp(self):
super(KeystoneV2WrapperTestCase, self).setUp()
self.client = mock.MagicMock()
self.client.version = 'v2.0'
self.wrapped_client = keystone.wrap(self.client)
def test_create_project(self):
self.wrapped_client.create_project('Foobar')
self.client.tenants.create.assert_called_once_with('Foobar')
def test_create_project_in_non_default_domain_fail(self):
self.assertRaises(
NotImplementedError, self.wrapped_client.create_project,
'Foobar', 'non-default-domain')
def test_delete_project(self):
self.wrapped_client.delete_project('fake_id')
self.client.tenants.delete.assert_called_once_with('fake_id')
def test_list_projects(self):
tenant = mock.MagicMock()
tenant.id = 'fake_id'
tenant.name = 'Foobar'
tenant.extra_field = 'extra_field'
self.client.tenants.list.return_value = [tenant]
result = self.wrapped_client.list_projects()
self.assertEqual([('fake_id', 'Foobar', 'default')], result)
self.assertEqual('fake_id', result[0].id)
self.assertEqual('Foobar', result[0].name)
self.assertEqual('default', result[0].domain_id)
self.assertFalse(hasattr(result[0], 'extra_field'))
def test_create_user(self):
self.wrapped_client.create_user('foo', 'bar', email='foo@bar.com',
project_id='tenant_id',
domain_name='default')
self.client.users.create.assert_called_once_with(
'foo', 'bar', 'foo@bar.com', 'tenant_id')
def test_create_user_in_non_default_domain_fail(self):
self.assertRaises(
NotImplementedError, self.wrapped_client.create_user,
'foo', 'bar', email='foo@bar.com', project_id='tenant_id',
domain_name='non-default-domain')
def test_delete_user(self):
self.wrapped_client.delete_user('fake_id')
self.client.users.delete.assert_called_once_with('fake_id')
def test_list_users(self):
user = mock.MagicMock()
user.id = 'fake_id'
user.name = 'foo'
user.tenantId = 'tenant_id'
user.extra_field = 'extra_field'
self.client.users.list.return_value = [user]
result = self.wrapped_client.list_users()
self.assertEqual([('fake_id', 'foo', 'tenant_id', 'default')], result)
self.assertEqual('fake_id', result[0].id)
self.assertEqual('foo', result[0].name)
self.assertEqual('tenant_id', result[0].project_id)
self.assertEqual('default', result[0].domain_id)
self.assertFalse(hasattr(result[0], 'extra_field'))
class KeystoneV3WrapperTestCase(test.TestCase, KeystoneWrapperTestBase):
def setUp(self):
super(KeystoneV3WrapperTestCase, self).setUp()
self.client = mock.MagicMock()
self.client.version = 'v3'
self.wrapped_client = keystone.wrap(self.client)
self.client.domains.get.side_effect = exceptions.NotFound
self.client.domains.list.return_value = [
mock.MagicMock(id='domain_id')]
def test_create_project(self):
self.wrapped_client.create_project('Foobar', 'domain')
self.client.projects.create.assert_called_once_with(
name='Foobar', domain='domain_id')
def test_create_project_with_non_existing_domain_fail(self):
self.client.domains.list.return_value = []
self.assertRaises(exceptions.NotFound,
self.wrapped_client.create_project,
'Foobar', 'non-existing-domain')
def test_delete_project(self):
self.wrapped_client.delete_project('fake_id')
self.client.projects.delete.assert_called_once_with('fake_id')
def test_list_projects(self):
project = mock.MagicMock()
project.id = 'fake_id'
project.name = 'Foobar'
project.domain_id = 'domain_id'
project.extra_field = 'extra_field'
self.client.projects.list.return_value = [project]
result = self.wrapped_client.list_projects()
self.assertEqual([('fake_id', 'Foobar', 'domain_id')], result)
self.assertEqual('fake_id', result[0].id)
self.assertEqual('Foobar', result[0].name)
self.assertEqual('domain_id', result[0].domain_id)
self.assertFalse(hasattr(result[0], 'extra_field'))
def test_create_user(self):
self.client.roles.list.return_value = [
mock.MagicMock(id='fake_role_id')]
self.client.users.create.return_value = mock.MagicMock(
id='fake_user_id')
self.wrapped_client.create_user(
'foo', 'bar', email='foo@bar.com',
project_id='project_id', domain_name='domain')
self.client.users.create.assert_called_once_with(
name='foo', password='bar',
email='foo@bar.com', default_project='project_id',
domain='domain_id')
self.client.roles.grant.assert_called_once_with(
'fake_role_id', user='fake_user_id', project='project_id')
def test_create_user_with_non_existing_domain_fail(self):
self.client.domains.list.return_value = []
self.assertRaises(exceptions.NotFound,
self.wrapped_client.create_user, 'foo', 'bar',
email='foo@bar.com', project_id='project_id',
domain_name='non-existing-domain')
def test_delete_user(self):
self.wrapped_client.delete_user('fake_id')
self.client.users.delete.assert_called_once_with('fake_id')
def test_list_users(self):
user = mock.MagicMock()
user.id = 'fake_id'
user.name = 'foo'
user.default_project_id = 'project_id'
user.domain_id = 'domain_id'
user.extra_field = 'extra_field'
self.client.users.list.return_value = [user]
result = self.wrapped_client.list_users()
self.assertEqual(
[('fake_id', 'foo', 'project_id', 'domain_id')], result)
self.assertEqual('fake_id', result[0].id)
self.assertEqual('foo', result[0].name)
self.assertEqual('project_id', result[0].project_id)
self.assertEqual('domain_id', result[0].domain_id)
self.assertFalse(hasattr(result[0], 'extra_field'))

View File

@ -33,8 +33,11 @@ class TestExistingCloud(test.TestCase):
'password': 'myadminpass',
'tenant_name': 'demo',
'region_name': 'RegionOne',
'use_public_urls': True,
'admin_port': 35357
'use_public_urls': False,
'admin_port': 35357,
'domain_name': None,
'project_domain_name': 'Default',
'user_domain_name': 'Default',
},
},
}

View File

@ -88,9 +88,9 @@ class OpenStackProviderTestCase(test.TestCase):
self.assertEqual('nova', os_provider.nova)
self.assertEqual('glance', os_provider.glance)
@mock.patch('rally.osclients.Clients.glance')
def test_init_no_glance(self, mock_glance):
mock_glance.side_effect = KeyError('image')
@mock.patch('rally.osclients.Clients')
def test_init_no_glance(self, mock_clients):
mock_clients.return_value.glance.side_effect = KeyError('image')
cfg = self._get_valid_config()
provider = OSProvider(mock.MagicMock(), cfg)
self.assertEqual(provider.glance, None)

View File

@ -546,6 +546,9 @@ class FakeServiceCatalog(object):
return {'image': [{'publicURL': 'http://fake.to'}],
'metering': [{'publicURL': 'http://fake.to'}]}
def url_for(self, **kwargs):
return 'http://fake.to'
class FakeGlanceClient(object):
@ -583,6 +586,7 @@ class FakeNovaClient(object):
self.security_groups = FakeSecurityGroupManager(
rule_manager=self.security_group_rules)
self.quotas = FakeNovaQuotasManager()
self.set_management_url = mock.MagicMock()
class FakeKeystoneClient(object):
@ -598,7 +602,11 @@ class FakeKeystoneClient(object):
self.auth_tenant_id = generate_uuid()
self.service_catalog = FakeServiceCatalog()
self.region_name = 'RegionOne'
self.auth_ref = {'user': {'roles': [{'name': 'admin'}]}}
self.auth_ref = mock.Mock()
self.auth_ref.role_names = ['admin']
self.version = 'v2.0'
self.session = mock.Mock()
self.authenticate = mock.MagicMock()
def authenticate(self):
return True

View File

@ -25,4 +25,7 @@ class EndpointTestCase(test.TestCase):
{"auth_url": "url", "username": "user",
"password": "pwd", "tenant_name": "tenant",
"region_name": None, "permission": "admin",
"use_public_urls": False, 'admin_port': 35357})
"domain_name": None, "use_public_urls": False,
"project_domain_name": "Default",
"user_domain_name": "Default",
'admin_port': 35357})

View File

@ -36,6 +36,9 @@ FAKE_DEPLOY_CONFIG = {
'region_name': 'RegionOne',
'use_public_urls': False,
'admin_port': 35357,
'domain_name': None,
'project_domain_name': 'Default',
'user_domain_name': 'Default',
},
}

View File

@ -33,24 +33,33 @@ class OSClientsTestCase(test.TestCase):
"tenant")
self.clients = osclients.Clients(self.endpoint)
self.fake_keystone = fakes.FakeKeystoneClient()
self.fake_keystone.auth_token = mock.MagicMock()
self.service_catalog = self.fake_keystone.service_catalog
self.service_catalog.url_for = mock.MagicMock()
keystone_patcher = mock.patch("rally.osclients.create_keystone_client")
self.mock_create_keystone_client = keystone_patcher.start()
self.addCleanup(keystone_patcher.stop)
self.mock_create_keystone_client.return_value = self.fake_keystone
def tearDown(self):
super(OSClientsTestCase, self).tearDown()
def test_keystone(self):
with mock.patch("rally.osclients.keystone") as mock_keystone:
fake_keystone = fakes.FakeKeystoneClient()
mock_keystone.Client = mock.MagicMock(return_value=fake_keystone)
self.assertTrue("keystone" not in self.clients.cache)
client = self.clients.keystone()
self.assertEqual(client, fake_keystone)
endpoint = {"timeout": cfg.CONF.openstack_client_http_timeout,
"insecure": False, "cacert": None}
kwargs = dict(self.endpoint.to_dict().items() + endpoint.items())
mock_keystone.Client.assert_called_once_with(**kwargs)
self.assertEqual(self.clients.cache["keystone"], fake_keystone)
self.assertTrue("keystone" not in self.clients.cache)
client = self.clients.keystone()
self.assertEqual(client, self.fake_keystone)
endpoint = {"timeout": cfg.CONF.openstack_client_http_timeout,
"insecure": False, "cacert": None}
kwargs = dict(self.endpoint.to_dict().items() + endpoint.items())
self.mock_create_keystone_client.assert_called_once_with(kwargs)
self.assertEqual(self.clients.cache["keystone"], self.fake_keystone)
@mock.patch("rally.osclients.Clients.keystone")
def test_verified_keystone_user_not_admin(self, mock_keystone):
mock_keystone.return_value = fakes.FakeKeystoneClient()
mock_keystone.return_value.auth_ref["user"]["roles"] = [{"name":
"notadmin"}]
mock_keystone.return_value.auth_ref.role_names = ["notadmin"]
self.assertRaises(exceptions.InvalidAdminException,
self.clients.verified_keystone)
@ -75,14 +84,17 @@ class OSClientsTestCase(test.TestCase):
self.assertTrue("nova" not in self.clients.cache)
client = self.clients.nova()
self.assertEqual(client, fake_nova)
self.service_catalog.url_for.assert_called_once_with(
service_type='compute', endpoint_type='public',
region_name=self.endpoint.region_name)
mock_nova.Client.assert_called_once_with(
"2", self.endpoint.username, self.endpoint.password,
self.endpoint.tenant_name, auth_url=self.endpoint.auth_url,
region_name=self.endpoint.region_name,
service_type="compute",
"2",
auth_token=self.fake_keystone.auth_token,
http_log_debug=False,
timeout=cfg.CONF.openstack_client_http_timeout,
insecure=False, cacert=None)
client.set_management_url.assert_called_once_with(
self.service_catalog.url_for.return_value)
self.assertEqual(self.clients.cache["nova"], fake_nova)
@mock.patch("rally.osclients.neutron")
@ -93,15 +105,15 @@ class OSClientsTestCase(test.TestCase):
client = self.clients.neutron()
self.assertEqual(client, fake_neutron)
kw = {
"username": self.endpoint.username,
"password": self.endpoint.password,
"tenant_name": self.endpoint.tenant_name,
"auth_url": self.endpoint.auth_url,
"region_name": self.endpoint.region_name,
"token": self.fake_keystone.auth_token,
"endpoint_url": self.service_catalog.url_for.return_value,
"timeout": cfg.CONF.openstack_client_http_timeout,
"insecure": cfg.CONF.https_insecure,
"cacert": cfg.CONF.https_cacert
}
self.service_catalog.url_for.assert_called_once_with(
service_type='network', endpoint_type='public',
region_name=self.endpoint.region_name)
mock_neutron.Client.assert_called_once_with("2.0", **kw)
self.assertEqual(self.clients.cache["neutron"], fake_neutron)
@ -109,36 +121,38 @@ class OSClientsTestCase(test.TestCase):
with mock.patch("rally.osclients.glance") as mock_glance:
fake_glance = fakes.FakeGlanceClient()
mock_glance.Client = mock.MagicMock(return_value=fake_glance)
kc = fakes.FakeKeystoneClient()
self.clients.keystone = mock.MagicMock(return_value=kc)
self.assertTrue("glance" not in self.clients.cache)
client = self.clients.glance()
self.assertEqual(client, fake_glance)
endpoint = kc.service_catalog.get_endpoints()["image"][0]
kw = {"endpoint": endpoint["publicURL"],
"token": kc.auth_token,
kw = {"endpoint": self.service_catalog.url_for.return_value,
"token": self.fake_keystone.auth_token,
"timeout": cfg.CONF.openstack_client_http_timeout,
"insecure": False, "cacert": None,
"region_name": None}
"insecure": False, "cacert": None}
self.service_catalog.url_for.assert_called_once_with(
service_type='image', endpoint_type='public',
region_name=self.endpoint.region_name)
mock_glance.Client.assert_called_once_with("1", **kw)
self.assertEqual(self.clients.cache["glance"], fake_glance)
def test_cinder(self):
with mock.patch("rally.osclients.cinder") as mock_cinder:
fake_cinder = fakes.FakeCinderClient()
fake_cinder.client = mock.MagicMock()
mock_cinder.Client = mock.MagicMock(return_value=fake_cinder)
self.assertTrue("cinder" not in self.clients.cache)
client = self.clients.cinder()
self.assertEqual(client, fake_cinder)
self.service_catalog.url_for.assert_called_once_with(
service_type='volume', endpoint_type='public',
region_name=self.endpoint.region_name)
mock_cinder.Client.assert_called_once_with(
"1", self.endpoint.username, self.endpoint.password,
self.endpoint.tenant_name, auth_url=self.endpoint.auth_url,
region_name=self.endpoint.region_name,
service_type="volume",
http_log_debug=False,
"1", None, None, http_log_debug=False,
timeout=cfg.CONF.openstack_client_http_timeout,
insecure=False, cacert=None)
self.assertEqual(fake_cinder.client.management_url,
self.service_catalog.url_for.return_value)
self.assertEqual(fake_cinder.client.auth_token,
self.fake_keystone.auth_token)
self.assertEqual(self.clients.cache["cinder"], fake_cinder)
def test_ceilometer(self):
@ -146,19 +160,16 @@ class OSClientsTestCase(test.TestCase):
fake_ceilometer = fakes.FakeCeilometerClient()
mock_ceilometer.Client = mock.MagicMock(
return_value=fake_ceilometer)
kc = fakes.FakeKeystoneClient()
self.clients.keystone = mock.MagicMock(return_value=kc)
self.assertTrue("ceilometer" not in self.clients.cache)
kc.auth_token = mock.MagicMock()
client = self.clients.ceilometer()
self.assertEqual(client, fake_ceilometer)
endpoint = kc.service_catalog.get_endpoints()["metering"][0]
kw = {"endpoint": endpoint["publicURL"],
"token": kc.auth_token,
self.service_catalog.url_for.assert_called_once_with(
service_type='metering', endpoint_type='public',
region_name=self.endpoint.region_name)
kw = {"endpoint": self.service_catalog.url_for.return_value,
"token": self.fake_keystone.auth_token,
"timeout": cfg.CONF.openstack_client_http_timeout,
"insecure": False, "cacert": None,
"region_name": None}
"insecure": False, "cacert": None}
mock_ceilometer.Client.assert_called_once_with("2", **kw)
self.assertEqual(self.clients.cache["ceilometer"],
fake_ceilometer)
@ -166,20 +177,21 @@ class OSClientsTestCase(test.TestCase):
@mock.patch("rally.osclients.ironic")
def test_ironic(self, mock_ironic):
fake_ironic = fakes.FakeIronicClient()
mock_ironic.Client = mock.MagicMock(return_value=fake_ironic)
mock_ironic.get_client = mock.MagicMock(return_value=fake_ironic)
self.assertTrue("ironic" not in self.clients.cache)
client = self.clients.ironic()
self.assertEqual(client, fake_ironic)
self.service_catalog.url_for.assert_called_once_with(
service_type='baremetal', endpoint_type='public',
region_name=self.endpoint.region_name)
kw = {
"username": self.endpoint.username,
"password": self.endpoint.password,
"tenant_name": self.endpoint.tenant_name,
"auth_url": self.endpoint.auth_url,
"os_auth_token": self.fake_keystone.auth_token,
"ironic_url": self.service_catalog.url_for.return_value,
"timeout": cfg.CONF.openstack_client_http_timeout,
"insecure": cfg.CONF.https_insecure,
"cacert": cfg.CONF.https_cacert
}
mock_ironic.Client.assert_called_once_with("1.0", **kw)
mock_ironic.get_client.assert_called_once_with("1.0", **kw)
self.assertEqual(self.clients.cache["ironic"], fake_ironic)
@mock.patch("rally.osclients.sahara")

View File

@ -43,6 +43,10 @@ class ConfigTestCase(test.TestCase):
self.deploy_id = "fake_deploy_id"
self.conf_generator = config.TempestConf(self.deploy_id)
keystone_patcher = mock.patch("rally.osclients.create_keystone_client")
keystone_patcher.start()
self.addCleanup(keystone_patcher.stop)
def _remove_default_section(self, items):
# getting items from configparser by specified section name
# retruns also values from DEFAULT section
@ -149,8 +153,7 @@ class ConfigTestCase(test.TestCase):
self.conf_generator._set_compute_flavors)
@mock.patch("rally.osclients.glance")
@mock.patch("rally.osclients.keystone")
def test__set_compute_images(self, mock_keystone, mock_glance):
def test__set_compute_images(self, mock_glance):
mock_glanceclient = mock.MagicMock()
mock_glanceclient.images.list.return_value = [
fakes.FakeImage(id="id1", name="cirros1"),
@ -163,10 +166,8 @@ class ConfigTestCase(test.TestCase):
self.assertEqual(sorted(expected), sorted(results))
@mock.patch("rally.osclients.glance")
@mock.patch("rally.osclients.keystone")
@mock.patch("six.moves.builtins.open")
def test__set_compute_images_create(self, mock_open, mock_keystone,
mock_glance):
def test__set_compute_images_create(self, mock_open, mock_glance):
mock_glanceclient = mock.MagicMock()
mock_glanceclient.images.list.return_value = []
mock_glanceclient.images.create.side_effect = [
@ -180,9 +181,7 @@ class ConfigTestCase(test.TestCase):
self.assertEqual(sorted(expected), sorted(results))
@mock.patch("rally.osclients.glance")
@mock.patch("rally.osclients.keystone")
def test__set_compute_images_create_fails(self, mock_keystone,
mock_glance):
def test__set_compute_images_create_fails(self, mock_glance):
mock_glanceclient = mock.MagicMock()
mock_glanceclient.images.list.return_value = []
mock_glanceclient.images.create.side_effect = Exception()