Add keystone-session support

Currently python-manilaclient doesn't support keystone sessions
unlike to others clients. keystoneclient.session.Session gives
end-user possibility to share common authentication and request
parameters between a variety of services.

- Add python-keystoneclient to requirements.txt
- Move authentication logic to v1/client.py
- Move HttpClient to separate module
- Simplify SecretsHelper in shell.py
- Replace service_catalog module by keystoneclient.service_catalog
- Add appropriate unit tests

Implements bp add-keystone-session-support

Change-Id: I7d1c50fa31d9e2f32bb518a78a02bc12b114d0ab
This commit is contained in:
Igor Malinovskiy
2015-02-23 11:51:27 +02:00
parent faf80118ac
commit 58cc003661
14 changed files with 407 additions and 1576 deletions

View File

@@ -1,6 +1,7 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,388 +20,14 @@
OpenStack Client interface. Handles the REST calls and responses.
"""
from __future__ import print_function
import logging
import os
try:
from eventlet import sleep
except ImportError:
from time import sleep # noqa
from oslo.serialization import jsonutils
from oslo.utils import importutils
import requests
from six.moves.urllib import parse
from manilaclient import exceptions
from manilaclient import service_catalog
class HTTPClient(object):
USER_AGENT = 'python-manilaclient'
def __init__(self,
user,
password,
projectid,
auth_url,
insecure=False,
timeout=None,
tenant_id=None,
proxy_tenant_id=None,
proxy_token=None,
region_name=None,
endpoint_type='publicURL',
service_type=None,
service_name=None,
share_service_name=None,
retries=None,
http_log_debug=False,
cacert=None,
os_cache=False):
self.user = user
self.password = password
self.projectid = projectid
self.tenant_id = tenant_id
self.auth_url = auth_url.rstrip('/')
self.version = 'v1'
self.region_name = region_name
self.endpoint_type = endpoint_type
self.service_type = service_type
self.service_name = service_name
self.share_service_name = share_service_name
self.retries = int(retries or 0)
self.http_log_debug = http_log_debug
self.management_url = None
self.auth_token = None
self.proxy_token = proxy_token
self.proxy_tenant_id = proxy_tenant_id
self.os_cache = os_cache
if insecure:
self.verify_cert = False
else:
if cacert:
self.verify_cert = cacert
else:
self.verify_cert = True
self._logger = logging.getLogger(__name__)
if self.http_log_debug:
ch = logging.StreamHandler()
self._logger.setLevel(logging.DEBUG)
self._logger.addHandler(ch)
if hasattr(requests, 'logging'):
requests.logging.getLogger(requests.__name__).addHandler(ch)
def http_log_req(self, args, kwargs):
if not self.http_log_debug:
return
string_parts = ['curl -i']
for element in args:
if element in ('GET', 'POST', 'DELETE', 'PUT'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
string_parts.append(header)
if 'data' in kwargs:
string_parts.append(" -d '%s'" % (kwargs['data']))
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
def http_log_resp(self, resp):
if not self.http_log_debug:
return
self._logger.debug(
"RESP: [%s] %s\nRESP BODY: %s\n",
resp.status_code,
resp.headers,
resp.text)
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
kwargs['headers']['Accept'] = 'application/json'
if 'body' in kwargs:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = jsonutils.dumps(kwargs['body'])
del kwargs['body']
self.http_log_req((url, method,), kwargs)
resp = requests.request(
method,
url,
verify=self.verify_cert,
**kwargs)
self.http_log_resp(resp)
if resp.text:
try:
body = jsonutils.loads(resp.text)
except ValueError:
pass
body = None
else:
body = None
if resp.status_code >= 400:
raise exceptions.from_response(resp, method, url)
return resp, body
def _cs_request(self, url, method, **kwargs):
auth_attempts = 0
attempts = 0
backoff = 1
while True:
attempts += 1
if not self.management_url or not self.auth_token:
self.authenticate()
kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
if self.projectid:
kwargs['headers']['X-Auth-Project-Id'] = self.projectid
try:
resp, body = self.request(self.management_url + url, method,
**kwargs)
return resp, body
except exceptions.BadRequest as e:
if attempts > self.retries:
raise
except exceptions.Unauthorized:
if auth_attempts > 0:
raise
self._logger.debug("Unauthorized, reauthenticating.")
self.management_url = self.auth_token = None
# First reauth. Discount this attempt.
attempts -= 1
auth_attempts += 1
continue
except exceptions.ClientException as e:
if attempts > self.retries:
raise
if 500 <= e.http_status <= 599:
pass
else:
raise
except requests.exceptions.ConnectionError as e:
# Catch a connection refused from requests.request
self._logger.debug("Connection refused: %s" % e)
raise
self._logger.debug(
"Failed attempt(%s of %s), retrying in %s seconds" %
(attempts, self.retries, backoff))
sleep(backoff)
backoff *= 2
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def _extract_service_catalog(self, url, resp, body, extract_token=True):
"""See what the auth service told us and process the response.
We may get redirected to another site, fail or actually get
back a service catalog with a token and our endpoints.
"""
method = None
if hasattr(resp, 'request') and hasattr(resp.request, 'method'):
method = resp.request.method
if resp.status_code == 200: # content must always present
try:
self.auth_url = url
self.service_catalog = service_catalog.ServiceCatalog(body)
if extract_token:
self.auth_token = self.service_catalog.get_token()
self.tenant_id = self.service_catalog.get_tenant_id()
management_url = self.service_catalog.url_for(
attr='region',
filter_value=self.region_name,
endpoint_type=self.endpoint_type,
service_type=self.service_type,
service_name=self.service_name,
share_service_name=self.share_service_name)
self.management_url = management_url.rstrip('/')
return None
except exceptions.AmbiguousEndpoints:
print("Found more than one valid endpoint. Use a more "
"restrictive filter")
raise
except KeyError:
raise exceptions.AuthorizationFailure()
except exceptions.EndpointNotFound:
print("Could not find any suitable endpoint. Correct region?")
raise
elif resp.status_code == 305:
return resp['location']
else:
raise exceptions.from_response(resp, method, url)
def _fetch_endpoints_from_auth(self, url):
"""Fetch endpoints from auth.
We have a token, but don't know the final endpoint for
the region. We have to go back to the auth service and
ask again. This request requires an admin-level token
to work. The proxy token supplied could be from a low-level enduser.
We can't get this from the keystone service endpoint, we have to use
the admin endpoint.
This will overwrite our admin token with the user token.
"""
# GET ...:5001/v2.0/tokens/#####/endpoints
url = '/'.join([url, 'tokens', '%s?belongsTo=%s'
% (self.proxy_token, self.proxy_tenant_id)])
self._logger.debug("Using Endpoint URL: %s" % url)
resp, body = self.request(url, "GET",
headers={'X-Auth-Token': self.auth_token})
return self._extract_service_catalog(url, resp, body,
extract_token=False)
def _save_keys(self):
# Store the token/mgmt url in the keyring for later requests.
if (self.os_cache):
self.keyring_saver.save(self.auth_token,
self.management_url,
self.tenant_id)
self.keyring_saver.save_password()
def authenticate(self):
magic_tuple = parse.urlsplit(self.auth_url)
scheme, netloc, path, query, frag = magic_tuple
port = magic_tuple.port
if port is None:
port = 80
path_parts = path.split('/')
for part in path_parts:
if len(part) > 0 and part[0] == 'v':
self.version = part
break
# TODO(sandy): Assume admin endpoint is 35357 for now.
# Ideally this is going to have to be provided by the service catalog.
new_netloc = netloc.replace(':%d' % port, ':%d' % (35357,))
admin_url = parse.urlunsplit((scheme, new_netloc,
path, query, frag))
auth_url = self.auth_url
if self.version == "v2.0":
while auth_url:
if "MANILA_RAX_AUTH" in os.environ:
auth_url = self._rax_auth(auth_url)
else:
auth_url = self._v2_auth(auth_url)
# Are we acting on behalf of another user via an
# existing token? If so, our actual endpoints may
# be different than that of the admin token.
if self.proxy_token:
self._fetch_endpoints_from_auth(admin_url)
# Since keystone no longer returns the user token
# with the endpoints any more, we need to replace
# our service account token with the user token.
self.auth_token = self.proxy_token
else:
try:
while auth_url:
auth_url = self._v1_auth(auth_url)
# In some configurations manila makes redirection to
# v2.0 keystone endpoint. Also, new location does not contain
# real endpoint, only hostname and port.
except exceptions.AuthorizationFailure:
if auth_url.find('v2.0') < 0:
auth_url = auth_url + '/v2.0'
self._v2_auth(auth_url)
self._save_keys()
def _v1_auth(self, url):
if self.proxy_token:
raise exceptions.NoTokenLookupException()
headers = {'X-Auth-User': self.user,
'X-Auth-Key': self.password}
if self.projectid:
headers['X-Auth-Project-Id'] = self.projectid
method = 'GET'
resp, body = self.request(url, method, headers=headers)
if resp.status_code in (200, 204): # in some cases we get No Content
try:
mgmt_header = 'x-server-management-url'
self.management_url = resp.headers[mgmt_header].rstrip('/')
self.auth_token = resp.headers['x-auth-token']
self.auth_url = url
except (KeyError, TypeError):
raise exceptions.AuthorizationFailure()
elif resp.status_code == 305:
return resp.headers['location']
else:
raise exceptions.from_response(resp, method, url)
def _v2_auth(self, url):
"""Authenticate against a v2.0 auth service."""
body = {"auth": {
"passwordCredentials": {"username": self.user,
"password": self.password}}}
if self.projectid:
body['auth']['tenantName'] = self.projectid
elif self.tenant_id:
body['auth']['tenantId'] = self.tenant_id
self._authenticate(url, body)
def _rax_auth(self, url):
"""Authenticate against the Rackspace auth service."""
body = {"auth": {
"RAX-KSKEY:apiKeyCredentials": {
"username": self.user,
"apiKey": self.password,
"tenantName": self.projectid}}}
self._authenticate(url, body)
def _authenticate(self, url, body):
"""Authenticate and extract the service catalog."""
token_url = url + "/tokens"
# Make sure we follow redirects when trying to reach Keystone
resp, body = self.request(
token_url,
"POST",
body=body,
allow_redirects=True)
return self._extract_service_catalog(url, resp, body)
def get_client_class(version):
version_map = {
'1': 'manilaclient.v1.client.Client',
'2': 'manilaclient.v2.client.Client',
}
try:
client_path = version_map[str(version)]

163
manilaclient/httpclient.py Normal file
View File

@@ -0,0 +1,163 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Piston Cloud Computing, Inc.
# Copyright 2015 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import logging
from oslo.serialization import jsonutils
from oslo_utils import strutils
import requests
import six
from manilaclient import exceptions
try:
from eventlet import sleep
except ImportError:
from time import sleep # noqa
class HTTPClient(object):
def __init__(self, base_url, token, user_agent,
insecure=False, cacert=None, timeout=None, retries=None,
http_log_debug=False):
self.base_url = base_url
self.retries = retries
self.http_log_debug = http_log_debug
self.request_options = self._set_request_options(
insecure, cacert, timeout)
self.default_headers = {
'X-Auth-Token': token,
'User-Agent': user_agent,
'Accept': 'application/json',
}
self._logger = logging.getLogger(__name__)
if self.http_log_debug:
ch = logging.StreamHandler()
self._logger.setLevel(logging.DEBUG)
self._logger.addHandler(ch)
if hasattr(requests, 'logging'):
requests.logging.getLogger(requests.__name__).addHandler(ch)
def _set_request_options(self, insecure, cacert, timeout=None):
options = {'verify': True}
if insecure:
options['verify'] = False
elif cacert:
options['verify'] = cacert
if timeout:
options['timeout'] = timeout
return options
def request(self, url, method, **kwargs):
headers = copy.deepcopy(self.default_headers)
headers.update(kwargs.get('headers', {}))
options = copy.deepcopy(self.request_options)
if 'body' in kwargs:
headers['Content-Type'] = 'application/json'
options['data'] = jsonutils.dumps(kwargs['body'])
self.log_request(method, url, headers, options.get('data', None))
resp = requests.request(method, url, headers=headers, **options)
self.log_response(resp)
body = None
if resp.text:
try:
body = jsonutils.loads(resp.text)
except ValueError:
pass
if resp.status_code >= 400:
raise exceptions.from_response(resp, method, url)
return resp, body
def _cs_request(self, url, method, **kwargs):
attempts = 0
timeout = 1
while True:
attempts += 1
try:
resp, body = self.request(self.base_url + url, method,
**kwargs)
return resp, body
except (exceptions.BadRequest,
requests.exceptions.RequestException,
exceptions.ClientException) as e:
if attempts > self.retries:
raise
self._logger.debug("Request error: %s" % six.text_type(e))
self._logger.debug(
"Failed attempt(%(current)s of %(total)s), "
" retrying in %(sec)s seconds" % {
'current': attempts,
'total': self.retries,
'sec': timeout
})
sleep(timeout)
timeout *= 2
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def log_request(self, method, url, headers, data=None):
if not self.http_log_debug:
return
string_parts = ['curl -i', ' -X %s' % method, ' %s' % url]
for element in headers:
header = ' -H "%s: %s"' % (element, headers[element])
string_parts.append(header)
if data:
if "password" in data:
data = strutils.mask_password(data)
string_parts.append(" -d '%s'" % data)
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
def log_response(self, resp):
if not self.http_log_debug:
return
self._logger.debug(
"RESP: [%(code)s] %(headers)s\nRESP BODY: %(body)s\n" % {
'code': resp.status_code,
'headers': resp.headers,
'body': resp.text
})

View File

@@ -1,83 +0,0 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011, Piston Cloud Computing, 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 manilaclient.exceptions
class ServiceCatalog(object):
"""Helper methods for dealing with a Keystone Service Catalog."""
def __init__(self, resource_dict):
self.catalog = resource_dict
def get_token(self):
return self.catalog['access']['token']['id']
def get_tenant_id(self):
return self.catalog['access']['token']['tenant']['id']
def url_for(self, attr=None, filter_value=None,
service_type=None, endpoint_type='publicURL',
service_name=None, share_service_name=None):
"""Returns url for specified endpoint type.
Fetch the public URL from the Compute service for
a particular endpoint attribute.
If none given, return the first. See tests for sample service catalog.
"""
matching_endpoints = []
if 'endpoints' in self.catalog:
# We have a bastardized service catalog. Treat it special. :/
for endpoint in self.catalog['endpoints']:
if not filter_value or endpoint[attr] == filter_value:
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise manilaclient.exceptions.EndpointNotFound()
# We don't always get a service catalog back ...
if 'serviceCatalog' not in self.catalog['access']:
return None
# Full catalog ...
catalog = self.catalog['access']['serviceCatalog']
for service in catalog:
if service.get("type") != service_type:
continue
if (service_name and service_type == 'compute' and
service.get('name') != service_name):
continue
if (share_service_name and service_type == 'share' and
service.get('name') != share_service_name):
continue
endpoints = service['endpoints']
for endpoint in endpoints:
if not filter_value or endpoint.get(attr) == filter_value:
endpoint["serviceName"] = service.get("name")
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise manilaclient.exceptions.EndpointNotFound()
elif len(matching_endpoints) > 1:
raise manilaclient.exceptions.AmbiguousEndpoints(
endpoints=matching_endpoints)
else:
return matching_endpoints[0][endpoint_type]

View File

@@ -21,7 +21,6 @@ Command-line interface to the OpenStack Manila API.
from __future__ import print_function
import argparse
import getpass
import glob
import imp
import itertools
@@ -30,9 +29,7 @@ import os
import pkgutil
import sys
import keyring
from oslo.utils import encodeutils
from oslo.utils import strutils
import six
from manilaclient import client
@@ -104,188 +101,6 @@ class ManilaClientArgumentParser(argparse.ArgumentParser):
return option_tuples
class ManilaKeyring(keyring.backends.file.EncryptedKeyring):
def delete_password(self, keyring_keys):
"""Delete passwords from keyring.
Delete the passwords for given usernames of the services.
:param keyring_key: dictionary containing pairs {service:username}
"""
if self._check_file():
self._unlock()
for key, value in six.iteritems(keyring_keys):
super(ManilaKeyring, self).delete_password(key, value)
class SecretsHelper(object):
"""Helper for working with keyring."""
def __init__(self, args, client):
self.args = args
self.client = client
self.key = None
self.keyring = ManilaKeyring()
def _validate_string(self, text):
if text is None or len(text) == 0:
return False
return True
def _make_key(self):
if self.key is not None:
return self.key
keys = [
self.client.auth_url,
self.client.user,
self.client.projectid,
self.client.region_name,
self.client.endpoint_type,
self.client.service_type,
self.client.service_name,
self.client.share_service_name,
]
for (index, key) in enumerate(keys):
if key is None:
keys[index] = '?'
else:
keys[index] = str(keys[index])
self.key = "/".join(keys)
return self.key
def _prompt_password(self, verify=True):
"""Suggest user to enter password from keyboard."""
pw = None
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
# Check for Ctl-D
try:
while True:
pw1 = getpass.getpass('OS Password: ')
if verify:
pw2 = getpass.getpass('Please verify: ')
else:
pw2 = pw1
if pw1 == pw2 and self._validate_string(pw1):
pw = pw1
break
except EOFError:
pass
return pw
def save(self, auth_token, management_url, tenant_id):
"""Save auth token, management url and tenant id in keyring.
If params are different from already cached ones, save new auth token,
management url and tenant id in keyring.
Raise ValueError in case of empty auth_token, management_url or
tenant_id.
"""
if (auth_token == self.auth_token and
management_url == self.management_url):
# Nothing changed....
return
if not all([management_url, auth_token, tenant_id]):
raise ValueError("Unable to save empty management url/auth "
"token")
value = "|".join([str(auth_token),
str(management_url),
str(tenant_id)])
self.keyring.set_password('manilaclient_auth', self._make_key(), value)
def reset(self):
"""Delete cached password and auth token."""
args = {'openstack': self.args.os_username,
'manilaclient_auth': self._make_key()}
self.keyring.delete_password(args)
def save_password(self):
self.keyring.set_password('openstack', self.args.os_username,
self.password)
def check_cached_password(self):
"""Check if os_password is equal to cached password."""
if self.args.os_cache:
cached_password = self.keyring.get_password('openstack',
self.args.os_username)
cached_token = self.keyring.get_password('manilaclient_auth',
self._make_key())
if cached_password and self.password != cached_password:
return False
if cached_token and not cached_password:
return False
return True
@property
def password(self):
"""Return user password.
Return os_password value or suggest user to enter new password from
keyboard.
"""
password = None
if self._validate_string(self.args.os_password):
password = self.args.os_password
else:
verify_pass = strutils.bool_from_string(
cliutils.env("OS_VERIFY_PASSWORD", default=False))
password = self._prompt_password(verify_pass)
if not password:
raise exc.CommandError(
'Expecting a password provided via either '
'--os-password, env[OS_PASSWORD], or '
'prompted response')
return password
@property
def management_url(self):
"""Return cached management url.
If os_cache enabled and management url already cached, return
management url. Otherwise return None.
"""
if not self.args.os_cache:
return None
management_url = None
block = self.keyring.get_password('manilaclient_auth',
self._make_key())
if block:
_token, management_url, _tenant_id = block.split('|', 2)
return management_url
@property
def auth_token(self):
"""Return cached auth token.
If os_cache enabled and auth token already cached, return auth token.
Otherwise return None.
"""
if not self.args.os_cache:
return None
token = None
block = self.keyring.get_password('manilaclient_auth',
self._make_key())
if block:
token, _management_url, _tenant_id = block.split('|', 2)
return token
@property
def tenant_id(self):
"""Return cached tenant id.
If os_cache enabled and tenant id already cached, return tenant id.
Otherwise return None.
"""
if not self.args.os_cache:
return None
tenant_id = None
block = self.keyring.get_password('manilaclient_auth',
self._make_key())
if block:
_token, _management_url, tenant_id = block.split('|', 2)
return tenant_id
class OpenStackManilaShell(object):
def get_base_parser(self):
@@ -576,6 +391,9 @@ class OpenStackManilaShell(object):
args.service_name, args.share_service_name,
args.os_cacert, args.os_cache, args.os_reset_cache)
if share_service_name:
service_name = share_service_name
if not endpoint_type:
endpoint_type = DEFAULT_MANILA_ENDPOINT_TYPE
@@ -612,39 +430,22 @@ class OpenStackManilaShell(object):
"You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL]")
self.cs = client.Client(options.os_share_api_version, os_username,
os_password, os_tenant_name, os_auth_url,
insecure, region_name=os_region_name,
self.cs = client.Client(options.os_share_api_version,
username=os_username,
api_key=os_password,
project_name=os_tenant_name,
auth_url=os_auth_url,
insecure=insecure, region_name=os_region_name,
tenant_id=os_tenant_id,
endpoint_type=endpoint_type,
extensions=self.extensions,
service_type=service_type,
service_name=service_name,
share_service_name=share_service_name,
retries=options.retries,
http_log_debug=args.debug,
cacert=cacert,
os_cache=os_cache)
if not cliutils.isunauthenticated(args.func):
helper = SecretsHelper(args, self.cs.client)
if os_reset_cache:
helper.reset()
self.cs.client.keyring_saver = helper
if (helper.tenant_id and helper.auth_token and
helper.management_url and
helper.check_cached_password()):
self.cs.client.tenant_id = helper.tenant_id
self.cs.client.auth_token = helper.auth_token
self.cs.client.management_url = helper.management_url
else:
self.cs.client.password = helper.password
try:
self.cs.authenticate()
except exc.Unauthorized:
raise exc.CommandError("Invalid OpenStack Manila "
"credentials.")
except exc.AuthorizationFailure:
raise exc.CommandError("Unable to authorize user")
use_keyring=os_cache,
force_new_token=os_reset_cache)
args.func(self.cs, args)

View File

@@ -13,10 +13,11 @@
import mock
import requests
from manilaclient import client
from manilaclient import exceptions
from manilaclient import httpclient
from manilaclient.tests.unit import utils
fake_user_agent = "fake"
fake_response = utils.TestResponse({
"status_code": 200,
@@ -42,17 +43,36 @@ bad_500_response = utils.TestResponse({
})
bad_500_request = mock.Mock(return_value=(bad_500_response))
retry_after_response = utils.TestResponse({
"status_code": 413,
"text": '',
"headers": {
"retry-after": "5"
},
})
retry_after_mock_request = mock.Mock(return_value=retry_after_response)
def get_client(retries=0):
cl = client.HTTPClient("username", "password",
"project_id", "auth_test", retries=retries)
return cl
retry_after_no_headers_response = utils.TestResponse({
"status_code": 413,
"text": '',
})
retry_after_no_headers_mock_request = mock.Mock(
return_value=retry_after_no_headers_response)
retry_after_non_supporting_response = utils.TestResponse({
"status_code": 403,
"text": '',
"headers": {
"retry-after": "5"
},
})
retry_after_non_supporting_mock_request = mock.Mock(
return_value=retry_after_non_supporting_response)
def get_authed_client(retries=0):
cl = get_client(retries=retries)
cl.management_url = "http://example.com"
cl.auth_token = "token"
cl = httpclient.HTTPClient("http://example.com", "token", fake_user_agent,
retries=retries, http_log_debug=True)
return cl
@@ -66,8 +86,7 @@ class ClientTest(utils.TestCase):
def test_get_call():
resp, body = cl.get("/hi")
headers = {"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"User-Agent": cl.USER_AGENT,
"User-Agent": fake_user_agent,
'Accept': 'application/json', }
mock_request.assert_called_with(
"GET",
@@ -79,28 +98,6 @@ class ClientTest(utils.TestCase):
test_get_call()
def test_get_reauth_0_retries(self):
cl = get_authed_client(retries=0)
self.requests = [bad_401_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
def reauth():
cl.management_url = "http://example.com"
cl.auth_token = "token"
@mock.patch.object(cl, 'authenticate', reauth)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
test_get_call()
self.assertEqual(self.requests, [])
def test_get_retry_500(self):
cl = get_authed_client(retries=1)
@@ -177,10 +174,9 @@ class ClientTest(utils.TestCase):
cl.post("/hi", body=[1, 2, 3])
headers = {
"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"Content-Type": "application/json",
'Accept': 'application/json',
"User-Agent": cl.USER_AGENT
"User-Agent": fake_user_agent
}
mock_request.assert_called_with(
"POST",
@@ -190,13 +186,3 @@ class ClientTest(utils.TestCase):
**self.TEST_REQUEST_BASE)
test_post_call()
def test_auth_failure(self):
cl = get_client()
# response must not have x-server-management-url header
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate)
test_auth_call()

View File

@@ -1,139 +0,0 @@
# 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 manilaclient import exceptions
from manilaclient import service_catalog
from manilaclient.tests.unit import utils
# Taken directly from keystone/content/common/samples/auth.json
# Do not edit this structure. Instead, grab the latest from there.
SERVICE_CATALOG = {
"access": {
"token": {
"id": "ab48a9efdfedb23ty3494",
"expires": "2010-11-01T03:32:15-05:00",
"tenant": {
"id": "345",
"name": "My Project"
}
},
"user": {
"id": "123",
"name": "jqsmith",
"roles": [
{
"id": "234",
"name": "compute:admin",
},
{
"id": "235",
"name": "object-store:admin",
"tenantId": "1",
}
],
"roles_links": [],
},
"serviceCatalog": [
{
"name": "Cloud Servers",
"type": "compute",
"endpoints": [
{
"tenantId": "1",
"publicURL": "https://compute1.host/v1/1234",
"internalURL": "https://compute1.host/v1/1234",
"region": "North",
"versionId": "1.0",
"versionInfo": "https://compute1.host/v1/",
"versionList": "https://compute1.host/"
},
{
"tenantId": "2",
"publicURL": "https://compute1.host/v1/3456",
"internalURL": "https://compute1.host/v1/3456",
"region": "North",
"versionId": "1.1",
"versionInfo": "https://compute1.host/v1/",
"versionList": "https://compute1.host/"
},
],
"endpoints_links": [],
},
{
"name": "Nova Shares",
"type": "share",
"endpoints": [
{
"tenantId": "1",
"publicURL": "https://share1.host/v1/1234",
"internalURL": "https://share1.host/v1/1234",
"region": "South",
"versionId": "1.0",
"versionInfo": "uri",
"versionList": "uri"
},
{
"tenantId": "2",
"publicURL": "https://share1.host/v1/3456",
"internalURL": "https://share1.host/v1/3456",
"region": "South",
"versionId": "1.1",
"versionInfo": "https://share1.host/v1/",
"versionList": "https://share1.host/"
},
],
"endpoints_links": [
{
"rel": "next",
"href": "https://identity1.host/v2.0/endpoints"
},
],
},
],
"serviceCatalog_links": [
{
"rel": "next",
"href": "https://identity.host/v2.0/endpoints?session=2hfh8Ar",
},
],
},
}
class ServiceCatalogTest(utils.TestCase):
def test_building_a_service_catalog(self):
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
service_type='compute')
self.assertEqual(sc.url_for('tenantId', '1', service_type='compute'),
"https://compute1.host/v1/1234")
self.assertEqual(sc.url_for('tenantId', '2', service_type='compute'),
"https://compute1.host/v1/3456")
self.assertRaises(exceptions.EndpointNotFound, sc.url_for,
"region", "South", service_type='compute')
def test_alternate_service_type(self):
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
service_type='share')
self.assertEqual(sc.url_for('tenantId', '1', service_type='share'),
"https://share1.host/v1/1234")
self.assertEqual(sc.url_for('tenantId', '2', service_type='share'),
"https://share1.host/v1/3456")
self.assertRaises(exceptions.EndpointNotFound, sc.url_for,
"region", "North", service_type='share')

View File

@@ -14,7 +14,7 @@
from six.moves.urllib import parse
from manilaclient import client as base_client
from manilaclient import httpclient
from manilaclient.tests.unit import fakes
from manilaclient.tests.unit import utils
from manilaclient.v1 import client
@@ -29,7 +29,7 @@ class FakeClient(fakes.FakeClient, client.Client):
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(base_client.HTTPClient):
class FakeHTTPClient(httpclient.HTTPClient):
def __init__(self, **kwargs):
self.username = 'username'

View File

@@ -24,7 +24,9 @@ class FakeClient(fakes.FakeClient):
def __init__(self, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
input_auth_token='token',
extensions=kwargs.get('extensions'),
service_catalog_url='http://localhost:8786')
self.client = FakeHTTPClient(**kwargs)

View File

@@ -1,399 +0,0 @@
# Copyright (c) 2013 OpenStack, LLC.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import requests
from manilaclient import exceptions
from manilaclient.tests.unit import utils
from manilaclient.v1 import client
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
resp = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
"tenant": {
"id": "FAKE_TENANT_ID"
},
},
"serviceCatalog": [
{
"type": "compute",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8774/v2",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_authenticate_tenant_id(self):
cs = client.Client("username", "password", auth_url="auth_url/v2.0",
tenant_id='tenant_id', service_type='compute')
resp = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
"tenant": {
"description": None,
"enabled": True,
"id": "tenant_id",
"name": "demo"
} # tenant associated with token
},
"serviceCatalog": [
{
"type": "compute",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8774/v2",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantId': cs.client.tenant_id,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
tenant_id = resp["access"]["token"]["tenant"]["id"]
self.assertEqual(cs.client.tenant_id, tenant_id)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0")
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2", service_type='compute')
dict_correct_response = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
"tenant": {
"id": "FAKE_TENANT_ID"
},
},
"serviceCatalog": [
{
"type": "compute",
"endpoints": [
{
"adminURL": "http://localhost:8774/v2",
"region": "RegionOne",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location': 'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, manila redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status_code": 200,
"text": correct_response}
]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_ambiguous_endpoints(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
resp = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
"tenant": {
"id": "FAKE_TENANT_ID"
},
},
"serviceCatalog": [
{
"adminURL": "http://localhost:8774/v2",
"type": "compute",
"name": "Compute CLoud",
"endpoints": [
{
"region": "RegionOne",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
{
"adminURL": "http://localhost:8774/v2",
"type": "compute",
"name": "Hyper-compute Cloud",
"endpoints": [
{
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AmbiguousEndpoints,
cs.client.authenticate)
test_auth_call()
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
management_url = 'https://localhost/v2.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
},
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'Accept': 'application/json',
'X-Auth-User': 'username',
'X-Auth-Key': 'password',
'X-Auth-Project-Id': 'project_id',
'User-Agent': cs.client.USER_AGENT
}
mock_request.assert_called_with(
"GET",
cs.client.auth_url,
headers=headers,
**self.TEST_REQUEST_BASE)
self.assertEqual(cs.client.management_url,
auth_response.headers['x-server-management-url'])
self.assertEqual(cs.client.auth_token,
auth_response.headers['x-auth-token'])
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id", "auth_url")
auth_response = utils.TestResponse({"status_code": 401})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_automatic(self):
cs = client.Client("username", "password", "project_id", "auth_url")
http_client = cs.client
http_client.management_url = ''
mock_request = mock.Mock(return_value=(None, None))
@mock.patch.object(http_client, 'request', mock_request)
@mock.patch.object(http_client, 'authenticate')
def test_auth_call(m):
http_client.get('/')
m.assert_called()
mock_request.assert_called()
test_auth_call()
def test_auth_manual(self):
cs = client.Client("username", "password", "project_id", "auth_url")
@mock.patch.object(cs.client, 'authenticate')
def test_auth_call(m):
cs.authenticate()
m.assert_called()
test_auth_call()

View File

@@ -0,0 +1,33 @@
# 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 uuid
from keystoneclient import session
from manilaclient.tests.unit import utils
from manilaclient.v1 import client
class ClientTest(utils.TestCase):
def test_adapter_properties(self):
# sample of properties, there are many more
retries = 3
base_url = uuid.uuid4().hex
s = session.Session()
c = client.Client(session=s, service_catalog_url=base_url,
retries=retries, input_auth_token='token')
self.assertEqual(base_url, c.client.base_url)
self.assertEqual(retries, c.client.retries)

View File

@@ -16,8 +16,6 @@
import fixtures
import mock
from oslo.serialization import jsonutils
import requests
import six
from six.moves.urllib import parse
@@ -29,7 +27,6 @@ from manilaclient.openstack.common import cliutils
from manilaclient import shell
from manilaclient.tests.unit import utils as test_utils
from manilaclient.tests.unit.v1 import fakes
from manilaclient.v1 import client as client_v1
from manilaclient.v1 import shell as shell_v1
@@ -922,273 +919,3 @@ class ShellTest(test_utils.TestCase):
cliutils.print_list.assert_called_once_with(
mock.ANY,
fields=['id', 'name', 'status', 'type'])
@mock.patch.object(fakes.FakeClient, 'authenticate', mock.Mock())
@mock.patch.object(shell.SecretsHelper, '_make_key', mock.Mock())
@mock.patch.object(shell.SecretsHelper, 'password', mock.Mock())
@mock.patch.object(shell.SecretsHelper, 'check_cached_password',
mock.Mock())
def test_os_cache_enabled_keys_saved_password_not_changed(self):
shell.SecretsHelper.tenant_id = 'fake'
shell.SecretsHelper.auth_token = 'fake'
shell.SecretsHelper.management_url = 'fake'
self.run_command('--os-cache list')
self.assertFalse(shell.SecretsHelper.password.called)
self.assertFalse(fakes.FakeClient.authenticate.called)
@mock.patch.object(shell.SecretsHelper, '_validate_string', mock.Mock())
@mock.patch.object(shell.SecretsHelper, '_prompt_password', mock.Mock())
@mock.patch.object(shell.ManilaKeyring, 'get_password', mock.Mock())
def test_os_cache_enabled_keys_not_saved_with_password(self):
shell.SecretsHelper._validate_string.return_value = True
shell.ManilaKeyring.get_password.return_value = None
shell.SecretsHelper.tenant_id = None
shell.SecretsHelper.auth_token = None
shell.SecretsHelper.management_url = None
self.run_command('--os-cache list')
self.assertFalse(shell.SecretsHelper._prompt_password.called)
shell.SecretsHelper._validate_string.assert_called_once_with(
'password')
@mock.patch.object(shell.SecretsHelper, '_prompt_password', mock.Mock())
@mock.patch.object(shell.ManilaKeyring, 'get_password', mock.Mock())
def test_os_cache_enabled_keys_not_saved_no_password(self):
shell.ManilaKeyring.get_password.return_value = None
shell.SecretsHelper._prompt_password.return_value = 'password'
self.useFixture(fixtures.EnvironmentVariable('MANILA_PASSWORD', ''))
shell.SecretsHelper.tenant_id = None
shell.SecretsHelper.auth_token = None
shell.SecretsHelper.management_url = None
self.run_command('--os-cache list')
self.assertTrue(shell.SecretsHelper._prompt_password.called)
@mock.patch.object(shell.SecretsHelper, '_validate_string', mock.Mock())
@mock.patch.object(shell.ManilaKeyring, 'get_password', mock.Mock())
@mock.patch.object(shell.SecretsHelper, 'reset', mock.Mock())
def test_os_cache_enabled_keys_reset_cached_password(self):
shell.ManilaKeyring.get_password.return_value = 'old_password'
shell.SecretsHelper._validate_string.return_value = True
shell.SecretsHelper.tenant_id = None
shell.SecretsHelper.auth_token = None
shell.SecretsHelper.management_url = None
self.run_command('--os-cache --os-reset-cache list')
shell.SecretsHelper._validate_string.assert_called_once_with(
'password')
shell.SecretsHelper.reset.assert_called_once_with()
@mock.patch.object(shell.SecretsHelper, '_validate_string', mock.Mock())
@mock.patch.object(shell.SecretsHelper, 'reset', mock.Mock())
def test_os_cache_disabled_keys_reset_cached_password(self):
shell.SecretsHelper._validate_string.return_value = True
self.run_command('--os-reset-cache list')
shell.SecretsHelper._validate_string.assert_called_once_with(
'password')
shell.SecretsHelper.reset.assert_called_once_with()
@mock.patch.object(fakes.FakeClient, 'authenticate', mock.Mock())
@mock.patch.object(shell.ManilaKeyring, 'get_password', mock.Mock())
@mock.patch.object(shell.SecretsHelper, '_make_key', mock.Mock())
def test_os_cache_enabled_os_password_differs_from_the_cached_one(self):
def _fake_get_password(service, username):
if service == 'openstack':
return 'old_cached_password'
else:
return 'old_cached_token'
shell.SecretsHelper.tenant_id = 'fake'
shell.SecretsHelper.auth_token = 'fake'
shell.SecretsHelper.management_url = 'fake'
self.run_command('--os-cache list')
fakes.FakeClient.authenticate.assert_called_once_with()
@mock.patch.object(requests, 'request', mock.Mock())
@mock.patch.object(client.HTTPClient, '_save_keys', mock.Mock())
def test_os_cache_token_expired(self):
def _fake_request(method, url, **kwargs):
headers = None
if url == 'new_url/shares/detail':
resp_text = {"shares": []}
return test_utils.TestResponse({
"status_code": 200,
"text": jsonutils.dumps(resp_text),
})
elif url == 'fake/shares/detail':
resp_text = {"unauthorized": {"message": "Unauthorized",
"code": "401"}}
return test_utils.TestResponse({
"status_code": 401,
"text": jsonutils.dumps(resp_text),
})
else:
headers = {
'x-server-management-url': 'new_url',
'x-auth-token': 'new_token',
}
resp_text = 'some_text'
return test_utils.TestResponse({
"status_code": 200,
"text": jsonutils.dumps(resp_text),
"headers": headers
})
client.get_client_class = lambda *_: client_v1.Client
shell.SecretsHelper.tenant_id = 'fake'
shell.SecretsHelper.auth_token = 'fake'
shell.SecretsHelper.management_url = 'fake'
requests.request.side_effect = _fake_request
self.run_command('--os-cache list')
client.HTTPClient._save_keys.assert_called_once_with()
expected_headers = {
'X-Auth-Project-Id': 'project_id',
'User-Agent': 'python-manilaclient',
'Accept': 'application/json',
'X-Auth-Token': 'new_token'}
requests.request.assert_called_with(
'GET',
'new_url/shares/detail',
headers=expected_headers,
verify=True,
)
class SecretsHelperTestCase(test_utils.TestCase):
def setUp(self):
super(SecretsHelperTestCase, self).setUp()
self.cs = client.Client(1, 'user', 'password',
project_id='project',
auth_url='http://111.11.11.11:5000',
region_name='region',
endpoint_type='publicURL',
service_type='share',
service_name='fake',
share_service_name='fake')
self.args = mock.Mock()
self.args.os_cache = True
self.args.reset_cached_password = False
self.helper = shell.SecretsHelper(self.args, self.cs.client)
mock.patch.object(shell.ManilaKeyring, 'set_password',
mock.Mock()).start()
mock.patch.object(shell.ManilaKeyring, 'get_password', mock.Mock(
return_value='fake_token|fake_url|fake_tenant_id')).start()
self.addCleanup(mock.patch.stopall)
def test_validate_string_empty_string(self):
self.assertFalse(self.helper._validate_string(''))
def test_validate_string_void_string(self):
self.assertFalse(self.helper._validate_string(None))
def test_validate_string_good_string(self):
self.assertTrue(self.helper._validate_string('this is a string'))
def test_make_key(self):
expected_key = ('http://111.11.11.11:5000/user/project/region/'
'publicURL/share/fake/fake')
self.assertEqual(self.helper._make_key(), expected_key)
def test_make_key_missing_attrs(self):
self.cs.client.service_name = self.cs.client.region_name = None
expected_key = ('http://111.11.11.11:5000/user/project/?/'
'publicURL/share/?/fake')
self.assertEqual(self.helper._make_key(), expected_key)
def test_save(self):
shell.ManilaKeyring.get_password.return_value = ''
expected_key = ('http://111.11.11.11:5000/user/project/region/'
'publicURL/share/fake/fake')
self.helper.save('fake_token', 'fake_url', 'fake_tenant_id')
shell.ManilaKeyring.set_password.assert_called_once_with(
'manilaclient_auth',
expected_key,
'fake_token|fake_url|fake_tenant_id',
)
def test_save_params_already_cached(self):
self.helper.save('fake_token', 'fake_url', 'fake_tenant_id')
self.assertFalse(shell.ManilaKeyring.set_password.called)
def test_save_missing_params(self):
self.assertRaises(ValueError, self.helper.save, None, 'fake_url',
'fake_tenant_id')
def test_password_os_password(self):
self.args.os_password = 'fake_password'
self.args.os_username = 'fake_username'
self.helper = shell.SecretsHelper(self.args, self.cs.client)
self.assertEqual(self.helper.password, 'fake_password')
@mock.patch.object(shell.SecretsHelper, '_prompt_password', mock.Mock())
def test_password_from_keyboard(self):
self.args.os_password = ''
self.helper = shell.SecretsHelper(self.args, self.cs.client)
shell.SecretsHelper._prompt_password.return_value = None
self.assertRaises(exceptions.CommandError, getattr, self.helper,
'password')
def test_management_url_os_cache_false(self):
self.args.os_cache = False
self.helper = shell.SecretsHelper(self.args, self.cs.client)
self.assertIsNone(self.helper.management_url)
def test_management_url_os_cache_true(self):
self.assertEqual(self.helper.management_url, 'fake_url')
expected_key = ('http://111.11.11.11:5000/user/project/region/'
'publicURL/share/fake/fake')
shell.ManilaKeyring.get_password.assert_called_once_with(
'manilaclient_auth', expected_key)
def test_auth_token_os_cache_false(self):
self.args.os_cache = False
self.helper = shell.SecretsHelper(self.args, self.cs.client)
self.assertIsNone(self.helper.auth_token)
def test_auth_token_os_cache_true(self):
self.assertEqual(self.helper.auth_token, 'fake_token')
expected_key = ('http://111.11.11.11:5000/user/project/region/'
'publicURL/share/fake/fake')
shell.ManilaKeyring.get_password.assert_called_once_with(
'manilaclient_auth', expected_key)
def test_tenant_id_os_cache_false(self):
self.args.os_cache = False
self.helper = shell.SecretsHelper(self.args, self.cs.client)
self.assertIsNone(self.helper.tenant_id)
def test_tenant_id_os_cache_true(self):
self.assertEqual(self.helper.tenant_id, 'fake_tenant_id')
expected_key = ('http://111.11.11.11:5000/user/project/region/'
'publicURL/share/fake/fake')
shell.ManilaKeyring.get_password.assert_called_once_with(
'manilaclient_auth', expected_key)
def test_check_cached_password_os_cache_false(self):
self.args.os_cache = False
self.helper = shell.SecretsHelper(self.args, self.cs.client)
self.assertTrue(self.helper.check_cached_password())
def test_check_cached_password_same_passwords(self):
self.args.os_password = 'user_password'
self.helper = shell.SecretsHelper(self.args, self.cs.client)
shell.ManilaKeyring.get_password.return_value = 'user_password'
self.assertTrue(self.helper.check_cached_password())
def test_check_cached_password_no_cache(self):
shell.ManilaKeyring.get_password.return_value = None
self.assertTrue(self.helper.check_cached_password())
def test_check_cached_password_different_passwords(self):
self.args.os_password = 'new_user_password'
self.helper = shell.SecretsHelper(self.args, self.cs.client)
shell.ManilaKeyring.get_password.return_value = 'cached_password'
self.assertFalse(self.helper.check_cached_password())
def test_check_cached_password_cached_password_deleted(self):
def _fake_get_password(service, username):
if service == 'openstack':
return None
else:
return 'fake_token'
shell.ManilaKeyring.get_password.side_effect = _fake_get_password
self.assertFalse(self.helper.check_cached_password())

View File

@@ -10,7 +10,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from manilaclient import client as httpclient
import warnings
from keystoneclient import adapter
from keystoneclient.v2_0 import client as keystone_client_v2
from keystoneclient.v3 import client as keystone_client_v3
import six
from manilaclient import httpclient
from manilaclient.v1 import limits
from manilaclient.v1 import quota_classes
from manilaclient.v1 import quotas
@@ -30,23 +37,117 @@ class Client(object):
>>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
Or, alternatively, you can create a client instance using the
keystoneclient.session API::
>>> from keystoneclient.auth.identity import v2
>>> from keystoneclient import session
>>> from manilaclient import client
>>> auth = v2.Password(auth_url=AUTH_URL,
username=USERNAME,
password=PASSWORD,
tenant_name=PROJECT_ID)
>>> sess = session.Session(auth=auth)
>>> manila = client.Client(VERSION, session=sess)
Then call methods on its managers::
>>> client.share.list()
>>> client.shares.list()
...
"""
def __init__(self, username, api_key, project_id=None, auth_url='',
insecure=False, timeout=None, tenant_id=None,
proxy_tenant_id=None, proxy_token=None, region_name=None,
def __init__(self, username=None, api_key=None, project_id=None,
auth_url=None, insecure=False, timeout=None, tenant_id=None,
project_name=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='share', service_name=None,
share_service_name=None, retries=None,
http_log_debug=False,
cacert=None, os_cache=False):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
password = api_key
service_type='share', service_name=None, retries=None,
http_log_debug=False, input_auth_token=None, session=None,
auth=None, cacert=None, service_catalog_url=None,
user_agent='python-manilaclient',
use_keyring=False, force_new_token=False,
cached_token_lifetime=300, **kwargs):
service_name = kwargs.get("share_service_name", service_name)
def check_deprecated_arguments():
deprecated = {
'share_service_name': 'service_name',
'proxy_tenant_id': None,
'proxy_token': None,
'os_cache': 'use_keyring'
}
for arg, replacement in six.iteritems(deprecated):
if kwargs.get(arg, None) is None:
continue
replacement_msg = ""
if replacement is not None:
replacement_msg = " Use %s instead." % replacement
msg = "Argument %(arg)s is deprecated.%(repl)s" % {
'arg': arg,
'repl': replacement_msg
}
warnings.warn(msg)
check_deprecated_arguments()
self.project_id = tenant_id if tenant_id is not None else project_id
self.keystone_client = None
self.session = session
if not input_auth_token:
if session:
self.keystone_client = adapter.LegacyJsonAdapter(
session=session,
auth=auth,
interface=endpoint_type,
service_type=service_type,
service_name=service_name,
region_name=region_name)
input_auth_token = self.keystone_client.session.get_token(auth)
else:
self.keystone_client = self._get_keystone_client(
username=username,
api_key=api_key,
auth_url=auth_url,
project_id=self.project_id,
project_name=project_name,
use_keyring=use_keyring,
force_new_token=force_new_token,
stale_duration=cached_token_lifetime)
input_auth_token = self.keystone_client.auth_token
if not input_auth_token:
raise RuntimeError("Not Authorized")
if session and not service_catalog_url:
service_catalog_url = self.keystone_client.session.get_endpoint(
auth, interface=endpoint_type,
service_type=service_type)
elif not service_catalog_url:
catalog = self.keystone_client.service_catalog.get_endpoints(
service_type)
if service_type in catalog:
for e_type, endpoint in catalog.get(service_type)[0].items():
if str(e_type).lower() == str(endpoint_type).lower():
service_catalog_url = endpoint
break
if not service_catalog_url:
raise RuntimeError("Could not find Manila endpoint in catalog")
self.client = httpclient.HTTPClient(service_catalog_url,
input_auth_token,
user_agent,
insecure=insecure,
cacert=cacert,
timeout=timeout,
retries=retries,
http_log_debug=http_log_debug)
self.limits = limits.LimitsManager(self)
self.services = services.ServiceManager(self)
self.security_services = security_services.SecurityServiceManager(self)
@@ -61,32 +162,42 @@ class Client(object):
self.share_types = share_types.ShareTypeManager(self)
self.share_servers = share_servers.ShareServerManager(self)
# Add in any extensions...
if extensions:
for extension in extensions:
if extension.manager_class:
setattr(self, extension.name,
extension.manager_class(self))
self._load_extensions(extensions)
self.client = httpclient.HTTPClient(
username,
password,
project_id,
auth_url,
insecure=insecure,
timeout=timeout,
tenant_id=tenant_id,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
share_service_name=share_service_name,
retries=retries,
http_log_debug=http_log_debug,
cacert=cacert,
os_cache=os_cache)
def _load_extensions(self, extensions):
if not extensions:
return
for extension in extensions:
if extension.manager_class:
setattr(self, extension.name, extension.manager_class(self))
def _get_keystone_client(self, username=None, api_key=None, auth_url=None,
token=None, project_id=None, project_name=None,
use_keyring=False, force_new_token=False,
stale_duration=0):
if not auth_url:
raise RuntimeError("No auth url specified")
if not getattr(self, "keystone_client", None):
imported_client = (keystone_client_v2 if "v2.0" in auth_url
else keystone_client_v3)
self.keystone_client = imported_client.Client(
username=username,
password=api_key,
token=token,
tenant_id=project_id,
tenant_name=project_name,
auth_url=auth_url,
endpoint=auth_url,
use_keyring=use_keyring,
force_new_token=force_new_token,
stale_duration=stale_duration)
self.keystone_client.authenticate()
return self.keystone_client
def authenticate(self):
"""Authenticate against the server.
@@ -97,4 +208,6 @@ class Client(object):
Returns on success; raises :exc:`exceptions.Unauthorized` if the
credentials are wrong.
"""
self.client.authenticate()
warnings.warn("authenticate() method is deprecated. "
"Client automatically makes authentication call "
"in the constructor.")

