Refactor osclients
Now keystoneclient are separeted into 2 parts: working with resources and authorization part. Secand part was moved into keystoneauth1 repo. That patch is intended to drop depraceted usage of keystoneclient for authorization in favor of keystoneauth1. Furthermore, in the next patch we are going to use keystoneauth1 session for initialization of clients where its possible. Also : * Remove deprecated register decorator from osclient * Use _get_endpoint() in proper places * Refactor create_client in Keystone * Refactor unit tests in test_osclients Change-Id: If67dffcd27f9bc3e7484d02eac62035c82415c3a
This commit is contained in:
parent
f8a86b4489
commit
35c149c1a2
@ -148,33 +148,45 @@ class OSClient(plugin.Plugin):
|
||||
self.cache)
|
||||
return keystone(*args, **kwargs)
|
||||
|
||||
def _get_session(self, auth=None, endpoint=None):
|
||||
from keystoneclient.auth import token_endpoint
|
||||
from keystoneclient import session as ks_session
|
||||
|
||||
if auth is None:
|
||||
endpoint = endpoint or self._get_endpoint()
|
||||
kc = self.keystone()
|
||||
auth = token_endpoint.Token(endpoint, kc.auth_token)
|
||||
verify = self.credential.cacert or not self.credential.insecure
|
||||
return ks_session.Session(
|
||||
auth=auth, verify=verify,
|
||||
timeout=CONF.openstack_client_http_timeout)
|
||||
|
||||
def _get_keystoneauth_session(self):
|
||||
from keystoneauth1 import loading
|
||||
def _get_session(self, auth_url=None, version=None):
|
||||
from keystoneauth1 import discover
|
||||
from keystoneauth1 import session
|
||||
loader = loading.get_plugin_loader("password")
|
||||
plugin = loader.load_from_options(
|
||||
auth_url=self.credential.auth_url,
|
||||
username=self.credential.username,
|
||||
password=self.credential.password,
|
||||
user_domain_name=self.credential.user_domain_name,
|
||||
project_name=self.credential.tenant_name,
|
||||
project_domain_name=self.credential.project_domain_name)
|
||||
sess = session.Session(auth=plugin, verify=(
|
||||
not self.credential.insecure))
|
||||
return sess
|
||||
from keystoneclient.auth import identity
|
||||
|
||||
password_args = {
|
||||
"auth_url": auth_url or self.credential.auth_url,
|
||||
"username": self.credential.username,
|
||||
"password": self.credential.password,
|
||||
"tenant_name": self.credential.tenant_name
|
||||
}
|
||||
|
||||
version = OSClient.get("keystone")(
|
||||
self.credential, self.api_info, self.cache).choose_version(version)
|
||||
if version is None:
|
||||
# NOTE(rvasilets): If version not specified than we discover
|
||||
# available version with the smallest number. To be able to
|
||||
# discover versions we need session
|
||||
temp_session = session.Session(
|
||||
verify=(
|
||||
self.credential.cacert or not self.credential.insecure),
|
||||
timeout=CONF.openstack_client_http_timeout)
|
||||
version = str(discover.Discover(
|
||||
temp_session,
|
||||
password_args["auth_url"]).version_data()[0]["version"][0])
|
||||
|
||||
if "v2.0" not in password_args["auth_url"] and (
|
||||
version != "2"):
|
||||
password_args.update({
|
||||
"user_domain_name": self.credential.user_domain_name,
|
||||
"domain_name": self.credential.domain_name,
|
||||
"project_domain_name": self.credential.project_domain_name,
|
||||
})
|
||||
identity_plugin = identity.Password(**password_args)
|
||||
sess = session.Session(
|
||||
auth=identity_plugin, verify=(
|
||||
self.credential.cacert or not self.credential.insecure),
|
||||
timeout=CONF.openstack_client_http_timeout)
|
||||
return sess, identity_plugin
|
||||
|
||||
def _get_endpoint(self, service_type=None):
|
||||
kc = self.keystone()
|
||||
@ -239,36 +251,6 @@ class Keystone(OSClient):
|
||||
raise exceptions.RallyException(_("Method 'keystone' is restricted "
|
||||
"for keystoneclient. :)"))
|
||||
|
||||
def _create_keystone_client(self, args, version=None):
|
||||
from keystoneclient.auth import identity
|
||||
from keystoneclient import client
|
||||
auth_arg_list = [
|
||||
"username", "project_name", "tenant_name", "auth_url",
|
||||
"password",
|
||||
]
|
||||
# NOTE(bigjools): If forcing a v2.0 URL then you cannot specify
|
||||
# domain-related info, or the service discovery will fail.
|
||||
if "v2.0" not in args["auth_url"] and version != "2":
|
||||
auth_arg_list.extend(
|
||||
["user_domain_name", "domain_name", "project_domain_name"])
|
||||
auth_args = {key: args.get(key) for key in auth_arg_list}
|
||||
auth = identity.Password(**auth_args)
|
||||
session = self._get_session(auth=auth)
|
||||
args["session"] = session
|
||||
# NOTE(bigjools): When using sessions, keystoneclient no longer
|
||||
# does any pre-auth and calling client.authenticate() with
|
||||
# sessions is deprecated (it's still possible to call it but if
|
||||
# endpoint is defined it'll crash). We're forcing that pre-auth
|
||||
# here because the use of the service_catalog depends on doing
|
||||
# this. Also note that while the API has got the
|
||||
# endpoints.list() equivalent, there is no service_type in that
|
||||
# list which is why we need to ensure service_catalog is still
|
||||
# present.
|
||||
auth_ref = auth.get_access(session)
|
||||
ks = client.Client(version=version, **args)
|
||||
ks.auth_ref = auth_ref
|
||||
return ks
|
||||
|
||||
def _remove_url_version(self):
|
||||
"""Remove any version from the auth_url.
|
||||
|
||||
@ -291,20 +273,32 @@ class Keystone(OSClient):
|
||||
If this object was constructed with a version in the api_info
|
||||
then that will be used unless the version parameter is passed.
|
||||
"""
|
||||
from keystoneclient import client
|
||||
|
||||
# Use the version in the api_info if provided, otherwise fall
|
||||
# back to the passed version (which may be None, in which case
|
||||
# keystoneclient chooses).
|
||||
|
||||
version = self.choose_version(version)
|
||||
new_kw = {
|
||||
"timeout": CONF.openstack_client_http_timeout,
|
||||
"insecure": self.credential.insecure,
|
||||
"cacert": self.credential.cacert
|
||||
}
|
||||
kw = self.credential.to_dict()
|
||||
kw.update(new_kw)
|
||||
|
||||
auth_url = self.credential.auth_url
|
||||
if version is not None:
|
||||
kw["auth_url"] = self._remove_url_version()
|
||||
return self._create_keystone_client(kw, version=version)
|
||||
auth_url = self._remove_url_version()
|
||||
sess, plugin = self._get_session(auth_url=auth_url, version=version)
|
||||
# NOTE(bigjools): When using sessions, keystoneclient no longer
|
||||
# does any pre-auth and calling client.authenticate() with
|
||||
# sessions is deprecated (it's still possible to call it but if
|
||||
# endpoint is defined it'll crash). We're forcing that pre-auth
|
||||
# here because the use of the service_catalog depends on doing
|
||||
# this. Also note that while the API has got the
|
||||
# endpoints.list() equivalent, there is no service_type in that
|
||||
# list which is why we need to ensure service_catalog is still
|
||||
# present.
|
||||
auth_ref = plugin.get_access(sess)
|
||||
ks = client.Client(version=version, session=sess,
|
||||
timeout=CONF.openstack_client_http_timeout)
|
||||
ks.auth_ref = auth_ref
|
||||
return ks
|
||||
|
||||
|
||||
@configure("nova", default_version="2", default_service_type="compute")
|
||||
@ -323,18 +317,15 @@ class Nova(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return nova client."""
|
||||
from novaclient import client as nova
|
||||
|
||||
kc = self.keystone()
|
||||
compute_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = nova.Client(self.choose_version(version),
|
||||
auth_token=kc.auth_token,
|
||||
http_log_debug=logging.is_debug(),
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
**self._get_auth_info(password_key="api_key"))
|
||||
client.set_management_url(compute_api_url)
|
||||
client.set_management_url(self._get_endpoint(service_type))
|
||||
return client
|
||||
|
||||
|
||||
@ -344,14 +335,11 @@ class Neutron(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return neutron client."""
|
||||
from neutronclient.neutron import client as neutron
|
||||
|
||||
kc = self.keystone()
|
||||
network_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = neutron.Client(self.choose_version(version),
|
||||
token=kc.auth_token,
|
||||
endpoint_url=network_api_url,
|
||||
endpoint_url=self._get_endpoint(service_type),
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
**self._get_auth_info(
|
||||
@ -366,13 +354,10 @@ class Glance(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return glance client."""
|
||||
import glanceclient as glance
|
||||
|
||||
kc = self.keystone()
|
||||
image_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = glance.Client(self.choose_version(version),
|
||||
endpoint=image_api_url,
|
||||
endpoint=self._get_endpoint(service_type),
|
||||
token=kc.auth_token,
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
@ -386,13 +371,10 @@ class Heat(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return heat client."""
|
||||
from heatclient import client as heat
|
||||
|
||||
kc = self.keystone()
|
||||
orchestration_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = heat.Client(self.choose_version(version),
|
||||
endpoint=orchestration_api_url,
|
||||
endpoint=self._get_endpoint(service_type),
|
||||
token=kc.auth_token,
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
@ -407,17 +389,14 @@ class Cinder(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return cinder client."""
|
||||
from cinderclient import client as cinder
|
||||
|
||||
client = cinder.Client(self.choose_version(version),
|
||||
http_log_debug=logging.is_debug(),
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
**self._get_auth_info(password_key="api_key"))
|
||||
kc = self.keystone()
|
||||
volume_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client.client.management_url = volume_api_url
|
||||
client.client.management_url = self._get_endpoint(service_type)
|
||||
client.client.auth_token = kc.auth_token
|
||||
return client
|
||||
|
||||
@ -437,10 +416,7 @@ class Manila(OSClient):
|
||||
**self._get_auth_info(password_key="api_key",
|
||||
project_name_key="project_name"))
|
||||
kc = self.keystone()
|
||||
manila_client.client.management_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
manila_client.client.management_url = self._get_endpoint(service_type)
|
||||
manila_client.client.auth_token = kc.auth_token
|
||||
return manila_client
|
||||
|
||||
@ -451,11 +427,8 @@ class Ceilometer(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return ceilometer client."""
|
||||
from ceilometerclient import client as ceilometer
|
||||
|
||||
kc = self.keystone()
|
||||
metering_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
auth_token = kc.auth_token
|
||||
if not hasattr(auth_token, "__call__"):
|
||||
# python-ceilometerclient requires auth_token to be a callable
|
||||
@ -463,7 +436,7 @@ class Ceilometer(OSClient):
|
||||
|
||||
client = ceilometer.get_client(
|
||||
self.choose_version(version),
|
||||
os_endpoint=metering_api_url,
|
||||
os_endpoint=self._get_endpoint(service_type),
|
||||
token=auth_token,
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
@ -480,8 +453,9 @@ class Gnocchi(OSClient):
|
||||
# NOTE(sumantmurke): gnocchiclient requires keystoneauth1 for
|
||||
# authenticating and creating a session.
|
||||
from gnocchiclient import client as gnocchi
|
||||
|
||||
service_type = self.choose_service_type(service_type)
|
||||
sess = self._get_keystoneauth_session()
|
||||
sess = self._get_session()[0]
|
||||
gclient = gnocchi.Client(version=self.choose_version(
|
||||
version), session=sess, service_type=service_type)
|
||||
return gclient
|
||||
@ -494,14 +468,11 @@ class Ironic(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return Ironic client."""
|
||||
from ironicclient import client as ironic
|
||||
|
||||
kc = self.keystone()
|
||||
baremetal_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = ironic.get_client(self.choose_version(version),
|
||||
os_auth_token=kc.auth_token,
|
||||
ironic_url=baremetal_api_url,
|
||||
ironic_url=self._get_endpoint(service_type),
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
cacert=self.credential.cacert)
|
||||
@ -525,6 +496,7 @@ class Sahara(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return Sahara client."""
|
||||
from saharaclient import client as sahara
|
||||
|
||||
client = sahara.Client(
|
||||
self.choose_version(version),
|
||||
service_type=self.choose_service_type(service_type),
|
||||
@ -547,10 +519,6 @@ class Zaqar(OSClient):
|
||||
"""Return Zaqar client."""
|
||||
from zaqarclient.queues import client as zaqar
|
||||
kc = self.keystone()
|
||||
messaging_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
conf = {"auth_opts": {"backend": "keystone", "options": {
|
||||
"os_username": self.credential.username,
|
||||
"os_password": self.credential.password,
|
||||
@ -559,7 +527,7 @@ class Zaqar(OSClient):
|
||||
"os_auth_url": self.credential.auth_url,
|
||||
"insecure": self.credential.insecure,
|
||||
}}}
|
||||
client = zaqar.Client(url=messaging_api_url,
|
||||
client = zaqar.Client(url=self._get_endpoint(),
|
||||
version=self.choose_version(version),
|
||||
conf=conf)
|
||||
return client
|
||||
@ -572,15 +540,10 @@ class Murano(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return Murano client."""
|
||||
from muranoclient import client as murano
|
||||
kc = self.keystone()
|
||||
murano_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name
|
||||
)
|
||||
|
||||
kc = self.keystone()
|
||||
client = murano.Client(self.choose_version(version),
|
||||
endpoint=murano_url,
|
||||
endpoint=self._get_endpoint(service_type),
|
||||
token=kc.auth_token)
|
||||
|
||||
return client
|
||||
@ -598,7 +561,7 @@ class Designate(OSClient):
|
||||
api_url = self._get_endpoint(service_type)
|
||||
api_url += "/v%s" % version
|
||||
|
||||
session = self._get_session(endpoint=api_url)
|
||||
session = self._get_session(auth_url=api_url)[0]
|
||||
return client.Client(version, session=session)
|
||||
|
||||
|
||||
@ -607,6 +570,7 @@ class Trove(OSClient):
|
||||
def create_client(self, version=None):
|
||||
"""Returns trove client."""
|
||||
from troveclient import client as trove
|
||||
|
||||
client = trove.Client(self.choose_version(version),
|
||||
region_name=self.credential.region_name,
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
@ -621,15 +585,10 @@ class Mistral(OSClient):
|
||||
def create_client(self, service_type=None):
|
||||
"""Return Mistral client."""
|
||||
from mistralclient.api import client
|
||||
|
||||
kc = self.keystone()
|
||||
|
||||
mistral_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
|
||||
client = client.client(
|
||||
mistral_url=mistral_url,
|
||||
mistral_url=self._get_endpoint(service_type),
|
||||
service_type=self.choose_service_type(service_type),
|
||||
auth_token=kc.auth_token)
|
||||
return client
|
||||
@ -640,13 +599,10 @@ class Swift(OSClient):
|
||||
def create_client(self, service_type=None):
|
||||
"""Return swift client."""
|
||||
from swiftclient import client as swift
|
||||
|
||||
kc = self.keystone()
|
||||
object_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = swift.Connection(retries=1,
|
||||
preauthurl=object_api_url,
|
||||
preauthurl=self._get_endpoint(service_type),
|
||||
preauthtoken=kc.auth_token,
|
||||
insecure=self.credential.insecure,
|
||||
cacert=self.credential.cacert,
|
||||
@ -661,6 +617,7 @@ class EC2(OSClient):
|
||||
def create_client(self):
|
||||
"""Return ec2 client."""
|
||||
import boto
|
||||
|
||||
kc = self.keystone()
|
||||
if kc.version != "v2.0":
|
||||
raise exceptions.RallyException(
|
||||
@ -668,12 +625,8 @@ class EC2(OSClient):
|
||||
"Keystone version 2"))
|
||||
ec2_credential = kc.ec2.create(user_id=kc.auth_user_id,
|
||||
tenant_id=kc.auth_tenant_id)
|
||||
ec2_api_url = kc.service_catalog.url_for(
|
||||
service_type=consts.ServiceType.EC2,
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
client = boto.connect_ec2_endpoint(
|
||||
url=ec2_api_url,
|
||||
url=self._get_endpoint(),
|
||||
aws_access_key_id=ec2_credential.access,
|
||||
aws_secret_access_key=ec2_credential.secret,
|
||||
is_secure=self.credential.insecure)
|
||||
@ -686,15 +639,12 @@ class Monasca(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return monasca client."""
|
||||
from monascaclient import client as monasca
|
||||
|
||||
kc = self.keystone()
|
||||
monitoring_api_url = kc.service_catalog.url_for(
|
||||
service_type=self.choose_service_type(service_type),
|
||||
endpoint_type=self.credential.endpoint_type,
|
||||
region_name=self.credential.region_name)
|
||||
auth_token = kc.auth_token
|
||||
client = monasca.Client(
|
||||
self.choose_version(version),
|
||||
monitoring_api_url,
|
||||
self._get_endpoint(service_type),
|
||||
token=auth_token,
|
||||
timeout=CONF.openstack_client_http_timeout,
|
||||
insecure=self.credential.insecure,
|
||||
@ -712,10 +662,10 @@ class Cue(OSClient):
|
||||
api_url = self._get_endpoint(service_type)
|
||||
api_url += "v%s" % version
|
||||
|
||||
session = self._get_session(endpoint=api_url)
|
||||
endpoint_type = self.credential.endpoint_type,
|
||||
session = self._get_session(auth_url=api_url)[0]
|
||||
endpoint_type = self.credential.endpoint_type
|
||||
|
||||
return cue.Client(session=session, interface=endpoint_type[0])
|
||||
return cue.Client(session=session, interface=endpoint_type)
|
||||
|
||||
|
||||
@configure("senlin", default_version="1", default_service_type="clustering",
|
||||
@ -724,6 +674,7 @@ class Senlin(OSClient):
|
||||
def create_client(self, version=None, service_type=None):
|
||||
"""Return senlin client."""
|
||||
from senlinclient import client as senlin
|
||||
|
||||
return senlin.Client(
|
||||
self.choose_version(version),
|
||||
**self._get_auth_info(project_name_key="project_name",
|
||||
@ -738,10 +689,10 @@ class Magnum(OSClient):
|
||||
from magnumclient import client as magnum
|
||||
|
||||
api_url = self._get_endpoint(service_type)
|
||||
session = self._get_session(endpoint=api_url)
|
||||
endpoint_type = self.credential.endpoint_type,
|
||||
session = self._get_session(auth_url=api_url)[0]
|
||||
endpoint_type = self.credential.endpoint_type
|
||||
|
||||
return magnum.Client(session=session, interface=endpoint_type[0])
|
||||
return magnum.Client(session=session, interface=endpoint_type)
|
||||
|
||||
|
||||
@configure("watcher", default_version="1", default_service_type="infra-optim",
|
||||
@ -835,50 +786,3 @@ class Clients(object):
|
||||
self.cache["services_data"] = services_data
|
||||
|
||||
return self.cache["services_data"]
|
||||
|
||||
@classmethod
|
||||
@logging.log_deprecated("Use rally.osclients.configure decorator instead.",
|
||||
"0.1.2")
|
||||
def register(cls, client_name):
|
||||
"""DEPRECATED!Decorator that adds new OpenStack client dynamically.
|
||||
|
||||
Use rally.osclients.configure decorator instead!
|
||||
|
||||
:param client_name: str name how client will be named in Rally clients
|
||||
|
||||
Decorated class will be added to Clients in runtime, so its sole
|
||||
argument is a Clients instance.
|
||||
|
||||
Decorated function will be added to Clients in runtime, so its sole
|
||||
argument is a Clients instance.
|
||||
|
||||
Example:
|
||||
>>> from rally import osclients
|
||||
>>> @osclients.Clients.register("supernova")
|
||||
... def another_nova_client(self):
|
||||
... from novaclient import client as nova
|
||||
... return nova.Client("2", auth_token=self.keystone().auth_token,
|
||||
... **self._get_auth_info(password_key="key"))
|
||||
...
|
||||
>>> clients = osclients.Clients.create_from_env()
|
||||
>>> clients.supernova().services.list()[:2]
|
||||
[<Service: nova-conductor>, <Service: nova-cert>]
|
||||
"""
|
||||
def wrap(client_func):
|
||||
try:
|
||||
OSClient.get(client_name)
|
||||
except exceptions.PluginNotFound:
|
||||
# everything is ok
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
_("Can not register client: name already exists: %s")
|
||||
% client_name)
|
||||
|
||||
@configure(client_name)
|
||||
class NewClient(OSClient):
|
||||
create_client = client_func
|
||||
|
||||
return NewClient
|
||||
|
||||
return wrap
|
||||
|
@ -1039,7 +1039,7 @@ class FakeKeystoneClient(object):
|
||||
self.auth_ref = mock.Mock()
|
||||
self.auth_ref.role_names = ["admin"]
|
||||
self.version = "v2.0"
|
||||
self.session = mock.Mock()
|
||||
self.session = mock.MagicMock()
|
||||
self.authenticate = mock.MagicMock()
|
||||
|
||||
def authenticate(self):
|
||||
|
@ -14,11 +14,9 @@
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
from keystoneclient.auth import token_endpoint
|
||||
from keystoneclient import exceptions as keystone_exceptions
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from testtools import matchers
|
||||
|
||||
from rally.common import objects
|
||||
from rally import consts
|
||||
@ -34,7 +32,51 @@ class DummyClient(osclients.OSClient):
|
||||
pass
|
||||
|
||||
|
||||
class OSClientTestCase(test.TestCase):
|
||||
class OSClientTestCaseUtils(object):
|
||||
|
||||
def set_up_keystone_mocks(self):
|
||||
self.ksc_module = mock.MagicMock()
|
||||
self.ksc_client = mock.MagicMock()
|
||||
self.ksc_identity = mock.MagicMock()
|
||||
self.ksc_identity_plugin = mock.MagicMock()
|
||||
self.ksc_password = mock.MagicMock(
|
||||
return_value=self.ksc_identity_plugin)
|
||||
self.ksc_auth = mock.MagicMock()
|
||||
|
||||
self.ksa_auth = mock.MagicMock()
|
||||
self.ksa_session = mock.MagicMock()
|
||||
self.patcher = mock.patch.dict("sys.modules",
|
||||
{"keystoneclient": self.ksc_module,
|
||||
"keystoneclient.auth": self.ksc_auth,
|
||||
"keystoneauth1": self.ksa_auth})
|
||||
self.patcher.start()
|
||||
self.addCleanup(self.patcher.stop)
|
||||
self.ksc_module.client = self.ksc_client
|
||||
self.ksc_auth.identity = self.ksc_identity
|
||||
self.ksc_auth.identity.Password = self.ksc_password
|
||||
self.ksa_auth.session = self.ksa_session
|
||||
|
||||
def make_auth_args(self):
|
||||
auth_kwargs = {
|
||||
"auth_url": "http://auth_url/", "username": "user",
|
||||
"password": "password", "tenant_name": "tenant",
|
||||
"domain_name": "domain", "project_name": "project_name",
|
||||
"project_domain_name": "project_domain_name",
|
||||
"user_domain_name": "user_domain_name",
|
||||
}
|
||||
kwargs = {"https_insecure": False, "https_cacert": None}
|
||||
kwargs.update(auth_kwargs)
|
||||
return auth_kwargs, kwargs
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class OSClientTestCase(test.TestCase, OSClientTestCaseUtils):
|
||||
|
||||
def setUp(self):
|
||||
super(OSClientTestCase, self).setUp()
|
||||
self.credential = objects.Credential("http://auth_url/v2.0", "user",
|
||||
"pass", "tenant")
|
||||
|
||||
def test_choose_service_type(self):
|
||||
default_service_type = "default_service_type"
|
||||
|
||||
@ -49,6 +91,45 @@ class OSClientTestCase(test.TestCase):
|
||||
self.assertEqual("foo",
|
||||
fake_client.choose_service_type("foo"))
|
||||
|
||||
@ddt.data(
|
||||
{"auth_url": "http://auth_url/v2.0"},
|
||||
{"auth_url": "http://auth_url/v3"},
|
||||
{"auth_url": "http://auth_url/"},
|
||||
{"auth_url": None},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test__get_session(self, auth_url):
|
||||
mock_keystoneauth1 = mock.MagicMock()
|
||||
self.set_up_keystone_mocks()
|
||||
osclient = osclients.OSClient(
|
||||
self.credential, {}, mock.MagicMock())
|
||||
with mock.patch.dict(
|
||||
"sys.modules", {
|
||||
"keystoneauth1": mock_keystoneauth1}):
|
||||
mock_keystoneauth1.discover.Discover.return_value = (
|
||||
mock.Mock(version_data=mock.Mock(return_value=[
|
||||
{"version": (1, 0)}]))
|
||||
)
|
||||
osclient._get_session(auth_url=auth_url)
|
||||
if auth_url == "http://auth_url/v2.0":
|
||||
self.ksc_password.assert_called_once_with(
|
||||
auth_url=auth_url, password="pass",
|
||||
tenant_name="tenant", username="user")
|
||||
elif auth_url is None:
|
||||
self.ksc_password.assert_called_once_with(
|
||||
auth_url="http://auth_url/v2.0", password="pass",
|
||||
tenant_name="tenant", username="user")
|
||||
else:
|
||||
self.ksc_password.assert_called_once_with(
|
||||
auth_url=auth_url, password="pass",
|
||||
tenant_name="tenant", username="user",
|
||||
domain_name=None, project_domain_name=None,
|
||||
user_domain_name=None)
|
||||
mock_keystoneauth1.session.Session.assert_has_calls(
|
||||
[mock.call(timeout=180.0, verify=True),
|
||||
mock.call(auth=self.ksc_identity_plugin, timeout=180.0,
|
||||
verify=True)])
|
||||
|
||||
|
||||
class CachedTestCase(test.TestCase):
|
||||
|
||||
@ -77,38 +158,14 @@ class CachedTestCase(test.TestCase):
|
||||
self.assertEqual({}, clients.cache)
|
||||
|
||||
|
||||
class TestCreateKeystoneClient(test.TestCase):
|
||||
class TestCreateKeystoneClient(test.TestCase, OSClientTestCaseUtils):
|
||||
|
||||
def make_auth_args(self):
|
||||
auth_kwargs = {
|
||||
"auth_url": "http://auth_url", "username": "user",
|
||||
"password": "password", "tenant_name": "tenant",
|
||||
"domain_name": "domain", "project_name": "project_name",
|
||||
"project_domain_name": "project_domain_name",
|
||||
"user_domain_name": "user_domain_name",
|
||||
}
|
||||
kwargs = {"https_insecure": False, "https_cacert": None}
|
||||
kwargs.update(auth_kwargs)
|
||||
return auth_kwargs, kwargs
|
||||
def setUp(self):
|
||||
super(TestCreateKeystoneClient, self).setUp()
|
||||
self.credential = objects.Credential("http://auth_url/v2.0", "user",
|
||||
"pass", "tenant")
|
||||
|
||||
def set_up_keystone_mocks(self):
|
||||
self.ksc_module = mock.MagicMock()
|
||||
self.ksc_client = mock.MagicMock()
|
||||
self.ksc_identity = mock.MagicMock()
|
||||
self.ksc_password = mock.MagicMock()
|
||||
self.ksc_session = mock.MagicMock()
|
||||
self.ksc_auth = mock.MagicMock()
|
||||
self.patcher = mock.patch.dict("sys.modules",
|
||||
{"keystoneclient": self.ksc_module,
|
||||
"keystoneclient.auth": self.ksc_auth})
|
||||
self.patcher.start()
|
||||
self.addCleanup(self.patcher.stop)
|
||||
self.ksc_module.client = self.ksc_client
|
||||
self.ksc_auth.identity = self.ksc_identity
|
||||
self.ksc_auth.identity.Password = self.ksc_password
|
||||
self.ksc_module.session = self.ksc_session
|
||||
|
||||
def test_create_keystone_client(self):
|
||||
def test_create_client(self):
|
||||
# NOTE(bigjools): This is a very poor testing strategy as it
|
||||
# tightly couples the test implementation to the tested
|
||||
# function's implementation. Ideally, we'd use a fake keystone
|
||||
@ -117,33 +174,31 @@ class TestCreateKeystoneClient(test.TestCase):
|
||||
# client. Hopefully one day we'll get a real fake from the
|
||||
# keystone guys.
|
||||
self.set_up_keystone_mocks()
|
||||
auth_kwargs, all_kwargs = self.make_auth_args()
|
||||
keystone = osclients.Keystone(
|
||||
mock.MagicMock(), mock.sentinel, mock.sentinel)
|
||||
client = keystone._create_keystone_client(all_kwargs)
|
||||
keystone = osclients.Keystone(self.credential, {}, mock.MagicMock())
|
||||
keystone._get_session = mock.Mock(
|
||||
return_value=(self.ksa_session, self.ksc_identity_plugin,))
|
||||
self.ksc_identity_plugin.get_access = mock.Mock(
|
||||
return_value="fake_auth_ref")
|
||||
client = keystone.create_client(version=3)
|
||||
|
||||
self.ksc_password.assert_called_once_with(**auth_kwargs)
|
||||
self.ksc_session.Session.assert_called_once_with(
|
||||
auth=self.ksc_identity.Password(), timeout=mock.ANY,
|
||||
verify=mock.ANY)
|
||||
kwargs_session = self.credential.to_dict()
|
||||
kwargs_session.update({
|
||||
"auth_url": "http://auth_url/",
|
||||
"session": self.ksa_session,
|
||||
"timeout": 180.0})
|
||||
keystone._get_session.assert_called_once_with(
|
||||
auth_url="http://auth_url/", version="3")
|
||||
self.ksc_identity_plugin.get_access.assert_called_once_with(
|
||||
self.ksa_session)
|
||||
self.ksc_client.Client.assert_called_once_with(
|
||||
version=None, **all_kwargs)
|
||||
session=self.ksa_session, timeout=180.0, version="3")
|
||||
self.assertIs(client, self.ksc_client.Client())
|
||||
|
||||
def test_client_is_pre_authed(self):
|
||||
self.assertEqual("fake_auth_ref", self.ksc_client.Client().auth_ref)
|
||||
# The client needs to be pre-authed so that service_catalog
|
||||
# works. This is because when using sessions, lazy auth is done
|
||||
# in keystoneclient.
|
||||
self.set_up_keystone_mocks()
|
||||
_, all_kwargs = self.make_auth_args()
|
||||
keystone = osclients.Keystone(
|
||||
mock.MagicMock(), mock.sentinel, mock.sentinel)
|
||||
client = keystone._create_keystone_client(all_kwargs)
|
||||
auth_ref = getattr(client, "auth_ref", None)
|
||||
self.assertIsNot(auth_ref, None)
|
||||
self.ksc_client.Client.assert_called_once_with(
|
||||
version=None, **all_kwargs)
|
||||
self.assertIs(client, self.ksc_client.Client())
|
||||
|
||||
def test_create_client_removes_url_path_if_version_specified(self):
|
||||
# If specifying a version on the client creation call, ensure
|
||||
@ -151,62 +206,17 @@ class TestCreateKeystoneClient(test.TestCase):
|
||||
# into the Client() call.
|
||||
self.set_up_keystone_mocks()
|
||||
auth_kwargs, all_kwargs = self.make_auth_args()
|
||||
credential = objects.Credential(
|
||||
"http://auth_url/v2.0", "user", "pass", "tenant")
|
||||
keystone = osclients.Keystone(
|
||||
credential, {}, mock.MagicMock())
|
||||
self.credential, {}, mock.MagicMock())
|
||||
keystone._get_session = mock.Mock(
|
||||
return_value=(self.ksa_session, self.ksc_identity_plugin,))
|
||||
client = keystone.create_client(version="3")
|
||||
|
||||
self.assertIs(client, self.ksc_client.Client())
|
||||
called_with = self.ksc_client.Client.call_args_list[0][1]
|
||||
self.expectThat(
|
||||
called_with["auth_url"], matchers.Equals("http://auth_url/"))
|
||||
self.expectThat(called_with["version"], matchers.Equals("3"))
|
||||
|
||||
def test_create_keystone_client_with_v2_url_omits_domain(self):
|
||||
# NOTE(bigjools): Test that domain-related info is not present
|
||||
# when forcing a v2 URL, because it breaks keystoneclient's
|
||||
# service discovery.
|
||||
self.set_up_keystone_mocks()
|
||||
auth_kwargs, all_kwargs = self.make_auth_args()
|
||||
|
||||
all_kwargs["auth_url"] = "http://auth_url/v2.0"
|
||||
auth_kwargs["auth_url"] = all_kwargs["auth_url"]
|
||||
keystone = osclients.Keystone(
|
||||
mock.MagicMock(), mock.sentinel, mock.sentinel)
|
||||
client = keystone._create_keystone_client(all_kwargs)
|
||||
|
||||
auth_kwargs.pop("user_domain_name")
|
||||
auth_kwargs.pop("project_domain_name")
|
||||
auth_kwargs.pop("domain_name")
|
||||
self.ksc_password.assert_called_once_with(**auth_kwargs)
|
||||
self.ksc_session.Session.assert_called_once_with(
|
||||
auth=self.ksc_identity.Password(), timeout=mock.ANY,
|
||||
verify=mock.ANY)
|
||||
self.ksc_client.Client.assert_called_once_with(
|
||||
version=None, **all_kwargs)
|
||||
self.assertIs(client, self.ksc_client.Client())
|
||||
|
||||
def test_create_keystone_client_with_v2_version_omits_domain(self):
|
||||
self.set_up_keystone_mocks()
|
||||
auth_kwargs, all_kwargs = self.make_auth_args()
|
||||
|
||||
all_kwargs["auth_url"] = "http://auth_url/"
|
||||
auth_kwargs["auth_url"] = all_kwargs["auth_url"]
|
||||
keystone = osclients.Keystone(
|
||||
mock.MagicMock(), mock.sentinel, mock.sentinel)
|
||||
client = keystone._create_keystone_client(all_kwargs, version="2")
|
||||
|
||||
auth_kwargs.pop("user_domain_name")
|
||||
auth_kwargs.pop("project_domain_name")
|
||||
auth_kwargs.pop("domain_name")
|
||||
self.ksc_password.assert_called_once_with(**auth_kwargs)
|
||||
self.ksc_session.Session.assert_called_once_with(
|
||||
auth=self.ksc_identity.Password(), timeout=mock.ANY,
|
||||
verify=mock.ANY)
|
||||
self.ksc_client.Client.assert_called_once_with(
|
||||
version="2", **all_kwargs)
|
||||
self.assertIs(client, self.ksc_client.Client())
|
||||
self.assertEqual(
|
||||
{"session": self.ksa_session, "timeout": 180.0, "version": "3"},
|
||||
called_with)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@ -214,7 +224,7 @@ class OSClientsTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(OSClientsTestCase, self).setUp()
|
||||
self.credential = objects.Credential("http://auth_url/v2.0", "use",
|
||||
self.credential = objects.Credential("http://auth_url/v2.0", "user",
|
||||
"pass", "tenant")
|
||||
self.clients = osclients.Clients(self.credential, {})
|
||||
|
||||
@ -224,7 +234,7 @@ class OSClientsTestCase(test.TestCase):
|
||||
self.service_catalog.url_for = mock.MagicMock()
|
||||
|
||||
keystone_patcher = mock.patch(
|
||||
"rally.osclients.Keystone._create_keystone_client")
|
||||
"rally.osclients.Keystone.create_client")
|
||||
self.mock_create_keystone_client = keystone_patcher.start()
|
||||
self.addCleanup(keystone_patcher.stop)
|
||||
self.mock_create_keystone_client.return_value = self.fake_keystone
|
||||
@ -247,69 +257,6 @@ class OSClientsTestCase(test.TestCase):
|
||||
self.assertEqual("foo_tenant_name", clients.credential.tenant_name)
|
||||
self.assertEqual("foo_region_name", clients.credential.region_name)
|
||||
|
||||
@mock.patch.object(DummyClient, "_get_endpoint")
|
||||
@mock.patch("keystoneclient.session.Session")
|
||||
def test_get_session(self, mock_session, mock_dummy_client__get_endpoint):
|
||||
# Use DummyClient since if not the abc meta kicks in
|
||||
osc = DummyClient(self.credential, {}, {})
|
||||
|
||||
with mock.patch.object(token_endpoint, "Token") as token:
|
||||
osc._get_session()
|
||||
|
||||
token.assert_called_once_with(
|
||||
mock_dummy_client__get_endpoint.return_value,
|
||||
self.fake_keystone.auth_token
|
||||
)
|
||||
mock_session.assert_called_once_with(
|
||||
auth=token.return_value, verify=not self.credential.insecure,
|
||||
timeout=cfg.CONF.openstack_client_http_timeout)
|
||||
|
||||
@mock.patch.object(DummyClient, "_get_endpoint")
|
||||
@mock.patch("keystoneclient.session.Session")
|
||||
def test_get_session_with_endpoint(
|
||||
self, mock_session, mock_dummy_client__get_endpoint):
|
||||
# Use DummyClient since if not the abc meta kicks in
|
||||
osc = DummyClient(self.credential, {}, {})
|
||||
|
||||
fake_endpoint = mock.Mock()
|
||||
with mock.patch.object(token_endpoint, "Token") as token:
|
||||
osc._get_session(endpoint=fake_endpoint)
|
||||
|
||||
self.assertFalse(mock_dummy_client__get_endpoint.called)
|
||||
|
||||
token.assert_called_once_with(
|
||||
fake_endpoint,
|
||||
self.fake_keystone.auth_token
|
||||
)
|
||||
mock_session.assert_called_once_with(
|
||||
auth=token.return_value, verify=not self.credential.insecure,
|
||||
timeout=cfg.CONF.openstack_client_http_timeout)
|
||||
|
||||
@mock.patch("keystoneclient.session.Session")
|
||||
def test_get_session_with_auth(self, mock_session):
|
||||
# Use DummyClient since if not the abc meta kicks in
|
||||
osc = DummyClient(self.credential, {}, {})
|
||||
|
||||
fake_auth = mock.Mock()
|
||||
osc._get_session(auth=fake_auth)
|
||||
|
||||
mock_session.assert_called_once_with(
|
||||
auth=fake_auth, verify=not self.credential.insecure,
|
||||
timeout=cfg.CONF.openstack_client_http_timeout)
|
||||
|
||||
@mock.patch("keystoneclient.session.Session")
|
||||
def test_get_session_with_ca(self, mock_session):
|
||||
# Use DummyClient since if not the abc meta kicks in
|
||||
osc = DummyClient(self.credential, {}, {})
|
||||
|
||||
self.credential.cacert = "/fake/ca"
|
||||
fake_auth = mock.Mock()
|
||||
osc._get_session(auth=fake_auth)
|
||||
|
||||
mock_session.assert_called_once_with(
|
||||
auth=fake_auth, verify="/fake/ca",
|
||||
timeout=cfg.CONF.openstack_client_http_timeout)
|
||||
|
||||
def test_keystone(self):
|
||||
self.assertNotIn("keystone", self.clients.cache)
|
||||
client = self.clients.keystone()
|
||||
@ -317,9 +264,8 @@ class OSClientsTestCase(test.TestCase):
|
||||
credential = {"timeout": cfg.CONF.openstack_client_http_timeout,
|
||||
"insecure": False, "cacert": None}
|
||||
kwargs = self.credential.to_dict()
|
||||
kwargs.update(credential.items())
|
||||
self.mock_create_keystone_client.assert_called_once_with(
|
||||
kwargs, version=None)
|
||||
kwargs.update(credential)
|
||||
self.mock_create_keystone_client.assert_called_once_with()
|
||||
self.assertEqual(self.fake_keystone, self.clients.cache["keystone"])
|
||||
|
||||
@mock.patch("rally.osclients.Keystone.create_client")
|
||||
@ -515,6 +461,10 @@ class OSClientsTestCase(test.TestCase):
|
||||
with mock.patch.dict("sys.modules",
|
||||
{"gnocchiclient": mock_gnocchi,
|
||||
"keystoneauth1": mock_keystoneauth1}):
|
||||
mock_keystoneauth1.discover.Discover.return_value = (
|
||||
mock.Mock(version_data=mock.Mock(return_value=[
|
||||
{"version": (1, 0)}]))
|
||||
)
|
||||
client = self.clients.gnocchi()
|
||||
|
||||
self.assertEqual(fake_gnocchi, client)
|
||||
@ -691,23 +641,21 @@ class OSClientsTestCase(test.TestCase):
|
||||
mock_swift.client.Connection.assert_called_once_with(**kw)
|
||||
self.assertEqual(self.clients.cache["swift"], fake_swift)
|
||||
|
||||
def test_ec2(self):
|
||||
@mock.patch("rally.osclients.EC2._get_endpoint")
|
||||
def test_ec2(self, mock_ec2__get_endpoint):
|
||||
mock_boto = mock.Mock()
|
||||
self.service_catalog.url_for.return_value = "http://fake.to:1/fake"
|
||||
self.fake_keystone.ec2 = mock.Mock()
|
||||
self.fake_keystone.ec2.create.return_value = mock.Mock(
|
||||
access="fake_access", secret="fake_secret")
|
||||
mock_ec2__get_endpoint.return_value = "http://fake.to:1/fake"
|
||||
fake_ec2 = fakes.FakeEC2Client()
|
||||
mock_boto.connect_ec2_endpoint.return_value = fake_ec2
|
||||
|
||||
self.assertNotIn("ec2", self.clients.cache)
|
||||
with mock.patch.dict("sys.modules", {"boto": mock_boto}):
|
||||
client = self.clients.ec2()
|
||||
|
||||
self.assertEqual(fake_ec2, client)
|
||||
self.service_catalog.url_for.assert_called_once_with(
|
||||
service_type="ec2",
|
||||
endpoint_type=consts.EndpointType.PUBLIC,
|
||||
region_name=self.credential.region_name)
|
||||
kw = {
|
||||
"url": "http://fake.to:1/fake",
|
||||
"aws_access_key_id": "fake_access",
|
||||
@ -764,7 +712,8 @@ class OSClientsTestCase(test.TestCase):
|
||||
mock_designate = mock.Mock()
|
||||
mock_designate.client.Client.return_value = fake_designate
|
||||
|
||||
mock_designate__get_session.return_value = self.fake_keystone.session
|
||||
mock_designate__get_session.return_value = ("fake_session",
|
||||
"fake_auth_plugin")
|
||||
|
||||
self.assertNotIn("designate", self.clients.cache)
|
||||
with mock.patch.dict("sys.modules",
|
||||
@ -787,10 +736,10 @@ class OSClientsTestCase(test.TestCase):
|
||||
url.__iadd__.assert_called_once_with("/v%s" % default)
|
||||
|
||||
mock_designate__get_session.assert_called_once_with(
|
||||
endpoint=url.__iadd__.return_value)
|
||||
auth_url=url.__iadd__.return_value)
|
||||
|
||||
mock_designate.client.Client.assert_called_once_with(
|
||||
default, session=self.fake_keystone.session)
|
||||
default, session="fake_session")
|
||||
|
||||
key = "designate"
|
||||
if version is not None:
|
||||
@ -802,9 +751,8 @@ class OSClientsTestCase(test.TestCase):
|
||||
fake_cue = fakes.FakeCueClient()
|
||||
mock_cue = mock.MagicMock()
|
||||
mock_cue.client.Client = mock.MagicMock(return_value=fake_cue)
|
||||
|
||||
mock_cue__get_session.return_value = self.fake_keystone.session
|
||||
|
||||
mock_cue__get_session.return_value = ("fake_session",
|
||||
"fake_auth_plugin")
|
||||
self.assertNotIn("cue", self.clients.cache)
|
||||
with mock.patch.dict("sys.modules", {"cueclient": mock_cue,
|
||||
"cueclient.v1": mock_cue}):
|
||||
@ -812,7 +760,7 @@ class OSClientsTestCase(test.TestCase):
|
||||
self.assertEqual(fake_cue, client)
|
||||
mock_cue.client.Client.assert_called_once_with(
|
||||
interface=consts.EndpointType.PUBLIC,
|
||||
session=self.fake_keystone.session)
|
||||
session="fake_session")
|
||||
self.assertEqual(fake_cue, self.clients.cache["cue"])
|
||||
|
||||
def test_senlin(self):
|
||||
@ -838,7 +786,8 @@ class OSClientsTestCase(test.TestCase):
|
||||
mock_magnum = mock.MagicMock()
|
||||
mock_magnum.client.Client.return_value = fake_magnum
|
||||
|
||||
mock_magnum__get_session.return_value = self.fake_keystone.session
|
||||
mock_magnum__get_session.return_value = (self.fake_keystone.session,
|
||||
"fake_auth_plugin")
|
||||
|
||||
self.assertNotIn("magnum", self.clients.cache)
|
||||
with mock.patch.dict("sys.modules", {"magnumclient": mock_magnum}):
|
||||
|
Loading…
Reference in New Issue
Block a user