Add auth_plugin support to cinderclient
With CINDER_RAX_AUTH being rightfully removed, cinderclient is no longer compatible with Rackspace/any non-keystone auth. To fix this, I stole auth_system/auth_plugin from novaclient's implementation. See https://review.openstack.org/#/c/23820/. Change-Id: If5f84003f868ef02bb7eb7da67cf62018602e8f0 Closes-Bug: 1280393
This commit is contained in:
parent
6fd8d8e12e
commit
d5334aa929
143
cinderclient/auth_plugin.py
Normal file
143
cinderclient/auth_plugin.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# Copyright 2013 OpenStack Foundation
|
||||||
|
# Copyright 2013 Spanish National Research Council.
|
||||||
|
# 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 logging
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from cinderclient import exceptions
|
||||||
|
from cinderclient import utils
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
_discovered_plugins = {}
|
||||||
|
|
||||||
|
|
||||||
|
def discover_auth_systems():
|
||||||
|
"""Discover the available auth-systems.
|
||||||
|
|
||||||
|
This won't take into account the old style auth-systems.
|
||||||
|
"""
|
||||||
|
ep_name = 'openstack.client.auth_plugin'
|
||||||
|
for ep in pkg_resources.iter_entry_points(ep_name):
|
||||||
|
try:
|
||||||
|
auth_plugin = ep.load()
|
||||||
|
except (ImportError, pkg_resources.UnknownExtra, AttributeError) as e:
|
||||||
|
logger.debug("ERROR: Cannot load auth plugin %s" % ep.name)
|
||||||
|
logger.debug(e, exc_info=1)
|
||||||
|
else:
|
||||||
|
_discovered_plugins[ep.name] = auth_plugin
|
||||||
|
|
||||||
|
|
||||||
|
def load_auth_system_opts(parser):
|
||||||
|
"""Load options needed by the available auth-systems into a parser.
|
||||||
|
|
||||||
|
This function will try to populate the parser with options from the
|
||||||
|
available plugins.
|
||||||
|
"""
|
||||||
|
for name, auth_plugin in six.iteritems(_discovered_plugins):
|
||||||
|
add_opts_fn = getattr(auth_plugin, "add_opts", None)
|
||||||
|
if add_opts_fn:
|
||||||
|
group = parser.add_argument_group("Auth-system '%s' options" %
|
||||||
|
name)
|
||||||
|
add_opts_fn(group)
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugin(auth_system):
|
||||||
|
if auth_system in _discovered_plugins:
|
||||||
|
return _discovered_plugins[auth_system]()
|
||||||
|
|
||||||
|
# NOTE(aloga): If we arrive here, the plugin will be an old-style one,
|
||||||
|
# so we have to create a fake AuthPlugin for it.
|
||||||
|
return DeprecatedAuthPlugin(auth_system)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAuthPlugin(object):
|
||||||
|
"""Base class for authentication plugins.
|
||||||
|
|
||||||
|
An authentication plugin needs to override at least the authenticate
|
||||||
|
method to be a valid plugin.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.opts = {}
|
||||||
|
|
||||||
|
def get_auth_url(self):
|
||||||
|
"""Return the auth url for the plugin (if any)."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_opts(parser):
|
||||||
|
"""Populate and return the parser with the options for this plugin.
|
||||||
|
|
||||||
|
If the plugin does not need any options, it should return the same
|
||||||
|
parser untouched.
|
||||||
|
"""
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def parse_opts(self, args):
|
||||||
|
"""Parse the actual auth-system options if any.
|
||||||
|
|
||||||
|
This method is expected to populate the attribute self.opts with a
|
||||||
|
dict containing the options and values needed to make authentication.
|
||||||
|
If the dict is empty, the client should assume that it needs the same
|
||||||
|
options as the 'keystone' auth system (i.e. os_username and
|
||||||
|
os_password).
|
||||||
|
|
||||||
|
Returns the self.opts dict.
|
||||||
|
"""
|
||||||
|
return self.opts
|
||||||
|
|
||||||
|
def authenticate(self, cls, auth_url):
|
||||||
|
"""Authenticate using plugin defined method."""
|
||||||
|
raise exceptions.AuthSystemNotFound(self.auth_system)
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecatedAuthPlugin(object):
|
||||||
|
"""Class to mimic the AuthPlugin class for deprecated auth systems.
|
||||||
|
|
||||||
|
Old auth systems only define two entry points: openstack.client.auth_url
|
||||||
|
and openstack.client.authenticate. This class will load those entry points
|
||||||
|
into a class similar to a valid AuthPlugin.
|
||||||
|
"""
|
||||||
|
def __init__(self, auth_system):
|
||||||
|
self.auth_system = auth_system
|
||||||
|
|
||||||
|
def authenticate(cls, auth_url):
|
||||||
|
raise exceptions.AuthSystemNotFound(self.auth_system)
|
||||||
|
|
||||||
|
self.opts = {}
|
||||||
|
|
||||||
|
self.get_auth_url = lambda: None
|
||||||
|
self.authenticate = authenticate
|
||||||
|
|
||||||
|
self._load_endpoints()
|
||||||
|
|
||||||
|
def _load_endpoints(self):
|
||||||
|
ep_name = 'openstack.client.auth_url'
|
||||||
|
fn = utils._load_entry_point(ep_name, name=self.auth_system)
|
||||||
|
if fn:
|
||||||
|
self.get_auth_url = fn
|
||||||
|
|
||||||
|
ep_name = 'openstack.client.authenticate'
|
||||||
|
fn = utils._load_entry_point(ep_name, name=self.auth_system)
|
||||||
|
if fn:
|
||||||
|
self.authenticate = fn
|
||||||
|
|
||||||
|
def parse_opts(self, args):
|
||||||
|
return self.opts
|
@ -54,16 +54,26 @@ class HTTPClient(object):
|
|||||||
|
|
||||||
USER_AGENT = 'python-cinderclient'
|
USER_AGENT = 'python-cinderclient'
|
||||||
|
|
||||||
def __init__(self, user, password, projectid, auth_url, insecure=False,
|
def __init__(self, user, password, projectid, auth_url=None,
|
||||||
timeout=None, tenant_id=None, proxy_tenant_id=None,
|
insecure=False, timeout=None, tenant_id=None,
|
||||||
proxy_token=None, region_name=None,
|
proxy_tenant_id=None, proxy_token=None, region_name=None,
|
||||||
endpoint_type='publicURL', service_type=None,
|
endpoint_type='publicURL', service_type=None,
|
||||||
service_name=None, volume_service_name=None, retries=None,
|
service_name=None, volume_service_name=None, retries=None,
|
||||||
http_log_debug=False, cacert=None):
|
http_log_debug=False, cacert=None,
|
||||||
|
auth_system='keystone', auth_plugin=None):
|
||||||
self.user = user
|
self.user = user
|
||||||
self.password = password
|
self.password = password
|
||||||
self.projectid = projectid
|
self.projectid = projectid
|
||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
|
|
||||||
|
if auth_system and auth_system != 'keystone' and not auth_plugin:
|
||||||
|
raise exceptions.AuthSystemNotFound(auth_system)
|
||||||
|
|
||||||
|
if not auth_url and auth_system and auth_system != 'keystone':
|
||||||
|
auth_url = auth_plugin.get_auth_url()
|
||||||
|
if not auth_url:
|
||||||
|
raise exceptions.EndpointNotFound()
|
||||||
|
|
||||||
self.auth_url = auth_url.rstrip('/')
|
self.auth_url = auth_url.rstrip('/')
|
||||||
self.version = 'v1'
|
self.version = 'v1'
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
@ -88,6 +98,9 @@ class HTTPClient(object):
|
|||||||
else:
|
else:
|
||||||
self.verify_cert = True
|
self.verify_cert = True
|
||||||
|
|
||||||
|
self.auth_system = auth_system
|
||||||
|
self.auth_plugin = auth_plugin
|
||||||
|
|
||||||
self._logger = logging.getLogger(__name__)
|
self._logger = logging.getLogger(__name__)
|
||||||
if self.http_log_debug and not self._logger.handlers:
|
if self.http_log_debug and not self._logger.handlers:
|
||||||
ch = logging.StreamHandler()
|
ch = logging.StreamHandler()
|
||||||
@ -295,7 +308,10 @@ class HTTPClient(object):
|
|||||||
auth_url = self.auth_url
|
auth_url = self.auth_url
|
||||||
if self.version == "v2.0":
|
if self.version == "v2.0":
|
||||||
while auth_url:
|
while auth_url:
|
||||||
auth_url = self._v2_auth(auth_url)
|
if not self.auth_system or self.auth_system == 'keystone':
|
||||||
|
auth_url = self._v2_auth(auth_url)
|
||||||
|
else:
|
||||||
|
auth_url = self._plugin_auth(auth_url)
|
||||||
|
|
||||||
# Are we acting on behalf of another user via an
|
# Are we acting on behalf of another user via an
|
||||||
# existing token? If so, our actual endpoints may
|
# existing token? If so, our actual endpoints may
|
||||||
@ -341,6 +357,9 @@ class HTTPClient(object):
|
|||||||
else:
|
else:
|
||||||
raise exceptions.from_response(resp, body)
|
raise exceptions.from_response(resp, body)
|
||||||
|
|
||||||
|
def _plugin_auth(self, auth_url):
|
||||||
|
return self.auth_plugin.authenticate(self, auth_url)
|
||||||
|
|
||||||
def _v2_auth(self, url):
|
def _v2_auth(self, url):
|
||||||
"""Authenticate against a v2.0 auth service."""
|
"""Authenticate against a v2.0 auth service."""
|
||||||
body = {"auth": {
|
body = {"auth": {
|
||||||
|
@ -41,6 +41,15 @@ class NoUniqueMatch(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthSystemNotFound(Exception):
|
||||||
|
"""When the user specify a AuthSystem but not installed."""
|
||||||
|
def __init__(self, auth_system):
|
||||||
|
self.auth_system = auth_system
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "AuthSystemNotFound: %s" % repr(self.auth_system)
|
||||||
|
|
||||||
|
|
||||||
class NoTokenLookupException(Exception):
|
class NoTokenLookupException(Exception):
|
||||||
"""This form of authentication does not support looking up
|
"""This form of authentication does not support looking up
|
||||||
endpoints from an existing token.
|
endpoints from an existing token.
|
||||||
|
@ -29,6 +29,7 @@ import pkgutil
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import cinderclient.auth_plugin
|
||||||
from cinderclient import client
|
from cinderclient import client
|
||||||
from cinderclient import exceptions as exc
|
from cinderclient import exceptions as exc
|
||||||
import cinderclient.extension
|
import cinderclient.extension
|
||||||
@ -142,6 +143,13 @@ class OpenStackCinderShell(object):
|
|||||||
parser.add_argument('--os_region_name',
|
parser.add_argument('--os_region_name',
|
||||||
help=argparse.SUPPRESS)
|
help=argparse.SUPPRESS)
|
||||||
|
|
||||||
|
parser.add_argument('--os-auth-system',
|
||||||
|
metavar='<auth-system>',
|
||||||
|
default=utils.env('OS_AUTH_SYSTEM'),
|
||||||
|
help='Defaults to env[OS_AUTH_SYSTEM].')
|
||||||
|
parser.add_argument('--os_auth_system',
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
|
|
||||||
parser.add_argument('--service-type',
|
parser.add_argument('--service-type',
|
||||||
metavar='<service-type>',
|
metavar='<service-type>',
|
||||||
help='Defaults to volume for most actions')
|
help='Defaults to volume for most actions')
|
||||||
@ -225,6 +233,10 @@ class OpenStackCinderShell(object):
|
|||||||
default=utils.env('CINDER_URL'),
|
default=utils.env('CINDER_URL'),
|
||||||
help=argparse.SUPPRESS)
|
help=argparse.SUPPRESS)
|
||||||
|
|
||||||
|
# The auth-system-plugins might require some extra options
|
||||||
|
cinderclient.auth_plugin.discover_auth_systems()
|
||||||
|
cinderclient.auth_plugin.load_auth_system_opts(parser)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def get_subcommand_parser(self, version):
|
def get_subcommand_parser(self, version):
|
||||||
@ -372,7 +384,8 @@ class OpenStackCinderShell(object):
|
|||||||
(os_username, os_password, os_tenant_name, os_auth_url,
|
(os_username, os_password, os_tenant_name, os_auth_url,
|
||||||
os_region_name, os_tenant_id, endpoint_type, insecure,
|
os_region_name, os_tenant_id, endpoint_type, insecure,
|
||||||
service_type, service_name, volume_service_name,
|
service_type, service_name, volume_service_name,
|
||||||
username, apikey, projectid, url, region_name, cacert) = (
|
username, apikey, projectid, url, region_name, cacert,
|
||||||
|
os_auth_system) = (
|
||||||
args.os_username, args.os_password,
|
args.os_username, args.os_password,
|
||||||
args.os_tenant_name, args.os_auth_url,
|
args.os_tenant_name, args.os_auth_url,
|
||||||
args.os_region_name, args.os_tenant_id,
|
args.os_region_name, args.os_tenant_id,
|
||||||
@ -380,7 +393,13 @@ class OpenStackCinderShell(object):
|
|||||||
args.service_type, args.service_name,
|
args.service_type, args.service_name,
|
||||||
args.volume_service_name, args.username,
|
args.volume_service_name, args.username,
|
||||||
args.apikey, args.projectid,
|
args.apikey, args.projectid,
|
||||||
args.url, args.region_name, args.os_cacert)
|
args.url, args.region_name, args.os_cacert,
|
||||||
|
args.os_auth_system)
|
||||||
|
|
||||||
|
if os_auth_system and os_auth_system != "keystone":
|
||||||
|
auth_plugin = cinderclient.auth_plugin.load_plugin(os_auth_system)
|
||||||
|
else:
|
||||||
|
auth_plugin = None
|
||||||
|
|
||||||
if not endpoint_type:
|
if not endpoint_type:
|
||||||
endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE
|
endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE
|
||||||
@ -393,13 +412,17 @@ class OpenStackCinderShell(object):
|
|||||||
# for os_username or os_password but for compatibility it is not.
|
# for os_username or os_password but for compatibility it is not.
|
||||||
|
|
||||||
if not utils.isunauthenticated(args.func):
|
if not utils.isunauthenticated(args.func):
|
||||||
if not os_username:
|
if auth_plugin:
|
||||||
if not username:
|
auth_plugin.parse_opts(args)
|
||||||
raise exc.CommandError(
|
|
||||||
"You must provide a username "
|
if not auth_plugin or not auth_plugin.opts:
|
||||||
"via either --os-username or env[OS_USERNAME]")
|
if not os_username:
|
||||||
else:
|
if not username:
|
||||||
os_username = username
|
raise exc.CommandError(
|
||||||
|
"You must provide a username "
|
||||||
|
"via either --os-username or env[OS_USERNAME]")
|
||||||
|
else:
|
||||||
|
os_username = username
|
||||||
|
|
||||||
if not os_password:
|
if not os_password:
|
||||||
if not apikey:
|
if not apikey:
|
||||||
@ -417,6 +440,10 @@ class OpenStackCinderShell(object):
|
|||||||
else:
|
else:
|
||||||
os_tenant_name = projectid
|
os_tenant_name = projectid
|
||||||
|
|
||||||
|
if not os_auth_url:
|
||||||
|
if os_auth_system and os_auth_system != 'keystone':
|
||||||
|
os_auth_url = auth_plugin.get_auth_url()
|
||||||
|
|
||||||
if not os_auth_url:
|
if not os_auth_url:
|
||||||
if not url:
|
if not url:
|
||||||
raise exc.CommandError(
|
raise exc.CommandError(
|
||||||
@ -449,7 +476,8 @@ class OpenStackCinderShell(object):
|
|||||||
volume_service_name=volume_service_name,
|
volume_service_name=volume_service_name,
|
||||||
retries=options.retries,
|
retries=options.retries,
|
||||||
http_log_debug=args.debug,
|
http_log_debug=args.debug,
|
||||||
cacert=cacert)
|
cacert=cacert, auth_system=os_auth_system,
|
||||||
|
auth_plugin=auth_plugin)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not utils.isunauthenticated(args.func):
|
if not utils.isunauthenticated(args.func):
|
||||||
|
346
cinderclient/tests/test_auth_plugins.py
Normal file
346
cinderclient/tests/test_auth_plugins.py
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
# Copyright 2012 OpenStack Foundation
|
||||||
|
# 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 argparse
|
||||||
|
import mock
|
||||||
|
import pkg_resources
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
from cinderclient import auth_plugin
|
||||||
|
from cinderclient import exceptions
|
||||||
|
from cinderclient.tests import utils
|
||||||
|
from cinderclient.v1 import client
|
||||||
|
|
||||||
|
|
||||||
|
def mock_http_request(resp=None):
|
||||||
|
"""Mock an HTTP Request."""
|
||||||
|
if not resp:
|
||||||
|
resp = {
|
||||||
|
"access": {
|
||||||
|
"token": {
|
||||||
|
"expires": "12345",
|
||||||
|
"id": "FAKE_ID",
|
||||||
|
"tenant": {
|
||||||
|
"id": "FAKE_TENANT_ID",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serviceCatalog": [
|
||||||
|
{
|
||||||
|
"type": "volume",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"region": "RegionOne",
|
||||||
|
"adminURL": "http://localhost:8774/v1.1",
|
||||||
|
"internalURL": "http://localhost:8774/v1.1",
|
||||||
|
"publicURL": "http://localhost:8774/v1.1/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_response = utils.TestResponse({
|
||||||
|
"status_code": 200,
|
||||||
|
"text": json.dumps(resp),
|
||||||
|
})
|
||||||
|
return mock.Mock(return_value=(auth_response))
|
||||||
|
|
||||||
|
|
||||||
|
def requested_headers(cs):
|
||||||
|
"""Return requested passed headers."""
|
||||||
|
return {
|
||||||
|
'User-Agent': cs.client.USER_AGENT,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecatedAuthPluginTest(utils.TestCase):
|
||||||
|
def test_auth_system_success(self):
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return self.authenticate
|
||||||
|
|
||||||
|
def authenticate(self, cls, auth_url):
|
||||||
|
cls._authenticate(auth_url, {"fake": "me"})
|
||||||
|
|
||||||
|
def mock_iter_entry_points(_type, name):
|
||||||
|
if _type == 'openstack.client.authenticate':
|
||||||
|
return [MockEntrypoint("fake", "fake", ["fake"])]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
mock_request = mock_http_request()
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points",
|
||||||
|
mock_iter_entry_points)
|
||||||
|
@mock.patch.object(requests, "request", mock_request)
|
||||||
|
def test_auth_call():
|
||||||
|
plugin = auth_plugin.DeprecatedAuthPlugin("fake")
|
||||||
|
cs = client.Client("username", "password", "project_id",
|
||||||
|
"auth_url/v2.0", auth_system="fake",
|
||||||
|
auth_plugin=plugin)
|
||||||
|
cs.client.authenticate()
|
||||||
|
|
||||||
|
headers = requested_headers(cs)
|
||||||
|
token_url = cs.client.auth_url + "/tokens"
|
||||||
|
|
||||||
|
mock_request.assert_called_with(
|
||||||
|
"POST",
|
||||||
|
token_url,
|
||||||
|
headers=headers,
|
||||||
|
data='{"fake": "me"}',
|
||||||
|
allow_redirects=True,
|
||||||
|
**self.TEST_REQUEST_BASE)
|
||||||
|
|
||||||
|
test_auth_call()
|
||||||
|
|
||||||
|
def test_auth_system_not_exists(self):
|
||||||
|
def mock_iter_entry_points(_t, name=None):
|
||||||
|
return [pkg_resources.EntryPoint("fake", "fake", ["fake"])]
|
||||||
|
|
||||||
|
mock_request = mock_http_request()
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points",
|
||||||
|
mock_iter_entry_points)
|
||||||
|
@mock.patch.object(requests.Session, "request", mock_request)
|
||||||
|
def test_auth_call():
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
plugin = auth_plugin.DeprecatedAuthPlugin("notexists")
|
||||||
|
cs = client.Client("username", "password", "project_id",
|
||||||
|
"auth_url/v2.0", auth_system="notexists",
|
||||||
|
auth_plugin=plugin)
|
||||||
|
self.assertRaises(exceptions.AuthSystemNotFound,
|
||||||
|
cs.client.authenticate)
|
||||||
|
|
||||||
|
test_auth_call()
|
||||||
|
|
||||||
|
def test_auth_system_defining_auth_url(self):
|
||||||
|
class MockAuthUrlEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return self.auth_url
|
||||||
|
|
||||||
|
def auth_url(self):
|
||||||
|
return "http://faked/v2.0"
|
||||||
|
|
||||||
|
class MockAuthenticateEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return self.authenticate
|
||||||
|
|
||||||
|
def authenticate(self, cls, auth_url):
|
||||||
|
cls._authenticate(auth_url, {"fake": "me"})
|
||||||
|
|
||||||
|
def mock_iter_entry_points(_type, name):
|
||||||
|
if _type == 'openstack.client.auth_url':
|
||||||
|
return [MockAuthUrlEntrypoint("fakewithauthurl",
|
||||||
|
"fakewithauthurl",
|
||||||
|
["auth_url"])]
|
||||||
|
elif _type == 'openstack.client.authenticate':
|
||||||
|
return [MockAuthenticateEntrypoint("fakewithauthurl",
|
||||||
|
"fakewithauthurl",
|
||||||
|
["authenticate"])]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
mock_request = mock_http_request()
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points",
|
||||||
|
mock_iter_entry_points)
|
||||||
|
@mock.patch.object(requests.Session, "request", mock_request)
|
||||||
|
def test_auth_call():
|
||||||
|
plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl")
|
||||||
|
cs = client.Client("username", "password", "project_id",
|
||||||
|
auth_system="fakewithauthurl",
|
||||||
|
auth_plugin=plugin)
|
||||||
|
cs.client.authenticate()
|
||||||
|
self.assertEqual(cs.client.auth_url, "http://faked/v2.0")
|
||||||
|
|
||||||
|
test_auth_call()
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_client_raises_exc_without_auth_url(self, mock_iter_entry_points):
|
||||||
|
class MockAuthUrlEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return self.auth_url
|
||||||
|
|
||||||
|
def auth_url(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t, name: [
|
||||||
|
MockAuthUrlEntrypoint("fakewithauthurl",
|
||||||
|
"fakewithauthurl",
|
||||||
|
["auth_url"])]
|
||||||
|
|
||||||
|
plugin = auth_plugin.DeprecatedAuthPlugin("fakewithauthurl")
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.EndpointNotFound,
|
||||||
|
client.Client, "username", "password", "project_id",
|
||||||
|
auth_system="fakewithauthurl", auth_plugin=plugin)
|
||||||
|
|
||||||
|
|
||||||
|
class AuthPluginTest(utils.TestCase):
|
||||||
|
@mock.patch.object(requests, "request")
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_auth_system_success(self, mock_iter_entry_points, mock_request):
|
||||||
|
"""Test that we can authenticate using the auth system."""
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return FakePlugin
|
||||||
|
|
||||||
|
class FakePlugin(auth_plugin.BaseAuthPlugin):
|
||||||
|
def authenticate(self, cls, auth_url):
|
||||||
|
cls._authenticate(auth_url, {"fake": "me"})
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t: [
|
||||||
|
MockEntrypoint("fake", "fake", ["FakePlugin"])]
|
||||||
|
|
||||||
|
mock_request.side_effect = mock_http_request()
|
||||||
|
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
plugin = auth_plugin.load_plugin("fake")
|
||||||
|
cs = client.Client("username", "password", "project_id",
|
||||||
|
"auth_url/v2.0", auth_system="fake",
|
||||||
|
auth_plugin=plugin)
|
||||||
|
cs.client.authenticate()
|
||||||
|
|
||||||
|
headers = requested_headers(cs)
|
||||||
|
token_url = cs.client.auth_url + "/tokens"
|
||||||
|
|
||||||
|
mock_request.assert_called_with(
|
||||||
|
"POST",
|
||||||
|
token_url,
|
||||||
|
headers=headers,
|
||||||
|
data='{"fake": "me"}',
|
||||||
|
allow_redirects=True,
|
||||||
|
**self.TEST_REQUEST_BASE)
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_discover_auth_system_options(self, mock_iter_entry_points):
|
||||||
|
"""Test that we can load the auth system options."""
|
||||||
|
class FakePlugin(auth_plugin.BaseAuthPlugin):
|
||||||
|
@staticmethod
|
||||||
|
def add_opts(parser):
|
||||||
|
parser.add_argument('--auth_system_opt',
|
||||||
|
default=False,
|
||||||
|
action='store_true',
|
||||||
|
help="Fake option")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return FakePlugin
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t: [
|
||||||
|
MockEntrypoint("fake", "fake", ["FakePlugin"])]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
auth_plugin.load_auth_system_opts(parser)
|
||||||
|
opts, args = parser.parse_known_args(['--auth_system_opt'])
|
||||||
|
|
||||||
|
self.assertTrue(opts.auth_system_opt)
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_parse_auth_system_options(self, mock_iter_entry_points):
|
||||||
|
"""Test that we can parse the auth system options."""
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return FakePlugin
|
||||||
|
|
||||||
|
class FakePlugin(auth_plugin.BaseAuthPlugin):
|
||||||
|
def __init__(self):
|
||||||
|
self.opts = {"fake_argument": True}
|
||||||
|
|
||||||
|
def parse_opts(self, args):
|
||||||
|
return self.opts
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t: [
|
||||||
|
MockEntrypoint("fake", "fake", ["FakePlugin"])]
|
||||||
|
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
plugin = auth_plugin.load_plugin("fake")
|
||||||
|
|
||||||
|
plugin.parse_opts([])
|
||||||
|
self.assertIn("fake_argument", plugin.opts)
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_auth_system_defining_url(self, mock_iter_entry_points):
|
||||||
|
"""Test the auth_system defining an url."""
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return FakePlugin
|
||||||
|
|
||||||
|
class FakePlugin(auth_plugin.BaseAuthPlugin):
|
||||||
|
def get_auth_url(self):
|
||||||
|
return "http://faked/v2.0"
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t: [
|
||||||
|
MockEntrypoint("fake", "fake", ["FakePlugin"])]
|
||||||
|
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
plugin = auth_plugin.load_plugin("fake")
|
||||||
|
|
||||||
|
cs = client.Client("username", "password", "project_id",
|
||||||
|
auth_system="fakewithauthurl",
|
||||||
|
auth_plugin=plugin)
|
||||||
|
self.assertEqual(cs.client.auth_url, "http://faked/v2.0")
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_exception_if_no_authenticate(self, mock_iter_entry_points):
|
||||||
|
"""Test that no authenticate raises a proper exception."""
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return FakePlugin
|
||||||
|
|
||||||
|
class FakePlugin(auth_plugin.BaseAuthPlugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t: [
|
||||||
|
MockEntrypoint("fake", "fake", ["FakePlugin"])]
|
||||||
|
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
plugin = auth_plugin.load_plugin("fake")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.EndpointNotFound,
|
||||||
|
client.Client, "username", "password", "project_id",
|
||||||
|
auth_system="fake", auth_plugin=plugin)
|
||||||
|
|
||||||
|
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||||
|
def test_exception_if_no_url(self, mock_iter_entry_points):
|
||||||
|
"""Test that no auth_url at all raises exception."""
|
||||||
|
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||||
|
def load(self):
|
||||||
|
return FakePlugin
|
||||||
|
|
||||||
|
class FakePlugin(auth_plugin.BaseAuthPlugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_iter_entry_points.side_effect = lambda _t: [
|
||||||
|
MockEntrypoint("fake", "fake", ["FakePlugin"])]
|
||||||
|
|
||||||
|
auth_plugin.discover_auth_systems()
|
||||||
|
plugin = auth_plugin.load_plugin("fake")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.EndpointNotFound,
|
||||||
|
client.Client, "username", "password", "project_id",
|
||||||
|
auth_system="fake", auth_plugin=plugin)
|
@ -16,6 +16,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import pkg_resources
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
@ -286,6 +287,15 @@ def import_class(import_str):
|
|||||||
__import__(mod_str)
|
__import__(mod_str)
|
||||||
return getattr(sys.modules[mod_str], class_str)
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_entry_point(ep_name, name=None):
|
||||||
|
"""Try to load the entry point ep_name that matches name."""
|
||||||
|
for ep in pkg_resources.iter_entry_points(ep_name, name=name):
|
||||||
|
try:
|
||||||
|
return ep.load()
|
||||||
|
except (ImportError, pkg_resources.UnknownExtra, AttributeError):
|
||||||
|
continue
|
||||||
|
|
||||||
_slugify_strip_re = re.compile(r'[^\w\s-]')
|
_slugify_strip_re = re.compile(r'[^\w\s-]')
|
||||||
_slugify_hyphenate_re = re.compile(r'[-\s]+')
|
_slugify_hyphenate_re = re.compile(r'[-\s]+')
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ class Client(object):
|
|||||||
endpoint_type='publicURL', extensions=None,
|
endpoint_type='publicURL', extensions=None,
|
||||||
service_type='volume', service_name=None,
|
service_type='volume', service_name=None,
|
||||||
volume_service_name=None, retries=None,
|
volume_service_name=None, retries=None,
|
||||||
http_log_debug=False,
|
http_log_debug=False, cacert=None,
|
||||||
cacert=None):
|
auth_system='keystone', auth_plugin=None):
|
||||||
# FIXME(comstud): Rename the api_key argument above when we
|
# FIXME(comstud): Rename the api_key argument above when we
|
||||||
# know it's not being used as keyword argument
|
# know it's not being used as keyword argument
|
||||||
password = api_key
|
password = api_key
|
||||||
@ -97,7 +97,9 @@ class Client(object):
|
|||||||
volume_service_name=volume_service_name,
|
volume_service_name=volume_service_name,
|
||||||
retries=retries,
|
retries=retries,
|
||||||
http_log_debug=http_log_debug,
|
http_log_debug=http_log_debug,
|
||||||
cacert=cacert)
|
cacert=cacert,
|
||||||
|
auth_system=auth_system,
|
||||||
|
auth_plugin=auth_plugin)
|
||||||
|
|
||||||
def authenticate(self):
|
def authenticate(self):
|
||||||
"""
|
"""
|
||||||
|
@ -48,8 +48,8 @@ class Client(object):
|
|||||||
endpoint_type='publicURL', extensions=None,
|
endpoint_type='publicURL', extensions=None,
|
||||||
service_type='volumev2', service_name=None,
|
service_type='volumev2', service_name=None,
|
||||||
volume_service_name=None, retries=None,
|
volume_service_name=None, retries=None,
|
||||||
http_log_debug=False,
|
http_log_debug=False, cacert=None,
|
||||||
cacert=None):
|
auth_system='keystone', auth_plugin=None):
|
||||||
# FIXME(comstud): Rename the api_key argument above when we
|
# FIXME(comstud): Rename the api_key argument above when we
|
||||||
# know it's not being used as keyword argument
|
# know it's not being used as keyword argument
|
||||||
password = api_key
|
password = api_key
|
||||||
@ -95,7 +95,9 @@ class Client(object):
|
|||||||
volume_service_name=volume_service_name,
|
volume_service_name=volume_service_name,
|
||||||
retries=retries,
|
retries=retries,
|
||||||
http_log_debug=http_log_debug,
|
http_log_debug=http_log_debug,
|
||||||
cacert=cacert)
|
cacert=cacert,
|
||||||
|
auth_system=auth_system,
|
||||||
|
auth_plugin=auth_plugin)
|
||||||
|
|
||||||
def authenticate(self):
|
def authenticate(self):
|
||||||
"""Authenticate against the server.
|
"""Authenticate against the server.
|
||||||
|
@ -128,6 +128,9 @@ class InstallVenv(object):
|
|||||||
"install")
|
"install")
|
||||||
return parser.parse_args(argv[1:])[0]
|
return parser.parse_args(argv[1:])[0]
|
||||||
|
|
||||||
|
def post_process(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Distro(InstallVenv):
|
class Distro(InstallVenv):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user