View File

@@ -132,16 +132,16 @@ def _extract_extra_specs(args):
def do_endpoints(cs, args):
"""Discover endpoints that get returned from the authenticate services."""
catalog = cs.client.service_catalog.catalog
for e in catalog['access']['serviceCatalog']:
catalog = cs.keystone_client.service_catalog.catalog
for e in catalog['serviceCatalog']:
cliutils.print_dict(e['endpoints'][0], e['name'])
def do_credentials(cs, args):
"""Show user credentials returned from auth."""
catalog = cs.client.service_catalog.catalog
cliutils.print_dict(catalog['access']['user'], "User Credentials")
cliutils.print_dict(catalog['access']['token'], "Token")
catalog = cs.keystone_client.service_catalog.catalog
cliutils.print_dict(catalog['user'], "User Credentials")
cliutils.print_dict(catalog['token'], "Token")
_quota_resources = ['shares', 'snapshots', 'gigabytes', 'share_networks']
@@ -184,9 +184,9 @@ def _quota_update(manager, identifier, args):
help='ID of user to list the quotas for.')
def do_quota_show(cs, args):
"""List the quotas for a tenant/user."""
project_id = cs.keystone_client.project_id
if not args.tenant:
_quota_show(cs.quotas.get(cs.client.tenant_id, user_id=args.user))
_quota_show(cs.quotas.get(project_id, user_id=args.user))
else:
_quota_show(cs.quotas.get(args.tenant, user_id=args.user))
@@ -198,9 +198,9 @@ def do_quota_show(cs, args):
help='ID of tenant to list the default quotas for.')
def do_quota_defaults(cs, args):
"""List the default quotas for a tenant."""
project_id = cs.keystone_client.project_id
if not args.tenant:
_quota_show(cs.quotas.defaults(cs.client.tenant_id))
_quota_show(cs.quotas.defaults(project_id))
else:
_quota_show(cs.quotas.defaults(args.tenant))
@@ -265,9 +265,9 @@ def do_quota_delete(cs, args):
The quota will revert back to default.
"""
if not args.tenant:
cs.quotas.delete(cs.client.tenant_id, user_id=args.user)
project_id = cs.keystone_client.project_id
cs.quotas.delete(project_id, user_id=args.user)
else:
cs.quotas.delete(args.tenant, user_id=args.user)

View File

@@ -7,7 +7,6 @@ pbr>=0.6,!=0.7,<1.0
argparse
iso8601>=0.1.9
keyring>=2.1,!=3.3
oslo.config>=1.6.0 # Apache-2.0
oslo.serialization>=1.2.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0
@@ -17,3 +16,4 @@ requests>=2.2.0,!=2.4.0
simplejson>=2.2.0
Babel>=1.3
six>=1.9.0
python-keystoneclient>=1.1.0