diff options
90 files changed, 776 insertions, 2170 deletions
diff --git a/doc/source/index.rst b/doc/source/index.rst index d75547c..08f8145 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -12,13 +12,10 @@ Contents: :maxdepth: 1 man/keystone - using-sessions - using-api-v2 using-api-v3 - + using-sessions authentication-plugins - middlewarearchitecture - + using-api-v2 api/modules Contributing diff --git a/doc/source/man/keystone.rst b/doc/source/man/keystone.rst index d96d89f..10d9d93 100644 --- a/doc/source/man/keystone.rst +++ b/doc/source/man/keystone.rst @@ -1,6 +1,6 @@ -======================================== -:program:`keystone` command line utility -======================================== +============================================================== +:program:`keystone` command line utility (pending deprecation) +============================================================== .. program:: keystone .. highlight:: bash @@ -18,13 +18,21 @@ SYNOPSIS DESCRIPTION =========== +.. WARNING:: + + The :program:`keystone` command line utility is pending deprecation. The + `OpenStackClient unified command line utility + <http://docs.openstack.org/developer/python-openstackclient/>`_ should be + used instead. The :program:`keystone` command line utility only supports V2 + of the Identity API whereas the OSC program supports both V2 and V3. + The :program:`keystone` command line utility interacts with services providing OpenStack Identity API (e.g. Keystone). To communicate with the API, you will need to be authenticated - and the :program:`keystone` provides multiple options for this. -While bootstrapping keystone the authentication is accomplished with a +While bootstrapping Keystone the authentication is accomplished with a shared secret token and the location of the Identity API endpoint. The shared secret token is configured in keystone.conf as "admin_token". @@ -33,7 +41,7 @@ and :option:`--os-endpoint`, or set them in environment variables: .. envvar:: OS_SERVICE_TOKEN - Your keystone administrative token + Your Keystone administrative token .. envvar:: OS_SERVICE_ENDPOINT diff --git a/doc/source/middlewarearchitecture.rst b/doc/source/middlewarearchitecture.rst deleted file mode 100644 index 47ae531..0000000 --- a/doc/source/middlewarearchitecture.rst +++ /dev/null @@ -1,428 +0,0 @@ -.. - Copyright 2011-2013 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. - -======================= -Middleware Architecture -======================= - -Abstract -======== - -The Keystone middleware architecture supports a common authentication protocol -in use between the OpenStack projects. By using keystone as a common -authentication and authorization mechanism, the OpenStack project can plug in -to existing authentication and authorization systems in use by existing -environments. - -In this document, we describe the architecture and responsibilities of the -authentication middleware which acts as the internal API mechanism for -OpenStack projects based on the WSGI standard. - -This documentation describes the implementation in -:class:`keystoneclient.middleware.auth_token` - -Specification Overview -====================== - -'Authentication' is the process of determining that users are who they say they -are. Typically, 'authentication protocols' such as HTTP Basic Auth, Digest -Access, public key, token, etc, are used to verify a user's identity. In this -document, we define an ''authentication component'' as a software module that -implements an authentication protocol for an OpenStack service. OpenStack is -using a token based mechanism to represent authentication and authorization. - -At a high level, an authentication middleware component is a proxy that -intercepts HTTP calls from clients and populates HTTP headers in the request -context for other WSGI middleware or applications to use. The general flow -of the middleware processing is: - -* clear any existing authorization headers to prevent forgery -* collect the token from the existing HTTP request headers -* validate the token - - * if valid, populate additional headers representing the identity that has - been authenticated and authorized - * if invalid, or no token present, reject the request (HTTPUnauthorized) - or pass along a header indicating the request is unauthorized (configurable - in the middleware) - * if the keystone service is unavailable to validate the token, reject - the request with HTTPServiceUnavailable. - -.. _authComponent: - -Authentication Component ------------------------- - -Figure 1. Authentication Component - -.. image:: images/graphs_authComp.svg - :width: 100% - :height: 180 - :alt: An Authentication Component - -The middleware may also be configured to operate in a 'delegated mode'. -In this mode, the decision to reject an unauthenticated client is delegated to -the OpenStack service, as illustrated in :ref:`authComponentDelegated`. - -Here, requests are forwarded to the OpenStack service with an identity status -message that indicates whether the client's identity has been confirmed or is -indeterminate. It is the OpenStack service that decides whether or not a reject -message should be sent to the client. - -.. _authComponentDelegated: - -Authentication Component (Delegated Mode) ------------------------------------------ - -Figure 2. Authentication Component (Delegated Mode) - -.. image:: images/graphs_authCompDelegate.svg - :width: 100% - :height: 180 - :alt: An Authentication Component (Delegated Mode) - -.. _deployStrategies: - -Deployment Strategy -=================== - -The middleware is intended to be used inline with OpenStack wsgi components, -based on the Oslo WSGI middleware class. It is typically deployed -as a configuration element in a paste configuration pipeline of other -middleware components, with the pipeline terminating in the service -application. The middleware conforms to the python WSGI standard [PEP-333]_. -In initializing the middleware, a configuration item (which acts like a python -dictionary) is passed to the middleware with relevant configuration options. - -Configuration -------------- - -The middleware is configured within the config file of the main application as -a WSGI component. Example for the auth_token middleware:: - - [app:myService] - paste.app_factory = myService:app_factory - - [pipeline:main] - pipeline = authtoken myService - - [filter:authtoken] - paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory - - # Prefix to prepend at the beginning of the path (string - # value) - #auth_admin_prefix= - - # Host providing the admin Identity API endpoint (string - # value) - auth_host=127.0.0.1 - - # Port of the admin Identity API endpoint (integer value) - auth_port=35357 - - # Protocol of the admin Identity API endpoint(http or https) - # (string value) - auth_protocol=https - - # Complete public Identity API endpoint (string value) - #auth_uri=<None> - - # API version of the admin Identity API endpoint (string - # value) - #auth_version=<None> - - # Do not handle authorization requests within the middleware, - # but delegate the authorization decision to downstream WSGI - # components (boolean value) - #delay_auth_decision=false - - # Request timeout value for communicating with Identity API - # server. (boolean value) - #http_connect_timeout=<None> - - # How many times are we trying to reconnect when communicating - # with Identity API Server. (integer value) - #http_request_max_retries=3 - - # Single shared secret with the Keystone configuration used - # for bootstrapping a Keystone installation, or otherwise - # bypassing the normal authentication process. (string value) - #admin_token=<None> - - # Keystone account username (string value) - #admin_user=<None> - - # Keystone account password (string value) - admin_password=SuperSekretPassword - - # Keystone service account tenant name to validate user tokens - # (string value) - #admin_tenant_name=admin - - # Env key for the swift cache (string value) - #cache=<None> - - # Required if Keystone server requires client certificate - # (string value) - #certfile=<None> - - # Required if Keystone server requires client certificate - # (string value) - #keyfile=<None> - - # A PEM encoded Certificate Authority to use when verifying - # HTTPs connections. Defaults to system CAs. (string value) - #cafile=<None> - - # Verify HTTPS connections. (boolean value) - #insecure=false - - # Directory used to cache files related to PKI tokens (string - # value) - #signing_dir=<None> - - # If defined, the memcache server(s) to use for caching (list - # value) - # Deprecated group/name - [DEFAULT]/memcache_servers - #memcached_servers=<None> - - # In order to prevent excessive requests and validations, the - # middleware uses an in-memory cache for the tokens the - # Keystone API returns. This is only valid if memcache_servers - # is defined. Set to -1 to disable caching completely. - # (integer value) - #token_cache_time=300 - - # Value only used for unit testing (integer value) - #revocation_cache_time=1 - - # (optional) if defined, indicate whether token data should be - # authenticated or authenticated and encrypted. Acceptable - # values are MAC or ENCRYPT. If MAC, token data is - # authenticated (with HMAC) in the cache. If ENCRYPT, token - # data is encrypted and authenticated in the cache. If the - # value is not one of these options or empty, auth_token will - # raise an exception on initialization. (string value) - #memcache_security_strategy=<None> - - # (optional, mandatory if memcache_security_strategy is - # defined) this string is used for key derivation. (string - # value) - #memcache_secret_key=<None> - - # (optional) indicate whether to set the X-Service-Catalog - # header. If False, middleware will not ask for service - # catalog on token validation and will not set the X-Service- - # Catalog header. (boolean value) - #include_service_catalog=true - - # Used to control the use and type of token binding. Can be - # set to: "disabled" to not check token binding. "permissive" - # (default) to validate binding information if the bind type - # is of a form known to the server and ignore it if not. - # "strict" like "permissive" but if the bind type is unknown - # the token will be rejected. "required" any form of token - # binding is needed to be allowed. Finally the name of a - # binding method that must be present in tokens. (string - # value) - #enforce_token_bind=permissive - -For services which have a separate paste-deploy ini file, auth_token middleware -can be alternatively configured in [keystone_authtoken] section in the main -config file. For example in Nova, all middleware parameters can be removed -from api-paste.ini:: - - [filter:authtoken] - paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory - -and set in nova.conf:: - - [DEFAULT] - ... - auth_strategy=keystone - - [keystone_authtoken] - auth_host = 127.0.0.1 - auth_port = 35357 - auth_protocol = http - admin_user = admin - admin_password = SuperSekretPassword - admin_tenant_name = service - # Any of the options that could be set in api-paste.ini can be set here. - -Note that middleware parameters in paste config take priority, they must be -removed to use values in [keystone_authtoken] section. - -Configuration Options ---------------------- - -* ``auth_admin_prefix``: Prefix to prepend at the beginning of the path -* ``auth_host``: (required) the host providing the keystone service API endpoint - for validating and requesting tokens -* ``auth_port``: (optional, default `35357`) the port used to validate tokens -* ``auth_protocol``: (optional, default `https`) -* ``auth_uri``: (optional, defaults to - `auth_protocol`://`auth_host`:`auth_port`) -* ``auth_version``: API version of the admin Identity API endpoint -* ``delay_auth_decision``: (optional, default `0`) (off). If on, the middleware - will not reject invalid auth requests, but will delegate that decision to - downstream WSGI components. -* ``http_connect_timeout``: (optional) Request timeout value for communicating - with Identity API server. -* ``http_request_max_retries``: (default 3) How many times are we trying to - reconnect when communicating with Identity API Server. -* ``http_handler``: (optional) Allows to pass in the name of a fake - http_handler callback function used instead of `httplib.HTTPConnection` or - `httplib.HTTPSConnection`. Useful for unit testing where network is not - available. - -* ``admin_token``: either this or the following three options are required. If - set, this is a single shared secret with the keystone configuration used to - validate tokens. -* ``admin_user``, ``admin_password``, ``admin_tenant_name``: if ``admin_token`` - is not set, or invalid, then admin_user, admin_password, and - admin_tenant_name are defined as a service account which is expected to have - been previously configured in Keystone to validate user tokens. - -* ``cache``: (optional) Env key for the swift cache - -* ``certfile``: (required, if Keystone server requires client cert) -* ``keyfile``: (required, if Keystone server requires client cert) This can be - the same as the certfile if the certfile includes the private key. -* ``cafile``: (optional, defaults to use system CA bundle) the path to a PEM - encoded CA file/bundle that will be used to verify HTTPS connections. -* ``insecure``: (optional, default `False`) Don't verify HTTPS connections - (overrides `cafile`). - -* ``signing_dir``: (optional) Directory used to cache files related to PKI - tokens - -* ``memcached_servers``: (optional) If defined, the memcache server(s) to use - for caching -* ``token_cache_time``: (default 300) In order to prevent excessive requests - and validations, the middleware uses an in-memory cache for the tokens the - Keystone API returns. This is only valid if memcache_servers s defined. Set - to -1 to disable caching completely. -* ``memcache_security_strategy``: (optional) if defined, indicate whether token - data should be authenticated or authenticated and encrypted. Acceptable - values are MAC or ENCRYPT. If MAC, token data is authenticated (with HMAC) - in the cache. If ENCRYPT, token data is encrypted and authenticated in the - cache. If the value is not one of these options or empty, auth_token will - raise an exception on initialization. -* ``memcache_secret_key``: (mandatory if memcache_security_strategy is defined) - this string is used for key derivation. -* ``include_service_catalog``: (optional, default `True`) Indicate whether to - set the X-Service-Catalog header. If False, middleware will not ask for - service catalog on token validation and will not set the X-Service-Catalog - header. -* ``enforce_token_bind``: (default ``permissive``) Used to control the use and - type of token binding. Can be set to: "disabled" to not check token binding. - "permissive" (default) to validate binding information if the bind type is of - a form known to the server and ignore it if not. "strict" like "permissive" - but if the bind type is unknown the token will be rejected. "required" any - form of token binding is needed to be allowed. Finally the name of a binding - method that must be present in tokens. - -Caching for improved response ------------------------------ - -In order to prevent excessive requests and validations, the middleware uses an -in-memory cache for the tokens the keystone API returns. Keep in mind that -invalidated tokens may continue to work if they are still in the token cache, -so token_cache_time is configurable. For larger deployments, the middleware -also supports memcache based caching. - -* ``memcached_servers``: (optonal) if defined, the memcache server(s) to use for - cacheing. It will be ignored if Swift MemcacheRing is used instead. -* ``token_cache_time``: (optional, default 300 seconds) Set to -1 to disable - caching completely. - -When deploying auth_token middleware with Swift, user may elect -to use Swift MemcacheRing instead of the local Keystone memcache. -The Swift MemcacheRing object is passed in from the request environment -and it defaults to 'swift.cache'. However it could be -different, depending on deployment. To use Swift MemcacheRing, you must -provide the ``cache`` option. - -* ``cache``: (optional) if defined, the environment key where the Swift - MemcacheRing object is stored. - -Memcached and System Time -========================= - -When using `memcached`_ with ``auth_token`` middleware, ensure that the system -time of memcached hosts is set to UTC. Memcached uses the host's system -time in determining whether a key has expired, whereas Keystone sets -key expiry in UTC. The timezone used by Keystone and memcached must -match if key expiry is to behave as expected. - -.. _`memcached`: http://memcached.org/ - -Memcache Protection -=================== - -When using memcached, we are storing user tokens and token validation -information into the cache as raw data. Which means that anyone who -has access to the memcache servers can read and modify data stored -there. To mitigate this risk, ``auth_token`` middleware provides an -option to authenticate and optionally encrypt the token data stored in -the cache. - -* ``memcache_security_strategy``: (optional) if defined, indicate - whether token data should be authenticated or authenticated and - encrypted. Acceptable values are ``MAC`` or ``ENCRYPT``. If ``MAC``, - token data is authenticated (with HMAC) in the cache. If - ``ENCRYPT``, token data is encrypted and authenticated in the - cache. If the value is not one of these options or empty, - ``auth_token`` will raise an exception on initialization. -* ``memcache_secret_key``: (optional, mandatory if - ``memcache_security_strategy`` is defined) this string is used for - key derivation. If ``memcache_security_strategy`` is defined and - ``memcache_secret_key`` is absent, ``auth_token`` will raise an - exception on initialization. - -Exchanging User Information -=========================== - -The middleware expects to find a token representing the user with the header -``X-Auth-Token`` or ``X-Storage-Token``. `X-Storage-Token` is supported for -swift/cloud files and for legacy Rackspace use. If the token isn't present and -the middleware is configured to not delegate auth responsibility, it will -respond to the HTTP request with HTTPUnauthorized, returning the header -``WWW-Authenticate`` with the value `Keystone uri='...'` to indicate where to -request a token. The auth_uri returned is configured with the middleware. - -The authentication middleware extends the HTTP request with the header -``X-Identity-Status``. If a request is successfully authenticated, the value -is set to `Confirmed`. If the middleware is delegating the auth decision to the -service, then the status is set to `Invalid` if the auth request was -unsuccessful. - -Extended the request with additional User Information ------------------------------------------------------ - -:py:class:`keystoneclient.middleware.auth_token.AuthProtocol` extends the -request with additional information if the user has been authenticated. See the -"What we add to the request for use by the OpenStack service" section in -:py:mod:`keystoneclient.middleware.auth_token` for the list of fields set by -the auth_token middleware. - - -References -========== - -.. [PEP-333] pep0333 Phillip J Eby. 'Python Web Server Gateway Interface - v1.0.'' http://www.python.org/dev/peps/pep-0333/. diff --git a/doc/source/using-api-v2.rst b/doc/source/using-api-v2.rst index 192e683..03e76e1 100644 --- a/doc/source/using-api-v2.rst +++ b/doc/source/using-api-v2.rst @@ -1,6 +1,6 @@ -================= -The Client v2 API -================= +======================= +Using the V2 Client API +======================= Introduction ============ @@ -13,7 +13,7 @@ The main concepts in the Identity v2 API are: * services * endpoints -The client v2 API lets you query and make changes through +The V2 client API lets you query and make changes through managers. For example, to manipulate tenants, you interact with a ``keystoneclient.v2_0.tenants.TenantManager`` object. diff --git a/doc/source/using-api-v3.rst b/doc/source/using-api-v3.rst index 1327446..d339636 100644 --- a/doc/source/using-api-v3.rst +++ b/doc/source/using-api-v3.rst @@ -1,23 +1,24 @@ -================= -The Client v3 API -================= +======================= +Using the V3 Client API +======================= Introduction ============ The main concepts in the Identity v3 API are: - * credentials - * domains - * endpoints - * groups - * policies - * projects - * role assignments - * roles - * services - * trusts - * users + * :py:mod:`~keystoneclient.v3.credentials` + * :py:mod:`~keystoneclient.v3.domains` + * :py:mod:`~keystoneclient.v3.endpoints` + * :py:mod:`~keystoneclient.v3.groups` + * :py:mod:`~keystoneclient.v3.policies` + * :py:mod:`~keystoneclient.v3.projects` + * :py:mod:`~keystoneclient.v3.regions` + * :py:mod:`~keystoneclient.v3.role_assignments` + * :py:mod:`~keystoneclient.v3.roles` + * :py:mod:`~keystoneclient.v3.services` + * :py:mod:`~keystoneclient.v3.tokens` + * :py:mod:`~keystoneclient.v3.users` The :py:mod:`keystoneclient.v3.client` API lets you query and make changes through ``managers``. For example, to manipulate a project (formerly diff --git a/doc/source/using-sessions.rst b/doc/source/using-sessions.rst index 099dc70..7d31a2d 100644 --- a/doc/source/using-sessions.rst +++ b/doc/source/using-sessions.rst @@ -75,13 +75,13 @@ fashion by passing the Session object to the client's constructor. Migrating keystoneclient to use a Session ----------------------------------------- -By using a session with a keystonclient Client we define that you have opted in -to new behaviour defined by the session. For example authentication is now -on-demand rather than on creation. To allow this change in behaviour there are -a number of functions that have changed behaviour or are no longer available. +By using a session with a keystoneclient Client we presume that you have opted +in to new behavior defined by the session. For example authentication is now +on-demand rather than on creation. To allow this change in behavior there are +a number of functions that have changed behavior or are no longer available. For example the -:py:meth:`keystoneclient.httpclient.HTTPClient.authenticate` command used +:py:meth:`keystoneclient.httpclient.HTTPClient.authenticate` method used to be able to always re-authenticate the current client and fetch a new token. As this is now controlled by the Session and not the client this has changed, however the function will still exist to provide compatibility with older diff --git a/keystoneclient/_discover.py b/keystoneclient/_discover.py index 05e74d9..07d0ae6 100644 --- a/keystoneclient/_discover.py +++ b/keystoneclient/_discover.py @@ -25,6 +25,7 @@ import logging import re from keystoneclient import exceptions +from keystoneclient.i18n import _, _LI, _LW from keystoneclient import utils @@ -65,7 +66,7 @@ def get_version_data(session, url, authenticated=None): pass err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text - msg = 'Invalid Response - Bad version data returned: %s' % err_text + msg = _('Invalid Response - Bad version data returned: %s') % err_text raise exceptions.DiscoveryFailure(msg) @@ -99,7 +100,7 @@ def normalize_version_number(version): except Exception: pass - raise TypeError('Invalid version specified: %s' % version) + raise TypeError(_('Invalid version specified: %s') % version) def version_match(required, candidate): @@ -161,8 +162,8 @@ class Discover(object): try: status = v['status'] except KeyError: - _LOGGER.warning('Skipping over invalid version data. ' - 'No stability status in version.') + _LOGGER.warning(_LW('Skipping over invalid version data. ' + 'No stability status in version.')) continue status = status.lower() @@ -201,13 +202,14 @@ class Discover(object): try: version_str = v['id'] except KeyError: - _LOGGER.info('Skipping invalid version data. Missing ID.') + _LOGGER.info(_LI('Skipping invalid version data. Missing ID.')) continue try: links = v['links'] except KeyError: - _LOGGER.info('Skipping invalid version data. Missing links') + _LOGGER.info( + _LI('Skipping invalid version data. Missing links')) continue version_number = normalize_version_number(version_str) @@ -217,15 +219,15 @@ class Discover(object): rel = link['rel'] url = link['href'] except (KeyError, TypeError): - _LOGGER.info('Skipping invalid version link. ' - 'Missing link URL or relationship.') + _LOGGER.info(_LI('Skipping invalid version link. ' + 'Missing link URL or relationship.')) continue if rel.lower() == 'self': break else: - _LOGGER.info('Skipping invalid version data. ' - 'Missing link to endpoint.') + _LOGGER.info(_LI('Skipping invalid version data. ' + 'Missing link to endpoint.')) continue versions.append({'version': version_number, diff --git a/keystoneclient/access.py b/keystoneclient/access.py index 95a041e..8d85c68 100644 --- a/keystoneclient/access.py +++ b/keystoneclient/access.py @@ -17,7 +17,9 @@ import datetime -from keystoneclient.openstack.common import timeutils +from oslo.utils import timeutils + +from keystoneclient.i18n import _ from keystoneclient import service_catalog @@ -62,7 +64,7 @@ class AccessInfo(dict): else: auth_ref = AccessInfoV2(**kwargs) else: - raise NotImplementedError('Unrecognized auth response') + raise NotImplementedError(_('Unrecognized auth response')) else: auth_ref = AccessInfoV2(**kwargs) diff --git a/keystoneclient/adapter.py b/keystoneclient/adapter.py index b75b713..49dd198 100644 --- a/keystoneclient/adapter.py +++ b/keystoneclient/adapter.py @@ -10,7 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from keystoneclient.openstack.common import jsonutils +from oslo.serialization import jsonutils + from keystoneclient import utils @@ -93,7 +94,8 @@ class Adapter(object): on the session. (optional) :type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin` - :raises AuthorizationFailure: if a new token fetch fails. + :raises keystoneclient.exceptions.AuthorizationFailure: if a new token + fetch fails. :returns: A valid token. :rtype: string @@ -107,7 +109,8 @@ class Adapter(object): the session. (optional) :type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin` - :raises MissingAuthPlugin: if a plugin is not available. + :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not + available. :returns: An endpoint if available or None. :rtype: string diff --git a/keystoneclient/auth/base.py b/keystoneclient/auth/base.py index a766a0b..1f4ce29 100644 --- a/keystoneclient/auth/base.py +++ b/keystoneclient/auth/base.py @@ -17,6 +17,8 @@ import six import stevedore from keystoneclient import exceptions +from keystoneclient.i18n import _ + # NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be # requested from get_endpoint. If a plugin receives this as the value of @@ -33,14 +35,15 @@ def get_plugin_class(name): :returns: An auth plugin class. - :raises exceptions.NoMatchingPlugin: if a plugin cannot be created. + :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be + created. """ try: mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE, name=name, invoke_on_load=False) except RuntimeError: - msg = 'The plugin %s could not be found' % name + msg = _('The plugin %s could not be found') % name raise exceptions.NoMatchingPlugin(msg) return mgr.driver diff --git a/keystoneclient/auth/cli.py b/keystoneclient/auth/cli.py index f1f2de3..ce4f11f 100644 --- a/keystoneclient/auth/cli.py +++ b/keystoneclient/auth/cli.py @@ -31,7 +31,8 @@ def register_argparse_arguments(parser, argv, default=None): :returns: The plugin class that will be loaded or None if not provided. - :raises exceptions.NoMatchingPlugin: if a plugin cannot be created. + :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be + created. """ in_parser = argparse.ArgumentParser(add_help=False) env_plugin = os.environ.get('OS_AUTH_PLUGIN', default) @@ -68,7 +69,8 @@ def load_from_argparse_arguments(namespace, **kwargs): :returns: An auth plugin, or None if a name is not provided. - :raises exceptions.NoMatchingPlugin: if a plugin cannot be created. + :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be + created. """ if not namespace.os_auth_plugin: return None diff --git a/keystoneclient/auth/conf.py b/keystoneclient/auth/conf.py index ad90281..0b68184 100644 --- a/keystoneclient/auth/conf.py +++ b/keystoneclient/auth/conf.py @@ -91,7 +91,8 @@ def load_from_conf_options(conf, group, **kwargs): :returns: An authentication Plugin or None if a name is not provided :rtype: plugin - :raises exceptions.NoMatchingPlugin: if a plugin cannot be created. + :raises keystoneclient.exceptions.NoMatchingPlugin: if a plugin cannot be + created. """ # NOTE(jamielennox): plugins are allowed to specify a 'section' which is # the group that auth options should be taken from. If not present they diff --git a/keystoneclient/auth/identity/base.py b/keystoneclient/auth/identity/base.py index 1169cf6..94b0712 100644 --- a/keystoneclient/auth/identity/base.py +++ b/keystoneclient/auth/identity/base.py @@ -19,6 +19,7 @@ import six from keystoneclient import _discover from keystoneclient.auth import base from keystoneclient import exceptions +from keystoneclient.i18n import _LW from keystoneclient import utils LOG = logging.getLogger(__name__) @@ -73,8 +74,11 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): when invoked. If you are looking to just retrieve the current auth data then you should use get_access. - :raises InvalidResponse: The response returned wasn't appropriate. - :raises HttpError: An error from an invalid HTTP response. + :raises keystoneclient.exceptions.InvalidResponse: The response + returned wasn't + appropriate. + :raises keystoneclient.exceptions.HttpError: An error from an invalid + HTTP response. :returns: Token access information. :rtype: :py:class:`keystoneclient.access.AccessInfo` @@ -85,7 +89,8 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): If a valid token is not present then a new one will be fetched. - :raises HttpError: An error from an invalid HTTP response. + :raises keystoneclient.exceptions.HttpError: An error from an invalid + HTTP response. :return: A valid token. :rtype: string @@ -120,7 +125,8 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): If a valid AccessInfo is present then it is returned otherwise a new one will be fetched. - :raises HttpError: An error from an invalid HTTP response. + :raises keystoneclient.exceptions.HttpError: An error from an invalid + HTTP response. :returns: Valid AccessInfo :rtype: :py:class:`keystoneclient.access.AccessInfo` @@ -173,7 +179,8 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): :param tuple version: The minimum version number required for this endpoint. (optional) - :raises HttpError: An error from an invalid HTTP response. + :raises keystoneclient.exceptions.HttpError: An error from an invalid + HTTP response. :return: A valid endpoint URL or None if not available. :rtype: string or None @@ -186,9 +193,9 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): return self.auth_url if not service_type: - LOG.warn('Plugin cannot return an endpoint without knowing the ' - 'service type that is required. Add service_type to ' - 'endpoint filtering data.') + LOG.warn(_LW('Plugin cannot return an endpoint without knowing ' + 'the service type that is required. Add service_type ' + 'to endpoint filtering data.')) return None if not interface: @@ -221,8 +228,9 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): # NOTE(jamielennox): Again if we can't contact the server we fall # back to just returning the URL from the catalog. This may not be # the best default but we need it for now. - LOG.warn('Failed to contact the endpoint at %s for discovery. ' - 'Fallback to using that endpoint as the base url.', url) + LOG.warn(_LW('Failed to contact the endpoint at %s for discovery. ' + 'Fallback to using that endpoint as the base url.'), + url) else: url = disc.url_for(version) @@ -245,8 +253,10 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): (optional) Defaults to None (use a token if a plugin is installed). - :raises: DiscoveryFailure if for some reason the lookup fails. - :raises: HttpError An error from an invalid HTTP response. + :raises keystoneclient.exceptions.DiscoveryFailure: if for some reason + the lookup fails. + :raises keystoneclient.exceptions.HttpError: An error from an invalid + HTTP response. :returns: A discovery object with the results of looking up that URL. """ diff --git a/keystoneclient/auth/identity/generic/base.py b/keystoneclient/auth/identity/generic/base.py index 94d48ec..631eebd 100644 --- a/keystoneclient/auth/identity/generic/base.py +++ b/keystoneclient/auth/identity/generic/base.py @@ -20,6 +20,8 @@ import six.moves.urllib.parse as urlparse from keystoneclient import _discover from keystoneclient.auth.identity import base from keystoneclient import exceptions +from keystoneclient.i18n import _, _LW + LOG = logging.getLogger(__name__) @@ -127,9 +129,9 @@ class BaseGenericPlugin(base.BaseIdentityPlugin): except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): - LOG.warn('Discovering versions from the identity service failed ' - 'when creating the password plugin. Attempting to ' - 'determine version from URL.') + LOG.warn(_LW('Discovering versions from the identity service ' + 'failed when creating the password plugin. ' + 'Attempting to determine version from URL.')) url_parts = urlparse.urlparse(self.auth_url) path = url_parts.path.lower() @@ -163,7 +165,7 @@ class BaseGenericPlugin(base.BaseIdentityPlugin): return plugin # so there were no URLs that i could use for auth of any version. - msg = 'Could not determine a suitable URL for the plugin' + msg = _('Could not determine a suitable URL for the plugin') raise exceptions.DiscoveryFailure(msg) def get_auth_ref(self, session, **kwargs): diff --git a/keystoneclient/auth/identity/v3.py b/keystoneclient/auth/identity/v3.py index 6755bef..0749924 100644 --- a/keystoneclient/auth/identity/v3.py +++ b/keystoneclient/auth/identity/v3.py @@ -19,6 +19,7 @@ import six from keystoneclient import access from keystoneclient.auth.identity import base from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils _logger = logging.getLogger(__name__) @@ -84,18 +85,17 @@ class Auth(base.BaseIdentityPlugin): ident[name] = auth_data if not ident: - raise exceptions.AuthorizationFailure('Authentication method ' - 'required (e.g. password)') + raise exceptions.AuthorizationFailure( + _('Authentication method required (e.g. password)')) mutual_exclusion = [bool(self.domain_id or self.domain_name), bool(self.project_id or self.project_name), bool(self.trust_id)] if sum(mutual_exclusion) > 1: - raise exceptions.AuthorizationFailure('Authentication cannot be ' - 'scoped to multiple ' - 'targets. Pick one of: ' - 'project, domain or trust') + raise exceptions.AuthorizationFailure( + _('Authentication cannot be scoped to multiple targets. Pick ' + 'one of: project, domain or trust')) if self.domain_id: body['auth']['scope'] = {'domain': {'id': self.domain_id}} @@ -165,7 +165,7 @@ class AuthMethod(object): setattr(self, param, kwargs.pop(param, None)) if kwargs: - msg = "Unexpected Attributes: %s" % ", ".join(kwargs.keys()) + msg = _("Unexpected Attributes: %s") % ", ".join(kwargs.keys()) raise AttributeError(msg) @classmethod diff --git a/keystoneclient/base.py b/keystoneclient/base.py index 2571a37..030afef 100644 --- a/keystoneclient/base.py +++ b/keystoneclient/base.py @@ -27,6 +27,7 @@ from six.moves import urllib from keystoneclient import auth from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient.openstack.common.apiclient import base @@ -219,7 +220,7 @@ class Manager(object): management=management, **kwargs) except KeyError: - raise exceptions.ClientException("Invalid update method: %s" + raise exceptions.ClientException(_("Invalid update method: %s") % method) # PUT requests may not return a body if body: @@ -244,7 +245,8 @@ class ManagerWithFind(Manager): num = len(rl) if num == 0: - msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) + msg = _("No %(name)s matching %(kwargs)s.") % { + 'name': self.resource_class.__name__, 'kwargs': kwargs} raise exceptions.NotFound(404, msg) elif num > 1: raise exceptions.NoUniqueMatch @@ -395,7 +397,8 @@ class CrudManager(Manager): num = len(rl) if num == 0: - msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) + msg = _("No %(name)s matching %(kwargs)s.") % { + 'name': self.resource_class.__name__, 'kwargs': kwargs} raise exceptions.NotFound(404, msg) elif num > 1: raise exceptions.NoUniqueMatch diff --git a/keystoneclient/client.py b/keystoneclient/client.py index c58009b..8b6a6b0 100644 --- a/keystoneclient/client.py +++ b/keystoneclient/client.py @@ -36,8 +36,10 @@ def Client(version=None, unstable=False, session=None, **kwargs): :returns: New keystone client object (keystoneclient.v2_0.Client or keystoneclient.v3.Client). - :raises: DiscoveryFailure if the server's response is invalid - :raises: VersionNotAvailable if a suitable client cannot be found. + :raises keystoneclient.exceptions.DiscoveryFailure: if the server's + response is invalid + :raises keystoneclient.exceptions.VersionNotAvailable: if a suitable client + cannot be found. """ if not session: session = client_session.Session.construct(kwargs) diff --git a/keystoneclient/common/cms.py b/keystoneclient/common/cms.py index 1c343f6..d49a0c5 100644 --- a/keystoneclient/common/cms.py +++ b/keystoneclient/common/cms.py @@ -28,6 +28,7 @@ import zlib import six from keystoneclient import exceptions +from keystoneclient.i18n import _, _LE, _LW subprocess = None @@ -38,6 +39,14 @@ PKIZ_CMS_FORM = 'DER' PKI_ASN1_FORM = 'PEM' +# The openssl cms command exits with these status codes. +# See https://www.openssl.org/docs/apps/cms.html#EXIT_CODES +class OpensslCmsExitStatus: + SUCCESS = 0 + INPUT_FILE_READ_ERROR = 2 + CREATE_CMS_READ_MIME_ERROR = 3 + + def _ensure_subprocess(): # NOTE(vish): late loading subprocess so we can # use the green version if we are in @@ -73,19 +82,12 @@ def _check_files_accessible(files): except IOError as e: # Catching IOError means there is an issue with # the given file. - err = ('Hit OSError in _process_communicate_handle_oserror()\n' - 'Likely due to %s: %s') % (try_file, e.strerror) + err = _('Hit OSError in _process_communicate_handle_oserror()\n' + 'Likely due to %(file)s: %(error)s') % {'file': try_file, + 'error': e.strerror} # Emulate openssl behavior, which returns with code 2 when - # access to a file failed: - - # You can get more from - # http://www.openssl.org/docs/apps/cms.html#EXIT_CODES - # - # $ openssl cms -verify -certfile not_exist_file -CAfile \ - # not_exist_file -inform PEM -nosmimecap -nodetach \ - # -nocerts -noattr - # Error opening certificate file not_exist_file - retcode = 2 + # access to a file failed. + retcode = OpensslCmsExitStatus.INPUT_FILE_READ_ERROR return retcode, err @@ -122,8 +124,9 @@ def _encoding_for_form(inform): elif inform == PKIZ_CMS_FORM: encoding = 'hex' else: - raise ValueError('"inform" must be either %s or %s' % - (PKI_ASN1_FORM, PKIZ_CMS_FORM)) + raise ValueError( + _('"inform" must be one of: %s') % ','.join((PKI_ASN1_FORM, + PKIZ_CMS_FORM))) return encoding @@ -132,8 +135,10 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name, inform=PKI_ASN1_FORM): """Verifies the signature of the contents IAW CMS syntax. - :raises: subprocess.CalledProcessError - :raises: CertificateConfigError if certificate is not configured properly. + :raises subprocess.CalledProcessError: + :raises keystoneclient.exceptions.CertificateConfigError: if certificate + is not configured + properly. """ _ensure_subprocess() if isinstance(formatted, six.string_types): @@ -148,7 +153,8 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name, '-nocerts', '-noattr'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + close_fds=True) output, err, retcode = _process_communicate_handle_oserror( process, data, (signing_cert_file_name, ca_file_name)) @@ -165,12 +171,12 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name, # -nocerts -noattr # Error opening certificate file not_exist_file # - if retcode == 2: + if retcode == OpensslCmsExitStatus.INPUT_FILE_READ_ERROR: if err.startswith('Error reading S/MIME message'): raise exceptions.CMSError(err) else: raise exceptions.CertificateConfigError(err) - elif retcode: + elif retcode != OpensslCmsExitStatus.SUCCESS: # NOTE(dmllr): Python 2.6 compatibility: # CalledProcessError did not have output keyword argument e = subprocess.CalledProcessError(retcode, 'openssl') @@ -295,8 +301,8 @@ def is_asn1_token(token): def is_ans1_token(token): """Deprecated. Use is_asn1_token() instead.""" - LOG.warning('The function is_ans1_token() is deprecated, ' - 'use is_asn1_token() instead.') + LOG.warning(_LW('The function is_ans1_token() is deprecated, ' + 'use is_asn1_token() instead.')) return is_asn1_token(token) @@ -336,19 +342,19 @@ def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name, '-md', 'sha256', ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + close_fds=True) output, err, retcode = _process_communicate_handle_oserror( process, data, (signing_cert_file_name, signing_key_file_name)) - if retcode or ('Error' in err): - LOG.error('Signing error: %s', err) - if retcode == 3: - LOG.error('Signing error: Unable to load certificate - ' - 'ensure you have configured PKI with ' - '"keystone-manage pki_setup"') + if retcode != OpensslCmsExitStatus.SUCCESS or ('Error' in err): + if retcode == OpensslCmsExitStatus.CREATE_CMS_READ_MIME_ERROR: + LOG.error(_LE('Signing error: Unable to load certificate - ' + 'ensure you have configured PKI with ' + '"keystone-manage pki_setup"')) else: - LOG.error('Signing error: %s', err) + LOG.error(_LE('Signing error: %s'), err) raise subprocess.CalledProcessError(retcode, 'openssl') if outform == PKI_ASN1_FORM: return output.decode('utf-8') diff --git a/keystoneclient/contrib/auth/v3/saml2.py b/keystoneclient/contrib/auth/v3/saml2.py index bb32c15..5458ac1 100644 --- a/keystoneclient/contrib/auth/v3/saml2.py +++ b/keystoneclient/contrib/auth/v3/saml2.py @@ -20,6 +20,7 @@ from six.moves import urllib from keystoneclient import access from keystoneclient.auth.identity import v3 from keystoneclient import exceptions +from keystoneclient.i18n import _ class _BaseSAMLPlugin(v3.AuthConstructor): @@ -30,7 +31,7 @@ class _BaseSAMLPlugin(v3.AuthConstructor): @staticmethod def _first(_list): if len(_list) != 1: - raise IndexError("Only single element list is acceptable") + raise IndexError(_("Only single element list is acceptable")) return _list[0] @staticmethod @@ -80,8 +81,8 @@ class Saml2UnscopedTokenAuthMethod(v3.AuthMethod): _method_parameters = [] def get_auth_data(self, session, auth, headers, **kwargs): - raise exceptions.MethodNotImplemented(('This method should never ' - 'be called')) + raise exceptions.MethodNotImplemented(_('This method should never ' + 'be called')) class Saml2UnscopedToken(_BaseSAMLPlugin): @@ -211,9 +212,9 @@ class Saml2UnscopedToken(_BaseSAMLPlugin): authenticated=False) # prepare error message and raise an exception. - msg = ("Consumer URLs from Service Provider %(service_provider)s " - "%(sp_consumer_url)s and Identity Provider " - "%(identity_provider)s %(idp_consumer_url)s are not equal") + msg = _("Consumer URLs from Service Provider %(service_provider)s " + "%(sp_consumer_url)s and Identity Provider " + "%(identity_provider)s %(idp_consumer_url)s are not equal") msg = msg % { 'service_provider': self.token_url, 'sp_consumer_url': sp_response_consumer_url, @@ -257,8 +258,8 @@ class Saml2UnscopedToken(_BaseSAMLPlugin): try: self.saml2_authn_request = etree.XML(sp_response.content) except etree.XMLSyntaxError as e: - msg = ("SAML2: Error parsing XML returned " - "from Service Provider, reason: %s" % e) + msg = _("SAML2: Error parsing XML returned " + "from Service Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) relay_state = self.saml2_authn_request.xpath( @@ -288,8 +289,8 @@ class Saml2UnscopedToken(_BaseSAMLPlugin): try: self.saml2_idp_authn_response = etree.XML(idp_response.content) except etree.XMLSyntaxError as e: - msg = ("SAML2: Error parsing XML returned " - "from Identity Provider, reason: %s" % e) + msg = _("SAML2: Error parsing XML returned " + "from Identity Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) idp_response_consumer_url = self.saml2_idp_authn_response.xpath( @@ -506,10 +507,6 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): ]) return options - @property - def _uuid4(self): - return str(uuid.uuid4()) - def _cookies(self, session): """Check if cookie jar is not empty. @@ -520,7 +517,7 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): :param session :returns: True if cookie jar is nonempty, False otherwise - :raises: AttributeError in case cookies are not find anywhere + :raises AttributeError: in case cookies are not find anywhere """ try: @@ -591,7 +588,7 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): messageID = etree.SubElement( header, '{http://www.w3.org/2005/08/addressing}MessageID') - messageID.text = 'urn:uuid:' + self._uuid4 + messageID.text = 'urn:uuid:' + uuid.uuid4().hex replyID = etree.SubElement( header, '{http://www.w3.org/2005/08/addressing}ReplyTo') address = etree.SubElement( @@ -632,7 +629,7 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): 'wss-wssecurity-secext-1.0.xsd}UsernameToken') usernametoken.set( ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-' - 'wssecurity-utility-1.0.xsd}u'), "uuid-%s-1" % self._uuid4) + 'wssecurity-utility-1.0.xsd}u'), "uuid-%s-1" % uuid.uuid4().hex) username = etree.SubElement( usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-' @@ -705,11 +702,12 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session - :raises: exceptions.AuthorizationFailure when HTTP response from the - ADFS server is not a valid XML ADFS security token. - :raises: exceptions.InternalServerError: If response status code is - HTTP 500 and the response XML cannot be recognized. - + :raises keystoneclient.exceptions.AuthorizationFailure: when HTTP + response from the ADFS server is not a valid XML ADFS security + token. + :raises keystoneclient.exceptions.InternalServerError: If response + status code is HTTP 500 and the response XML cannot be + recognized. """ def _get_failure(e): @@ -737,8 +735,8 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): except exceptions.InternalServerError as e: reason = _get_failure(e) raise exceptions.AuthorizationFailure(reason) - msg = ("Error parsing XML returned from " - "the ADFS Identity Provider, reason: %s") + msg = _("Error parsing XML returned from " + "the ADFS Identity Provider, reason: %s") self.adfs_token = self.str_to_xml(response.content, msg) def _prepare_sp_request(self): @@ -798,14 +796,15 @@ class ADFSUnscopedToken(_BaseSAMLPlugin): :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session - :raises: exceptions.AuthorizationFailure: in case session object - has empty cookie jar. + :raises keystoneclient.exceptions.AuthorizationFailure: in case session + object has empty cookie jar. """ if self._cookies(session) is False: raise exceptions.AuthorizationFailure( - "Session object doesn't contain a cookie, therefore you are " - "not allowed to enter the Identity Provider's protected area.") + _("Session object doesn't contain a cookie, therefore you are " + "not allowed to enter the Identity Provider's protected " + "area.")) self.authenticated_response = session.get(self.token_url, authenticated=False) @@ -884,4 +883,4 @@ class Saml2ScopedToken(v3.Token): super(Saml2ScopedToken, self).__init__(auth_url, token, **kwargs) if not (self.project_id or self.domain_id): raise exceptions.ValidationError( - 'Neither project nor domain specified') + _('Neither project nor domain specified')) diff --git a/keystoneclient/contrib/ec2/utils.py b/keystoneclient/contrib/ec2/utils.py index 899b95a..d093b6e 100644 --- a/keystoneclient/contrib/ec2/utils.py +++ b/keystoneclient/contrib/ec2/utils.py @@ -24,6 +24,8 @@ import re import six from six.moves import urllib +from keystoneclient.i18n import _ + class Ec2Signer(object): """Utility class which adds allows a request to be signed with an AWS style @@ -91,10 +93,10 @@ class Ec2Signer(object): credentials['body_hash']) if signature_version is not None: - raise Exception('Unknown signature version: %s' % + raise Exception(_('Unknown signature version: %s') % signature_version) else: - raise Exception('Unexpected signature format') + raise Exception(_('Unexpected signature format')) @staticmethod def _get_utf8_value(value): @@ -257,7 +259,7 @@ class Ec2Signer(object): credential_date = credential_split[1] param_date = date_param() if not param_date.startswith(credential_date): - raise Exception('Request date mismatch error') + raise Exception(_('Request date mismatch error')) # Create the string to sign # http://docs.aws.amazon.com/general/latest/gr/ diff --git a/keystoneclient/contrib/revoke/model.py b/keystoneclient/contrib/revoke/model.py index 18d5034..c3f3864 100644 --- a/keystoneclient/contrib/revoke/model.py +++ b/keystoneclient/contrib/revoke/model.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from keystoneclient.openstack.common import timeutils +from oslo.utils import timeutils # The set of attributes common between the RevokeEvent # and the dictionaries created from the token Data. diff --git a/keystoneclient/discover.py b/keystoneclient/discover.py index ee73ddc..695d345 100644 --- a/keystoneclient/discover.py +++ b/keystoneclient/discover.py @@ -16,6 +16,7 @@ import six from keystoneclient import _discover from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import session as client_session from keystoneclient import utils from keystoneclient.v2_0 import client as v2_client @@ -122,9 +123,9 @@ class Discover(_discover.Discover): url = auth_url if not url: - raise exceptions.DiscoveryFailure('Not enough information to ' - 'determine URL. Provide either ' - 'auth_url or endpoint') + raise exceptions.DiscoveryFailure( + _('Not enough information to determine URL. Provide either ' + 'auth_url or endpoint')) self._client_kwargs = kwargs super(Discover, self).__init__(session, url, @@ -214,10 +215,11 @@ class Discover(_discover.Discover): version_data = all_versions[-1] if not version_data: - msg = 'Could not find a suitable endpoint' + msg = _('Could not find a suitable endpoint') if version: - msg += ' for client version: %s' % str(version) + msg = _('Could not find a suitable endpoint for client ' + 'version: %s') % str(version) raise exceptions.VersionNotAvailable(msg) @@ -229,7 +231,7 @@ class Discover(_discover.Discover): client_class = _CLIENT_VERSIONS[version_data['version'][0]] except KeyError: version = '.'.join(str(v) for v in version_data['version']) - msg = 'No client available for version: %s' % version + msg = _('No client available for version: %s') % version raise exceptions.DiscoveryFailure(msg) # kwargs should take priority over stored kwargs. @@ -262,8 +264,10 @@ class Discover(_discover.Discover): :returns: An instantiated identity client object. - :raises: DiscoveryFailure if the server response is invalid - :raises: VersionNotAvailable if a suitable client cannot be found. + :raises keystoneclient.exceptions.DiscoveryFailure: if the server + response is invalid + :raises keystoneclient.exceptions.VersionNotAvailable: if a suitable + client cannot be found. """ version_data = self._calculate_version(version, unstable) return self._create_client(version_data, **kwargs) diff --git a/keystoneclient/exceptions.py b/keystoneclient/exceptions.py index 5ad84c0..241d27b 100644 --- a/keystoneclient/exceptions.py +++ b/keystoneclient/exceptions.py @@ -14,10 +14,19 @@ # under the License. """ Exception definitions. + +.. py:exception:: AuthorizationFailure + +.. py:exception:: ClientException + +.. py:exception:: HttpError + +.. py:exception:: Unauthorized + """ -#flake8: noqa -from keystoneclient.openstack.common.apiclient.exceptions import * +from keystoneclient.i18n import _ +from keystoneclient.openstack.common.apiclient.exceptions import * # noqa # NOTE(akurilin): This alias should be left here to support backwards # compatibility until we are sure that usage of these exceptions in @@ -29,18 +38,18 @@ HTTPError = HttpError class CertificateConfigError(Exception): - """Error reading the certificate""" + """Error reading the certificate.""" def __init__(self, output): self.output = output - msg = 'Unable to load certificate.' + msg = _('Unable to load certificate.') super(CertificateConfigError, self).__init__(msg) class CMSError(Exception): - """Error reading the certificate""" + """Error reading the certificate.""" def __init__(self, output): self.output = output - msg = 'Unable to sign or verify data.' + msg = _('Unable to sign or verify data.') super(CMSError, self).__init__(msg) @@ -71,7 +80,8 @@ class MissingAuthPlugin(ClientException): class NoMatchingPlugin(ClientException): """There were no auth plugins that could be created from the parameters - provided.""" + provided. + """ class InvalidResponse(ClientException): diff --git a/keystoneclient/fixture/discovery.py b/keystoneclient/fixture/discovery.py index 94cfe11..c7edf15 100644 --- a/keystoneclient/fixture/discovery.py +++ b/keystoneclient/fixture/discovery.py @@ -12,7 +12,8 @@ import datetime -from keystoneclient.openstack.common import timeutils +from oslo.utils import timeutils + from keystoneclient import utils __all__ = ['DiscoveryList', diff --git a/keystoneclient/fixture/v2.py b/keystoneclient/fixture/v2.py index 467ad4c..3a107f4 100644 --- a/keystoneclient/fixture/v2.py +++ b/keystoneclient/fixture/v2.py @@ -13,8 +13,9 @@ import datetime import uuid +from oslo.utils import timeutils + from keystoneclient.fixture import exception -from keystoneclient.openstack.common import timeutils class _Service(dict): diff --git a/keystoneclient/fixture/v3.py b/keystoneclient/fixture/v3.py index e40b314..4f0d581 100644 --- a/keystoneclient/fixture/v3.py +++ b/keystoneclient/fixture/v3.py @@ -13,8 +13,9 @@ import datetime import uuid +from oslo.utils import timeutils + from keystoneclient.fixture import exception -from keystoneclient.openstack.common import timeutils class _Service(dict): diff --git a/keystoneclient/generic/client.py b/keystoneclient/generic/client.py index 3c81268..7ca39fb 100644 --- a/keystoneclient/generic/client.py +++ b/keystoneclient/generic/client.py @@ -19,6 +19,7 @@ from six.moves.urllib import parse as urlparse from keystoneclient import exceptions from keystoneclient import httpclient +from keystoneclient.i18n import _, _LE _logger = logging.getLogger(__name__) @@ -94,7 +95,7 @@ class Client(httpclient.HTTPClient): try: results = {} if 'version' in body: - results['message'] = "Keystone found at %s" % url + results['message'] = _("Keystone found at %s") % url version = body['version'] # Stable/diablo incorrect format id, status, version_url = ( @@ -105,7 +106,7 @@ class Client(httpclient.HTTPClient): return results elif 'versions' in body: # Correct format - results['message'] = "Keystone found at %s" % url + results['message'] = _("Keystone found at %s") % url for version in body['versions']['values']: id, status, version_url = ( self._get_version_info(version, url)) @@ -114,8 +115,8 @@ class Client(httpclient.HTTPClient): "url": version_url} return results else: - results['message'] = ("Unrecognized response from %s" - % url) + results['message'] = ( + _("Unrecognized response from %s") % url) return results except KeyError: raise exceptions.AuthorizationFailure() @@ -123,8 +124,8 @@ class Client(httpclient.HTTPClient): return self._check_keystone_versions(resp['location']) else: raise exceptions.from_response(resp, "GET", url) - except Exception as e: - _logger.exception(e) + except Exception: + _logger.exception(_LE('Failed to detect available versions.')) def discover_extensions(self, url=None): """Discover Keystone extensions supported. @@ -159,7 +160,7 @@ class Client(httpclient.HTTPClient): extensions = body['extensions'] else: return dict(message=( - 'Unrecognized extensions response from %s' % url)) + _('Unrecognized extensions response from %s') % url)) return dict(self._get_extension_info(e) for e in extensions) elif resp.status_code == 305: @@ -167,8 +168,8 @@ class Client(httpclient.HTTPClient): else: raise exceptions.from_response( resp, "GET", "%sextensions" % url) - except Exception as e: - _logger.exception(e) + except Exception: + _logger.exception(_LE('Failed to check keystone extensions.')) @staticmethod def _get_version_info(version, root_url): diff --git a/keystoneclient/generic/shell.py b/keystoneclient/generic/shell.py index 4f9bd33..e5330a5 100644 --- a/keystoneclient/generic/shell.py +++ b/keystoneclient/generic/shell.py @@ -16,6 +16,7 @@ import six from keystoneclient.generic import client +from keystoneclient.i18n import _ from keystoneclient import utils @@ -37,13 +38,14 @@ def do_discover(cs, args): print(versions['message']) for key, version in six.iteritems(versions): if key != 'message': - print(" - supports version %s (%s) here %s" % - (version['id'], version['status'], version['url'])) + print(_(" - supports version %(id)s (%(status)s) here " + "%(url)s") % + version) extensions = cs.discover_extensions(version['url']) if extensions: for key, extension in six.iteritems(extensions): if key != 'message': - print(" - and %s: %s" % - (key, extension)) + print(_(" - and %(key)s: %(extension)s") % + {'key': key, 'extension': extension}) else: - print("No Keystone-compatible endpoint found") + print(_("No Keystone-compatible endpoint found")) diff --git a/keystoneclient/httpclient.py b/keystoneclient/httpclient.py index ce49dc4..79b5e88 100644 --- a/keystoneclient/httpclient.py +++ b/keystoneclient/httpclient.py @@ -21,6 +21,7 @@ OpenStack Client interface. Handles the REST calls and responses. import logging +from oslo.serialization import jsonutils import pkg_resources import requests from six.moves.urllib import parse as urlparse @@ -54,7 +55,7 @@ from keystoneclient import access from keystoneclient.auth import base from keystoneclient import baseclient from keystoneclient import exceptions -from keystoneclient.openstack.common import jsonutils +from keystoneclient.i18n import _, _LI, _LW from keystoneclient import session as client_session from keystoneclient import utils @@ -265,7 +266,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): # keyring setup if use_keyring and keyring is None: - _logger.warning('Failed to load keyring modules.') + _logger.warning(_LW('Failed to load keyring modules.')) self.use_keyring = use_keyring and keyring is not None self.force_new_token = force_new_token self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION @@ -312,7 +313,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): def has_service_catalog(self): """Returns True if this client provides a service catalog.""" - return self.auth_ref.has_service_catalog() + return self.auth_ref and self.auth_ref.has_service_catalog() @property def tenant_id(self): @@ -364,9 +365,10 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): self.management_url from the details provided in the token. :returns: ``True`` if authentication was successful. - :raises: AuthorizationFailure if unable to authenticate or validate - the existing authorization token - :raises: ValueError if insufficient parameters are used. + :raises keystoneclient.exceptions.AuthorizationFailure: if unable to + authenticate or validate the existing authorization token + :raises keystoneclient.exceptions.ValueError: if insufficient + parameters are used. If keyring is used, token is retrieved from keyring instead. Authentication will only be necessary if any of the following @@ -476,7 +478,8 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): auth_ref = None except Exception as e: auth_ref = None - _logger.warning('Unable to retrieve token from keyring %s', e) + _logger.warning( + _LW('Unable to retrieve token from keyring %s'), e) return (keyring_key, auth_ref) def store_auth_ref_into_keyring(self, keyring_key): @@ -489,7 +492,8 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): keyring_key, pickle.dumps(self.auth_ref)) except Exception as e: - _logger.warning("Failed to store token into keyring %s", e) + _logger.warning( + _LW("Failed to store token into keyring %s"), e) def _process_management_url(self, region_name): try: @@ -498,7 +502,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): endpoint_type='admin', region_name=region_name) except exceptions.EndpointNotFound: - _logger.warning("Failed to retrieve management_url from token") + pass def process_token(self, region_name=None): """Extract and process information from the new auth_ref. @@ -511,14 +515,14 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): if self.auth_ref.project_scoped: if not self.auth_ref.tenant_id: raise exceptions.AuthorizationFailure( - "Token didn't provide tenant_id") + _("Token didn't provide tenant_id")) self._process_management_url(region_name) self.project_name = self.auth_ref.tenant_name self.project_id = self.auth_ref.tenant_id if not self.auth_ref.user_id: raise exceptions.AuthorizationFailure( - "Token didn't provide user_id") + _("Token didn't provide user_id")) self.user_id = self.auth_ref.user_id @@ -620,10 +624,11 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): try: return self.request(url, method, **kwargs) except exceptions.MissingAuthPlugin: - _logger.info('Cannot get authenticated endpoint without an ' - 'auth plugin') + _logger.info(_LI('Cannot get authenticated endpoint without an ' + 'auth plugin')) raise exceptions.AuthorizationFailure( - 'Current authorization does not have a known management url') + _('Current authorization does not have a known management ' + 'url')) def get(self, url, **kwargs): return self._cs_request(url, 'GET', **kwargs) @@ -656,7 +661,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin): try: var_name = self.deprecated_session_variables[name] except KeyError: - raise AttributeError("Unknown Attribute: %s" % name) + raise AttributeError(_("Unknown Attribute: %s") % name) return getattr(self.session, var_name or name) diff --git a/keystoneclient/i18n.py b/keystoneclient/i18n.py new file mode 100644 index 0000000..fd1c03a --- /dev/null +++ b/keystoneclient/i18n.py @@ -0,0 +1,37 @@ +# Copyright 2014 IBM Corp. +# +# 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. + +"""oslo.i18n integration module. + +See http://docs.openstack.org/developer/oslo.i18n/usage.html . + +""" + +from oslo import i18n + + +_translators = i18n.TranslatorFactory(domain='keystoneclient') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py index bb1e041..43a7ea5 100644 --- a/keystoneclient/middleware/auth_token.py +++ b/keystoneclient/middleware/auth_token.py @@ -16,6 +16,12 @@ """ TOKEN-BASED AUTH MIDDLEWARE +.. warning:: + + This module is DEPRECATED. The auth_token middleware has been moved to the + `keystonemiddleware repository + <http://docs.openstack.org/developer/keystonemiddleware/>`_. + This WSGI component: * Verifies that incoming client requests have valid tokens by validating @@ -26,9 +32,6 @@ This WSGI component: * Collects and forwards identity information based on a valid token such as user name, tenant, etc -Refer to: http://docs.openstack.org/developer/python-keystoneclient/ -middlewarearchitecture.html - HEADERS ------- @@ -155,6 +158,8 @@ import time import netaddr from oslo.config import cfg +from oslo.serialization import jsonutils +from oslo.utils import timeutils import requests import six from six.moves import urllib @@ -163,9 +168,7 @@ from keystoneclient import access from keystoneclient.common import cms from keystoneclient import exceptions from keystoneclient.middleware import memcache_crypt -from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import memorycache -from keystoneclient.openstack.common import timeutils # alternative middleware configuration in the main application's diff --git a/keystoneclient/middleware/s3_token.py b/keystoneclient/middleware/s3_token.py index 50d0f1c..b27b9ce 100644 --- a/keystoneclient/middleware/s3_token.py +++ b/keystoneclient/middleware/s3_token.py @@ -33,13 +33,12 @@ This WSGI component: import logging +from oslo.serialization import jsonutils import requests import six from six.moves import urllib import webob -from keystoneclient.openstack.common import jsonutils - PROTOCOL_NAME = 'S3 Token Authentication' diff --git a/keystoneclient/openstack/common/__init__.py b/keystoneclient/openstack/common/__init__.py index d1223ea..e69de29 100644 --- a/keystoneclient/openstack/common/__init__.py +++ b/keystoneclient/openstack/common/__init__.py @@ -1,17 +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. - -import six - - -six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff --git a/keystoneclient/openstack/common/_i18n.py b/keystoneclient/openstack/common/_i18n.py new file mode 100644 index 0000000..52a5e84 --- /dev/null +++ b/keystoneclient/openstack/common/_i18n.py @@ -0,0 +1,40 @@ +# 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. + +"""oslo.i18n integration module. + +See http://docs.openstack.org/developer/oslo.i18n/usage.html + +""" + +import oslo.i18n + + +# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the +# application name when this module is synced into the separate +# repository. It is OK to have more than one translation function +# using the same domain, since there will still only be one message +# catalog. +_translators = oslo.i18n.TranslatorFactory(domain='keystoneclient') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical diff --git a/keystoneclient/openstack/common/apiclient/base.py b/keystoneclient/openstack/common/apiclient/base.py index 9d7119d..72d7999 100644 --- a/keystoneclient/openstack/common/apiclient/base.py +++ b/keystoneclient/openstack/common/apiclient/base.py @@ -26,12 +26,12 @@ Base utilities to build API operation managers and objects on top of. import abc import copy +from oslo.utils import strutils import six from six.moves.urllib import parse +from keystoneclient.openstack.common._i18n import _ from keystoneclient.openstack.common.apiclient import exceptions -from keystoneclient.openstack.common.gettextutils import _ -from keystoneclient.openstack.common import strutils def getid(obj): @@ -495,6 +495,8 @@ class Resource(object): new = self.manager.get(self.id) if new: self._add_details(new._info) + self._add_details( + {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): if not isinstance(other, Resource): diff --git a/keystoneclient/openstack/common/apiclient/client.py b/keystoneclient/openstack/common/apiclient/client.py index 4e435b7..dd560ab 100644 --- a/keystoneclient/openstack/common/apiclient/client.py +++ b/keystoneclient/openstack/common/apiclient/client.py @@ -25,6 +25,7 @@ OpenStack Client interface. Handles the REST calls and responses. # E0202: An attribute inherited from %s hide this method # pylint: disable=E0202 +import hashlib import logging import time @@ -33,14 +34,15 @@ try: except ImportError: import json +from oslo.utils import encodeutils +from oslo.utils import importutils import requests +from keystoneclient.openstack.common._i18n import _ from keystoneclient.openstack.common.apiclient import exceptions -from keystoneclient.openstack.common.gettextutils import _ -from keystoneclient.openstack.common import importutils - _logger = logging.getLogger(__name__) +SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',) class HTTPClient(object): @@ -98,6 +100,18 @@ class HTTPClient(object): self.http = http or requests.Session() self.cached_token = None + self.last_request_id = None + + def _safe_header(self, name, value): + if name in SENSITIVE_HEADERS: + # because in python3 byte string handling is ... ug + v = value.encode('utf-8') + h = hashlib.sha1(v) + d = h.hexdigest() + return encodeutils.safe_decode(name), "{SHA1}%s" % d + else: + return (encodeutils.safe_decode(name), + encodeutils.safe_decode(value)) def _http_log_req(self, method, url, kwargs): if not self.debug: @@ -110,7 +124,8 @@ class HTTPClient(object): ] for element in kwargs['headers']: - header = "-H '%s: %s'" % (element, kwargs['headers'][element]) + header = ("-H '%s: %s'" % + self._safe_header(element, kwargs['headers'][element])) string_parts.append(header) _logger.debug("REQ: %s" % " ".join(string_parts)) @@ -156,7 +171,7 @@ class HTTPClient(object): requests.Session.request (such as `headers`) or `json` that will be encoded as JSON and used as `data` argument """ - kwargs.setdefault("headers", kwargs.get("headers", {})) + kwargs.setdefault("headers", {}) kwargs["headers"]["User-Agent"] = self.user_agent if self.original_ip: kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( @@ -177,6 +192,8 @@ class HTTPClient(object): start_time, time.time())) self._http_log_resp(resp) + self.last_request_id = resp.headers.get('x-openstack-request-id') + if resp.status_code >= 400: _logger.debug( "Request returned failure status: %s", @@ -247,6 +264,10 @@ class HTTPClient(object): raise self.cached_token = None client.cached_endpoint = None + if self.auth_plugin.opts.get('token'): + self.auth_plugin.opts['token'] = None + if self.auth_plugin.opts.get('endpoint'): + self.auth_plugin.opts['endpoint'] = None self.authenticate() try: token, endpoint = self.auth_plugin.token_and_endpoint( @@ -323,6 +344,10 @@ class BaseClient(object): return self.http_client.client_request( self, method, url, **kwargs) + @property + def last_request_id(self): + return self.http_client.last_request_id + def head(self, url, **kwargs): return self.client_request("HEAD", url, **kwargs) diff --git a/keystoneclient/openstack/common/apiclient/exceptions.py b/keystoneclient/openstack/common/apiclient/exceptions.py index 7e5c2ea..a4ff25a 100644 --- a/keystoneclient/openstack/common/apiclient/exceptions.py +++ b/keystoneclient/openstack/common/apiclient/exceptions.py @@ -25,7 +25,7 @@ import sys import six -from keystoneclient.openstack.common.gettextutils import _ +from keystoneclient.openstack.common._i18n import _ class ClientException(Exception): @@ -34,14 +34,6 @@ class ClientException(Exception): pass -class MissingArgs(ClientException): - """Supplied arguments are not sufficient for calling a function.""" - def __init__(self, missing): - self.missing = missing - msg = _("Missing arguments: %s") % ", ".join(missing) - super(MissingArgs, self).__init__(msg) - - class ValidationError(ClientException): """Error in validation on API client side.""" pass diff --git a/keystoneclient/openstack/common/apiclient/fake_client.py b/keystoneclient/openstack/common/apiclient/fake_client.py index ce7311c..46fc536 100644 --- a/keystoneclient/openstack/common/apiclient/fake_client.py +++ b/keystoneclient/openstack/common/apiclient/fake_client.py @@ -168,6 +168,8 @@ class FakeHTTPClient(client.HTTPClient): else: status, body = resp headers = {} + self.last_request_id = headers.get('x-openstack-request-id', + 'req-test') return TestResponse({ "status_code": status, "text": body, diff --git a/keystoneclient/openstack/common/apiclient/utils.py b/keystoneclient/openstack/common/apiclient/utils.py new file mode 100644 index 0000000..6aa2975 --- /dev/null +++ b/keystoneclient/openstack/common/apiclient/utils.py @@ -0,0 +1,87 @@ +# +# 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 oslo.utils import encodeutils +import six + +from keystoneclient.openstack.common._i18n import _ +from keystoneclient.openstack.common.apiclient import exceptions +from keystoneclient.openstack.common import uuidutils + + +def find_resource(manager, name_or_id, **find_args): + """Look for resource in a given manager. + + Used as a helper for the _find_* methods. + Example: + + .. code-block:: python + + def _find_hypervisor(cs, hypervisor): + #Get a hypervisor by name or ID. + return cliutils.find_resource(cs.hypervisors, hypervisor) + """ + # first try to get entity as integer id + try: + return manager.get(int(name_or_id)) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # now try to get entity as uuid + try: + if six.PY2: + tmp_id = encodeutils.safe_encode(name_or_id) + else: + tmp_id = encodeutils.safe_decode(name_or_id) + + if uuidutils.is_uuid_like(tmp_id): + return manager.get(tmp_id) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # for str id which is not uuid + if getattr(manager, 'is_alphanum_id_allowed', False): + try: + return manager.get(name_or_id) + except exceptions.NotFound: + pass + + try: + try: + return manager.find(human_id=name_or_id, **find_args) + except exceptions.NotFound: + pass + + # finally try to find entity by name + try: + resource = getattr(manager, 'resource_class', None) + name_attr = resource.NAME_ATTR if resource else 'name' + kwargs = {name_attr: name_or_id} + kwargs.update(find_args) + return manager.find(**kwargs) + except exceptions.NotFound: + msg = _("No %(name)s with a name or " + "ID of '%(name_or_id)s' exists.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + except exceptions.NoUniqueMatch: + msg = _("Multiple %(name)s matches found for " + "'%(name_or_id)s', use an ID to be more specific.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) diff --git a/keystoneclient/openstack/common/gettextutils.py b/keystoneclient/openstack/common/gettextutils.py deleted file mode 100644 index 55a60df..0000000 --- a/keystoneclient/openstack/common/gettextutils.py +++ /dev/null @@ -1,479 +0,0 @@ -# Copyright 2012 Red Hat, Inc. -# Copyright 2013 IBM Corp. -# 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. - -""" -gettext for openstack-common modules. - -Usual usage in an openstack.common module: - - from keystoneclient.openstack.common.gettextutils import _ -""" - -import copy -import gettext -import locale -from logging import handlers -import os - -from babel import localedata -import six - -_AVAILABLE_LANGUAGES = {} - -# FIXME(dhellmann): Remove this when moving to oslo.i18n. -USE_LAZY = False - - -class TranslatorFactory(object): - """Create translator functions - """ - - def __init__(self, domain, localedir=None): - """Establish a set of translation functions for the domain. - - :param domain: Name of translation domain, - specifying a message catalog. - :type domain: str - :param lazy: Delays translation until a message is emitted. - Defaults to False. - :type lazy: Boolean - :param localedir: Directory with translation catalogs. - :type localedir: str - """ - self.domain = domain - if localedir is None: - localedir = os.environ.get(domain.upper() + '_LOCALEDIR') - self.localedir = localedir - - def _make_translation_func(self, domain=None): - """Return a new translation function ready for use. - - Takes into account whether or not lazy translation is being - done. - - The domain can be specified to override the default from the - factory, but the localedir from the factory is always used - because we assume the log-level translation catalogs are - installed in the same directory as the main application - catalog. - - """ - if domain is None: - domain = self.domain - t = gettext.translation(domain, - localedir=self.localedir, - fallback=True) - # Use the appropriate method of the translation object based - # on the python version. - m = t.gettext if six.PY3 else t.ugettext - - def f(msg): - """oslo.i18n.gettextutils translation function.""" - if USE_LAZY: - return Message(msg, domain=domain) - return m(msg) - return f - - @property - def primary(self): - "The default translation function." - return self._make_translation_func() - - def _make_log_translation_func(self, level): - return self._make_translation_func(self.domain + '-log-' + level) - - @property - def log_info(self): - "Translate info-level log messages." - return self._make_log_translation_func('info') - - @property - def log_warning(self): - "Translate warning-level log messages." - return self._make_log_translation_func('warning') - - @property - def log_error(self): - "Translate error-level log messages." - return self._make_log_translation_func('error') - - @property - def log_critical(self): - "Translate critical-level log messages." - return self._make_log_translation_func('critical') - - -# NOTE(dhellmann): When this module moves out of the incubator into -# oslo.i18n, these global variables can be moved to an integration -# module within each application. - -# Create the global translation functions. -_translators = TranslatorFactory('keystoneclient') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical - -# NOTE(dhellmann): End of globals that will move to the application's -# integration module. - - -def enable_lazy(): - """Convenience function for configuring _() to use lazy gettext - - Call this at the start of execution to enable the gettextutils._ - function to use lazy gettext functionality. This is useful if - your project is importing _ directly instead of using the - gettextutils.install() way of importing the _ function. - """ - global USE_LAZY - USE_LAZY = True - - -def install(domain): - """Install a _() function using the given translation domain. - - Given a translation domain, install a _() function using gettext's - install() function. - - The main difference from gettext.install() is that we allow - overriding the default localedir (e.g. /usr/share/locale) using - a translation-domain-specific environment variable (e.g. - NOVA_LOCALEDIR). - - Note that to enable lazy translation, enable_lazy must be - called. - - :param domain: the translation domain - """ - from six import moves - tf = TranslatorFactory(domain) - moves.builtins.__dict__['_'] = tf.primary - - -class Message(six.text_type): - """A Message object is a unicode object that can be translated. - - Translation of Message is done explicitly using the translate() method. - For all non-translation intents and purposes, a Message is simply unicode, - and can be treated as such. - """ - - def __new__(cls, msgid, msgtext=None, params=None, - domain='keystoneclient', *args): - """Create a new Message object. - - In order for translation to work gettext requires a message ID, this - msgid will be used as the base unicode text. It is also possible - for the msgid and the base unicode text to be different by passing - the msgtext parameter. - """ - # If the base msgtext is not given, we use the default translation - # of the msgid (which is in English) just in case the system locale is - # not English, so that the base text will be in that locale by default. - if not msgtext: - msgtext = Message._translate_msgid(msgid, domain) - # We want to initialize the parent unicode with the actual object that - # would have been plain unicode if 'Message' was not enabled. - msg = super(Message, cls).__new__(cls, msgtext) - msg.msgid = msgid - msg.domain = domain - msg.params = params - return msg - - def translate(self, desired_locale=None): - """Translate this message to the desired locale. - - :param desired_locale: The desired locale to translate the message to, - if no locale is provided the message will be - translated to the system's default locale. - - :returns: the translated message in unicode - """ - - translated_message = Message._translate_msgid(self.msgid, - self.domain, - desired_locale) - if self.params is None: - # No need for more translation - return translated_message - - # This Message object may have been formatted with one or more - # Message objects as substitution arguments, given either as a single - # argument, part of a tuple, or as one or more values in a dictionary. - # When translating this Message we need to translate those Messages too - translated_params = _translate_args(self.params, desired_locale) - - translated_message = translated_message % translated_params - - return translated_message - - @staticmethod - def _translate_msgid(msgid, domain, desired_locale=None): - if not desired_locale: - system_locale = locale.getdefaultlocale() - # If the system locale is not available to the runtime use English - if not system_locale[0]: - desired_locale = 'en_US' - else: - desired_locale = system_locale[0] - - locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') - lang = gettext.translation(domain, - localedir=locale_dir, - languages=[desired_locale], - fallback=True) - if six.PY3: - translator = lang.gettext - else: - translator = lang.ugettext - - translated_message = translator(msgid) - return translated_message - - def __mod__(self, other): - # When we mod a Message we want the actual operation to be performed - # by the parent class (i.e. unicode()), the only thing we do here is - # save the original msgid and the parameters in case of a translation - params = self._sanitize_mod_params(other) - unicode_mod = super(Message, self).__mod__(params) - modded = Message(self.msgid, - msgtext=unicode_mod, - params=params, - domain=self.domain) - return modded - - def _sanitize_mod_params(self, other): - """Sanitize the object being modded with this Message. - - - Add support for modding 'None' so translation supports it - - Trim the modded object, which can be a large dictionary, to only - those keys that would actually be used in a translation - - Snapshot the object being modded, in case the message is - translated, it will be used as it was when the Message was created - """ - if other is None: - params = (other,) - elif isinstance(other, dict): - # Merge the dictionaries - # Copy each item in case one does not support deep copy. - params = {} - if isinstance(self.params, dict): - for key, val in self.params.items(): - params[key] = self._copy_param(val) - for key, val in other.items(): - params[key] = self._copy_param(val) - else: - params = self._copy_param(other) - return params - - def _copy_param(self, param): - try: - return copy.deepcopy(param) - except Exception: - # Fallback to casting to unicode this will handle the - # python code-like objects that can't be deep-copied - return six.text_type(param) - - def __add__(self, other): - msg = _('Message objects do not support addition.') - raise TypeError(msg) - - def __radd__(self, other): - return self.__add__(other) - - if six.PY2: - def __str__(self): - # NOTE(luisg): Logging in python 2.6 tries to str() log records, - # and it expects specifically a UnicodeError in order to proceed. - msg = _('Message objects do not support str() because they may ' - 'contain non-ascii characters. ' - 'Please use unicode() or translate() instead.') - raise UnicodeError(msg) - - -def get_available_languages(domain): - """Lists the available languages for the given translation domain. - - :param domain: the domain to get languages for - """ - if domain in _AVAILABLE_LANGUAGES: - return copy.copy(_AVAILABLE_LANGUAGES[domain]) - - localedir = '%s_LOCALEDIR' % domain.upper() - find = lambda x: gettext.find(domain, - localedir=os.environ.get(localedir), - languages=[x]) - - # NOTE(mrodden): en_US should always be available (and first in case - # order matters) since our in-line message strings are en_US - language_list = ['en_US'] - # NOTE(luisg): Babel <1.0 used a function called list(), which was - # renamed to locale_identifiers() in >=1.0, the requirements master list - # requires >=0.9.6, uncapped, so defensively work with both. We can remove - # this check when the master list updates to >=1.0, and update all projects - list_identifiers = (getattr(localedata, 'list', None) or - getattr(localedata, 'locale_identifiers')) - locale_identifiers = list_identifiers() - - for i in locale_identifiers: - if find(i) is not None: - language_list.append(i) - - # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported - # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they - # are perfectly legitimate locales: - # https://github.com/mitsuhiko/babel/issues/37 - # In Babel 1.3 they fixed the bug and they support these locales, but - # they are still not explicitly "listed" by locale_identifiers(). - # That is why we add the locales here explicitly if necessary so that - # they are listed as supported. - aliases = {'zh': 'zh_CN', - 'zh_Hant_HK': 'zh_HK', - 'zh_Hant': 'zh_TW', - 'fil': 'tl_PH'} - for (locale_, alias) in six.iteritems(aliases): - if locale_ in language_list and alias not in language_list: - language_list.append(alias) - - _AVAILABLE_LANGUAGES[domain] = language_list - return copy.copy(language_list) - - -def translate(obj, desired_locale=None): - """Gets the translated unicode representation of the given object. - - If the object is not translatable it is returned as-is. - If the locale is None the object is translated to the system locale. - - :param obj: the object to translate - :param desired_locale: the locale to translate the message to, if None the - default system locale will be used - :returns: the translated object in unicode, or the original object if - it could not be translated - """ - message = obj - if not isinstance(message, Message): - # If the object to translate is not already translatable, - # let's first get its unicode representation - message = six.text_type(obj) - if isinstance(message, Message): - # Even after unicoding() we still need to check if we are - # running with translatable unicode before translating - return message.translate(desired_locale) - return obj - - -def _translate_args(args, desired_locale=None): - """Translates all the translatable elements of the given arguments object. - - This method is used for translating the translatable values in method - arguments which include values of tuples or dictionaries. - If the object is not a tuple or a dictionary the object itself is - translated if it is translatable. - - If the locale is None the object is translated to the system locale. - - :param args: the args to translate - :param desired_locale: the locale to translate the args to, if None the - default system locale will be used - :returns: a new args object with the translated contents of the original - """ - if isinstance(args, tuple): - return tuple(translate(v, desired_locale) for v in args) - if isinstance(args, dict): - translated_dict = {} - for (k, v) in six.iteritems(args): - translated_v = translate(v, desired_locale) - translated_dict[k] = translated_v - return translated_dict - return translate(args, desired_locale) - - -class TranslationHandler(handlers.MemoryHandler): - """Handler that translates records before logging them. - - The TranslationHandler takes a locale and a target logging.Handler object - to forward LogRecord objects to after translating them. This handler - depends on Message objects being logged, instead of regular strings. - - The handler can be configured declaratively in the logging.conf as follows: - - [handlers] - keys = translatedlog, translator - - [handler_translatedlog] - class = handlers.WatchedFileHandler - args = ('/var/log/api-localized.log',) - formatter = context - - [handler_translator] - class = openstack.common.log.TranslationHandler - target = translatedlog - args = ('zh_CN',) - - If the specified locale is not available in the system, the handler will - log in the default locale. - """ - - def __init__(self, locale=None, target=None): - """Initialize a TranslationHandler - - :param locale: locale to use for translating messages - :param target: logging.Handler object to forward - LogRecord objects to after translation - """ - # NOTE(luisg): In order to allow this handler to be a wrapper for - # other handlers, such as a FileHandler, and still be able to - # configure it using logging.conf, this handler has to extend - # MemoryHandler because only the MemoryHandlers' logging.conf - # parsing is implemented such that it accepts a target handler. - handlers.MemoryHandler.__init__(self, capacity=0, target=target) - self.locale = locale - - def setFormatter(self, fmt): - self.target.setFormatter(fmt) - - def emit(self, record): - # We save the message from the original record to restore it - # after translation, so other handlers are not affected by this - original_msg = record.msg - original_args = record.args - - try: - self._translate_and_log_record(record) - finally: - record.msg = original_msg - record.args = original_args - - def _translate_and_log_record(self, record): - record.msg = translate(record.msg, self.locale) - - # In addition to translating the message, we also need to translate - # arguments that were passed to the log method that were not part - # of the main message e.g., log.info(_('Some message %s'), this_one)) - record.args = _translate_args(record.args, self.locale) - - self.target.emit(record) diff --git a/keystoneclient/openstack/common/importutils.py b/keystoneclient/openstack/common/importutils.py deleted file mode 100644 index 1261278..0000000 --- a/keystoneclient/openstack/common/importutils.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2011 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 related utilities and helper functions. -""" - -import sys -import traceback - - -def import_class(import_str): - """Returns a class from a string including module and class.""" - mod_str, _sep, class_str = import_str.rpartition('.') - __import__(mod_str) - try: - return getattr(sys.modules[mod_str], class_str) - except AttributeError: - raise ImportError('Class %s cannot be found (%s)' % - (class_str, - traceback.format_exception(*sys.exc_info()))) - - -def import_object(import_str, *args, **kwargs): - """Import a class and return an instance of it.""" - return import_class(import_str)(*args, **kwargs) - - -def import_object_ns(name_space, import_str, *args, **kwargs): - """Tries to import object from default namespace. - - Imports a class and return an instance of it, first by trying - to find the class in a default namespace, then failing back to - a full path if not found in the default namespace. - """ - import_value = "%s.%s" % (name_space, import_str) - try: - return import_class(import_value)(*args, **kwargs) - except ImportError: - return import_class(import_str)(*args, **kwargs) - - -def import_module(import_str): - """Import a module.""" - __import__(import_str) - return sys.modules[import_str] - - -def import_versioned_module(version, submodule=None): - module = 'keystoneclient.v%s' % version - if submodule: - module = '.'.join((module, submodule)) - return import_module(module) - - -def try_import(import_str, default=None): - """Try to import a module and if it fails return default.""" - try: - return import_module(import_str) - except ImportError: - return default diff --git a/keystoneclient/openstack/common/jsonutils.py b/keystoneclient/openstack/common/jsonutils.py deleted file mode 100644 index 3252588..0000000 --- a/keystoneclient/openstack/common/jsonutils.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# 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. - -''' -JSON related utilities. - -This module provides a few things: - - 1) A handy function for getting an object down to something that can be - JSON serialized. See to_primitive(). - - 2) Wrappers around loads() and dumps(). The dumps() wrapper will - automatically use to_primitive() for you if needed. - - 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson - is available. -''' - - -import codecs -import datetime -import functools -import inspect -import itertools -import sys - -is_simplejson = False -if sys.version_info < (2, 7): - # On Python <= 2.6, json module is not C boosted, so try to use - # simplejson module if available - try: - import simplejson as json - # NOTE(mriedem): Make sure we have a new enough version of simplejson - # to support the namedobject_as_tuple argument. This can be removed - # in the Kilo release when python 2.6 support is dropped. - if 'namedtuple_as_object' in inspect.getargspec(json.dumps).args: - is_simplejson = True - else: - import json - except ImportError: - import json -else: - import json - -import six -import six.moves.xmlrpc_client as xmlrpclib - -from keystoneclient.openstack.common import gettextutils -from keystoneclient.openstack.common import importutils -from keystoneclient.openstack.common import strutils -from keystoneclient.openstack.common import timeutils - -netaddr = importutils.try_import("netaddr") - -_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod, - inspect.isfunction, inspect.isgeneratorfunction, - inspect.isgenerator, inspect.istraceback, inspect.isframe, - inspect.iscode, inspect.isbuiltin, inspect.isroutine, - inspect.isabstract] - -_simple_types = (six.string_types + six.integer_types - + (type(None), bool, float)) - - -def to_primitive(value, convert_instances=False, convert_datetime=True, - level=0, max_depth=3): - """Convert a complex object into primitives. - - Handy for JSON serialization. We can optionally handle instances, - but since this is a recursive function, we could have cyclical - data structures. - - To handle cyclical data structures we could track the actual objects - visited in a set, but not all objects are hashable. Instead we just - track the depth of the object inspections and don't go too deep. - - Therefore, convert_instances=True is lossy ... be aware. - - """ - # handle obvious types first - order of basic types determined by running - # full tests on nova project, resulting in the following counts: - # 572754 <type 'NoneType'> - # 460353 <type 'int'> - # 379632 <type 'unicode'> - # 274610 <type 'str'> - # 199918 <type 'dict'> - # 114200 <type 'datetime.datetime'> - # 51817 <type 'bool'> - # 26164 <type 'list'> - # 6491 <type 'float'> - # 283 <type 'tuple'> - # 19 <type 'long'> - if isinstance(value, _simple_types): - return value - - if isinstance(value, datetime.datetime): - if convert_datetime: - return timeutils.strtime(value) - else: - return value - - # value of itertools.count doesn't get caught by nasty_type_tests - # and results in infinite loop when list(value) is called. - if type(value) == itertools.count: - return six.text_type(value) - - # FIXME(vish): Workaround for LP bug 852095. Without this workaround, - # tests that raise an exception in a mocked method that - # has a @wrap_exception with a notifier will fail. If - # we up the dependency to 0.5.4 (when it is released) we - # can remove this workaround. - if getattr(value, '__module__', None) == 'mox': - return 'mock' - - if level > max_depth: - return '?' - - # The try block may not be necessary after the class check above, - # but just in case ... - try: - recursive = functools.partial(to_primitive, - convert_instances=convert_instances, - convert_datetime=convert_datetime, - level=level, - max_depth=max_depth) - if isinstance(value, dict): - return dict((k, recursive(v)) for k, v in six.iteritems(value)) - elif isinstance(value, (list, tuple)): - return [recursive(lv) for lv in value] - - # It's not clear why xmlrpclib created their own DateTime type, but - # for our purposes, make it a datetime type which is explicitly - # handled - if isinstance(value, xmlrpclib.DateTime): - value = datetime.datetime(*tuple(value.timetuple())[:6]) - - if convert_datetime and isinstance(value, datetime.datetime): - return timeutils.strtime(value) - elif isinstance(value, gettextutils.Message): - return value.data - elif hasattr(value, 'iteritems'): - return recursive(dict(value.iteritems()), level=level + 1) - elif hasattr(value, '__iter__'): - return recursive(list(value)) - elif convert_instances and hasattr(value, '__dict__'): - # Likely an instance of something. Watch for cycles. - # Ignore class member vars. - return recursive(value.__dict__, level=level + 1) - elif netaddr and isinstance(value, netaddr.IPAddress): - return six.text_type(value) - else: - if any(test(value) for test in _nasty_type_tests): - return six.text_type(value) - return value - except TypeError: - # Class objects are tricky since they may define something like - # __iter__ defined but it isn't callable as list(). - return six.text_type(value) - - -def dumps(value, default=to_primitive, **kwargs): - if is_simplejson: - kwargs['namedtuple_as_object'] = False - return json.dumps(value, default=default, **kwargs) - - -def dump(obj, fp, *args, **kwargs): - if is_simplejson: - kwargs['namedtuple_as_object'] = False - return json.dump(obj, fp, *args, **kwargs) - - -def loads(s, encoding='utf-8', **kwargs): - return json.loads(strutils.safe_decode(s, encoding), **kwargs) - - -def load(fp, encoding='utf-8', **kwargs): - return json.load(codecs.getreader(encoding)(fp), **kwargs) - - -try: - import anyjson -except ImportError: - pass -else: - anyjson._modules.append((__name__, 'dumps', TypeError, - 'loads', ValueError, 'load')) - anyjson.force_implementation(__name__) diff --git a/keystoneclient/openstack/common/memorycache.py b/keystoneclient/openstack/common/memorycache.py index a833634..4826865 100644 --- a/keystoneclient/openstack/common/memorycache.py +++ b/keystoneclient/openstack/common/memorycache.py @@ -17,8 +17,7 @@ """Super simple fake memcache client.""" from oslo.config import cfg - -from keystoneclient.openstack.common import timeutils +from oslo.utils import timeutils memcache_opts = [ cfg.ListOpt('memcached_servers', diff --git a/keystoneclient/openstack/common/strutils.py b/keystoneclient/openstack/common/strutils.py deleted file mode 100644 index fc3ef3f..0000000 --- a/keystoneclient/openstack/common/strutils.py +++ /dev/null @@ -1,311 +0,0 @@ -# Copyright 2011 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. - -""" -System-level utilities and helper functions. -""" - -import math -import re -import sys -import unicodedata - -import six - -from keystoneclient.openstack.common.gettextutils import _ - - -UNIT_PREFIX_EXPONENT = { - 'k': 1, - 'K': 1, - 'Ki': 1, - 'M': 2, - 'Mi': 2, - 'G': 3, - 'Gi': 3, - 'T': 4, - 'Ti': 4, -} -UNIT_SYSTEM_INFO = { - 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')), - 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')), -} - -TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') -FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') - -SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") -SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") - - -# NOTE(flaper87): The following globals are used by `mask_password` -_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] - -# NOTE(ldbragst): Let's build a list of regex objects using the list of -# _SANITIZE_KEYS we already have. This way, we only have to add the new key -# to the list of _SANITIZE_KEYS and we can generate regular expressions -# for XML and JSON automatically. -_SANITIZE_PATTERNS_2 = [] -_SANITIZE_PATTERNS_1 = [] - -# NOTE(amrith): Some regular expressions have only one parameter, some -# have two parameters. Use different lists of patterns here. -_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+'] -_FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', - r'(%(key)s\s+[\"\']).*?([\"\'])', - r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)', - r'(<%(key)s>).*?(</%(key)s>)', - r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', - r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])', - r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?' - '[\'"]).*?([\'"])', - r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)'] - -for key in _SANITIZE_KEYS: - for pattern in _FORMAT_PATTERNS_2: - reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) - _SANITIZE_PATTERNS_2.append(reg_ex) - - for pattern in _FORMAT_PATTERNS_1: - reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) - _SANITIZE_PATTERNS_1.append(reg_ex) - - -def int_from_bool_as_string(subject): - """Interpret a string as a boolean and return either 1 or 0. - - Any string value in: - - ('True', 'true', 'On', 'on', '1') - - is interpreted as a boolean True. - - Useful for JSON-decoded stuff and config file parsing - """ - return bool_from_string(subject) and 1 or 0 - - -def bool_from_string(subject, strict=False, default=False): - """Interpret a string as a boolean. - - A case-insensitive match is performed such that strings matching 't', - 'true', 'on', 'y', 'yes', or '1' are considered True and, when - `strict=False`, anything else returns the value specified by 'default'. - - Useful for JSON-decoded stuff and config file parsing. - - If `strict=True`, unrecognized values, including None, will raise a - ValueError which is useful when parsing values passed in from an API call. - Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. - """ - if not isinstance(subject, six.string_types): - subject = six.text_type(subject) - - lowered = subject.strip().lower() - - if lowered in TRUE_STRINGS: - return True - elif lowered in FALSE_STRINGS: - return False - elif strict: - acceptable = ', '.join( - "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) - msg = _("Unrecognized value '%(val)s', acceptable values are:" - " %(acceptable)s") % {'val': subject, - 'acceptable': acceptable} - raise ValueError(msg) - else: - return default - - -def safe_decode(text, incoming=None, errors='strict'): - """Decodes incoming text/bytes string using `incoming` if they're not - already unicode. - - :param incoming: Text's current encoding - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: text or a unicode `incoming` encoded - representation of it. - :raises TypeError: If text is not an instance of str - """ - if not isinstance(text, (six.string_types, six.binary_type)): - raise TypeError("%s can't be decoded" % type(text)) - - if isinstance(text, six.text_type): - return text - - if not incoming: - incoming = (sys.stdin.encoding or - sys.getdefaultencoding()) - - try: - return text.decode(incoming, errors) - except UnicodeDecodeError: - # Note(flaper87) If we get here, it means that - # sys.stdin.encoding / sys.getdefaultencoding - # didn't return a suitable encoding to decode - # text. This happens mostly when global LANG - # var is not set correctly and there's no - # default encoding. In this case, most likely - # python will use ASCII or ANSI encoders as - # default encodings but they won't be capable - # of decoding non-ASCII characters. - # - # Also, UTF-8 is being used since it's an ASCII - # extension. - return text.decode('utf-8', errors) - - -def safe_encode(text, incoming=None, - encoding='utf-8', errors='strict'): - """Encodes incoming text/bytes string using `encoding`. - - If incoming is not specified, text is expected to be encoded with - current python's default encoding. (`sys.getdefaultencoding`) - - :param incoming: Text's current encoding - :param encoding: Expected encoding for text (Default UTF-8) - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: text or a bytestring `encoding` encoded - representation of it. - :raises TypeError: If text is not an instance of str - """ - if not isinstance(text, (six.string_types, six.binary_type)): - raise TypeError("%s can't be encoded" % type(text)) - - if not incoming: - incoming = (sys.stdin.encoding or - sys.getdefaultencoding()) - - if isinstance(text, six.text_type): - return text.encode(encoding, errors) - elif text and encoding != incoming: - # Decode text before encoding it with `encoding` - text = safe_decode(text, incoming, errors) - return text.encode(encoding, errors) - else: - return text - - -def string_to_bytes(text, unit_system='IEC', return_int=False): - """Converts a string into an float representation of bytes. - - The units supported for IEC :: - - Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it) - KB, KiB, MB, MiB, GB, GiB, TB, TiB - - The units supported for SI :: - - kb(it), Mb(it), Gb(it), Tb(it) - kB, MB, GB, TB - - Note that the SI unit system does not support capital letter 'K' - - :param text: String input for bytes size conversion. - :param unit_system: Unit system for byte size conversion. - :param return_int: If True, returns integer representation of text - in bytes. (default: decimal) - :returns: Numerical representation of text in bytes. - :raises ValueError: If text has an invalid value. - - """ - try: - base, reg_ex = UNIT_SYSTEM_INFO[unit_system] - except KeyError: - msg = _('Invalid unit system: "%s"') % unit_system - raise ValueError(msg) - match = reg_ex.match(text) - if match: - magnitude = float(match.group(1)) - unit_prefix = match.group(2) - if match.group(3) in ['b', 'bit']: - magnitude /= 8 - else: - msg = _('Invalid string format: %s') % text - raise ValueError(msg) - if not unit_prefix: - res = magnitude - else: - res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix]) - if return_int: - return int(math.ceil(res)) - return res - - -def to_slug(value, incoming=None, errors="strict"): - """Normalize string. - - Convert to lowercase, remove non-word characters, and convert spaces - to hyphens. - - Inspired by Django's `slugify` filter. - - :param value: Text to slugify - :param incoming: Text's current encoding - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: slugified unicode representation of `value` - :raises TypeError: If text is not an instance of str - """ - value = safe_decode(value, incoming, errors) - # NOTE(aababilov): no need to use safe_(encode|decode) here: - # encodings are always "ascii", error handling is always "ignore" - # and types are always known (first: unicode; second: str) - value = unicodedata.normalize("NFKD", value).encode( - "ascii", "ignore").decode("ascii") - value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() - return SLUGIFY_HYPHENATE_RE.sub("-", value) - - -def mask_password(message, secret="***"): - """Replace password with 'secret' in message. - - :param message: The string which includes security information. - :param secret: value with which to replace passwords. - :returns: The unicode value of message with the password fields masked. - - For example: - - >>> mask_password("'adminPass' : 'aaaaa'") - "'adminPass' : '***'" - >>> mask_password("'admin_pass' : 'aaaaa'") - "'admin_pass' : '***'" - >>> mask_password('"password" : "aaaaa"') - '"password" : "***"' - >>> mask_password("'original_password' : 'aaaaa'") - "'original_password' : '***'" - >>> mask_password("u'original_password' : u'aaaaa'") - "u'original_password' : u'***'" - """ - message = six.text_type(message) - - # NOTE(ldbragst): Check to see if anything in message contains any key - # specified in _SANITIZE_KEYS, if not then just return the message since - # we don't have to mask any passwords. - if not any(key in message for key in _SANITIZE_KEYS): - return message - - substitute = r'\g<1>' + secret + r'\g<2>' - for pattern in _SANITIZE_PATTERNS_2: - message = re.sub(pattern, substitute, message) - - substitute = r'\g<1>' + secret - for pattern in _SANITIZE_PATTERNS_1: - message = re.sub(pattern, substitute, message) - - return message diff --git a/keystoneclient/openstack/common/timeutils.py b/keystoneclient/openstack/common/timeutils.py deleted file mode 100644 index 49d8287..0000000 --- a/keystoneclient/openstack/common/timeutils.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright 2011 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. - -""" -Time related utilities and helper functions. -""" - -import calendar -import datetime -import time - -import iso8601 -import six - - -# ISO 8601 extended time format with microseconds -_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' -_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' -PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND - - -def isotime(at=None, subsecond=False): - """Stringify time in ISO 8601 format.""" - if not at: - at = utcnow() - st = at.strftime(_ISO8601_TIME_FORMAT - if not subsecond - else _ISO8601_TIME_FORMAT_SUBSECOND) - tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' - st += ('Z' if tz == 'UTC' else tz) - return st - - -def parse_isotime(timestr): - """Parse time from ISO 8601 format.""" - try: - return iso8601.parse_date(timestr) - except iso8601.ParseError as e: - raise ValueError(six.text_type(e)) - except TypeError as e: - raise ValueError(six.text_type(e)) - - -def strtime(at=None, fmt=PERFECT_TIME_FORMAT): - """Returns formatted utcnow.""" - if not at: - at = utcnow() - return at.strftime(fmt) - - -def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): - """Turn a formatted time back into a datetime.""" - return datetime.datetime.strptime(timestr, fmt) - - -def normalize_time(timestamp): - """Normalize time in arbitrary timezone to UTC naive object.""" - offset = timestamp.utcoffset() - if offset is None: - return timestamp - return timestamp.replace(tzinfo=None) - offset - - -def is_older_than(before, seconds): - """Return True if before is older than seconds.""" - if isinstance(before, six.string_types): - before = parse_strtime(before).replace(tzinfo=None) - else: - before = before.replace(tzinfo=None) - - return utcnow() - before > datetime.timedelta(seconds=seconds) - - -def is_newer_than(after, seconds): - """Return True if after is newer than seconds.""" - if isinstance(after, six.string_types): - after = parse_strtime(after).replace(tzinfo=None) - else: - after = after.replace(tzinfo=None) - - return after - utcnow() > datetime.timedelta(seconds=seconds) - - -def utcnow_ts(): - """Timestamp version of our utcnow function.""" - if utcnow.override_time is None: - # NOTE(kgriffs): This is several times faster - # than going through calendar.timegm(...) - return int(time.time()) - - return calendar.timegm(utcnow().timetuple()) - - -def utcnow(): - """Overridable version of utils.utcnow.""" - if utcnow.override_time: - try: - return utcnow.override_time.pop(0) - except AttributeError: - return utcnow.override_time - return datetime.datetime.utcnow() - - -def iso8601_from_timestamp(timestamp): - """Returns an iso8601 formatted date from timestamp.""" - return isotime(datetime.datetime.utcfromtimestamp(timestamp)) - - -utcnow.override_time = None - - -def set_time_override(override_time=None): - """Overrides utils.utcnow. - - Make it return a constant time or a list thereof, one at a time. - - :param override_time: datetime instance or list thereof. If not - given, defaults to the current UTC time. - """ - utcnow.override_time = override_time or datetime.datetime.utcnow() - - -def advance_time_delta(timedelta): - """Advance overridden time using a datetime.timedelta.""" - assert utcnow.override_time is not None - try: - for dt in utcnow.override_time: - dt += timedelta - except TypeError: - utcnow.override_time += timedelta - - -def advance_time_seconds(seconds): - """Advance overridden time by seconds.""" - advance_time_delta(datetime.timedelta(0, seconds)) - - -def clear_time_override(): - """Remove the overridden time.""" - utcnow.override_time = None - - -def marshall_now(now=None): - """Make an rpc-safe datetime with microseconds. - - Note: tzinfo is stripped, but not required for relative times. - """ - if not now: - now = utcnow() - return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, - minute=now.minute, second=now.second, - microsecond=now.microsecond) - - -def unmarshall_time(tyme): - """Unmarshall a datetime dict.""" - return datetime.datetime(day=tyme['day'], - month=tyme['month'], - year=tyme['year'], - hour=tyme['hour'], - minute=tyme['minute'], - second=tyme['second'], - microsecond=tyme['microsecond']) - - -def delta_seconds(before, after): - """Return the difference between two timing objects. - - Compute the difference in seconds between two date, time, or - datetime objects (as a float, to microsecond resolution). - """ - delta = after - before - return total_seconds(delta) - - -def total_seconds(delta): - """Return the total seconds of datetime.timedelta object. - - Compute total seconds of datetime.timedelta, datetime.timedelta - doesn't have method total_seconds in Python2.6, calculate it manually. - """ - try: - return delta.total_seconds() - except AttributeError: - return ((delta.days * 24 * 3600) + delta.seconds + - float(delta.microseconds) / (10 ** 6)) - - -def is_soon(dt, window): - """Determines if time is going to happen in the next window seconds. - - :param dt: the time - :param window: minimum seconds to remain to consider the time not soon - - :returns: True if expiration is within the given duration - """ - soon = (utcnow() + datetime.timedelta(seconds=window)) - return normalize_time(dt) <= soon diff --git a/keystoneclient/openstack/common/uuidutils.py b/keystoneclient/openstack/common/uuidutils.py new file mode 100644 index 0000000..234b880 --- /dev/null +++ b/keystoneclient/openstack/common/uuidutils.py @@ -0,0 +1,37 @@ +# Copyright (c) 2012 Intel Corporation. +# 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. + +""" +UUID related utilities and helper functions. +""" + +import uuid + + +def generate_uuid(): + return str(uuid.uuid4()) + + +def is_uuid_like(val): + """Returns validation of a value as a UUID. + + For our purposes, a UUID is a canonical form string: + aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa + + """ + try: + return str(uuid.UUID(val)) == val + except (TypeError, ValueError, AttributeError): + return False diff --git a/keystoneclient/service_catalog.py b/keystoneclient/service_catalog.py index cf85ed7..7c9085b 100644 --- a/keystoneclient/service_catalog.py +++ b/keystoneclient/service_catalog.py @@ -21,6 +21,7 @@ import abc import six from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils @@ -36,7 +37,7 @@ class ServiceCatalog(object): elif ServiceCatalogV2.is_valid(resource_dict): return ServiceCatalogV2(resource_dict, region_name) else: - raise NotImplementedError('Unrecognized auth response') + raise NotImplementedError(_('Unrecognized auth response')) def __init__(self, region_name=None): self._region_name = region_name @@ -208,7 +209,7 @@ class ServiceCatalog(object): """ if not self.get_data(): - raise exceptions.EmptyCatalog('The service catalog is empty.') + raise exceptions.EmptyCatalog(_('The service catalog is empty.')) urls = self.get_urls(attr=attr, filter_value=filter_value, @@ -222,12 +223,30 @@ class ServiceCatalog(object): except Exception: pass - msg = '%s endpoint for %s service' % (endpoint_type, service_type) - if service_name: - msg += ' named %s' % service_name - if region_name: - msg += ' in %s region' % region_name - msg += ' not found' + if service_name and region_name: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'named %(service_name)s in %(region_name)s region not ' + 'found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type, 'service_name': service_name, + 'region_name': region_name}) + elif service_name: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'named %(service_name)s not found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type, + 'service_name': service_name}) + elif region_name: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'in %(region_name)s region not found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type, 'region_name': region_name}) + else: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'not found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type}) + raise exceptions.EndpointNotFound(msg) @abc.abstractmethod diff --git a/keystoneclient/session.py b/keystoneclient/session.py index 111d369..084613f 100644 --- a/keystoneclient/session.py +++ b/keystoneclient/session.py @@ -18,13 +18,14 @@ import os import time from oslo.config import cfg +from oslo.serialization import jsonutils +from oslo.utils import importutils import requests import six from six.moves import urllib from keystoneclient import exceptions -from keystoneclient.openstack.common import importutils -from keystoneclient.openstack.common import jsonutils +from keystoneclient.i18n import _, _LI, _LW from keystoneclient import utils osprofiler_web = importutils.try_import("osprofiler.web") @@ -40,10 +41,10 @@ def _positive_non_zero_float(argument_value): try: value = float(argument_value) except ValueError: - msg = "%s must be a float" % argument_value + msg = _("%s must be a float") % argument_value raise argparse.ArgumentTypeError(msg) if value <= 0: - msg = "%s must be greater than 0" % argument_value + msg = _("%s must be greater than 0") % argument_value raise argparse.ArgumentTypeError(msg) return value @@ -138,7 +139,7 @@ class Session(object): # debug log. return - string_parts = ['REQ: curl -i'] + string_parts = ['REQ: curl -g -i'] # NOTE(jamielennox): None means let requests do its default validation # so we need to actually check that this is False. @@ -259,8 +260,8 @@ class Session(object): 'allow_redirects' is ignored as redirects are handled by the session. - :raises exceptions.ClientException: For connection failure, or to - indicate an error response code. + :raises keystoneclient.exceptions.ClientException: For connection + failure, or to indicate an error response code. :returns: The response to the request. """ @@ -274,7 +275,7 @@ class Session(object): token = self.get_token(auth) if not token: - raise exceptions.AuthorizationFailure("No token Available") + raise exceptions.AuthorizationFailure(_("No token Available")) headers['X-Auth-Token'] = token @@ -372,20 +373,20 @@ class Session(object): try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError: - msg = 'SSL exception connecting to %s' % url + msg = _('SSL exception connecting to %s') % url raise exceptions.SSLError(msg) except requests.exceptions.Timeout: - msg = 'Request to %s timed out' % url + msg = _('Request to %s timed out') % url raise exceptions.RequestTimeout(msg) except requests.exceptions.ConnectionError: - msg = 'Unable to establish connection to %s' % url + msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) as e: if connect_retries <= 0: raise - _logger.info('Failure: %s. Retrying in %.1fs.', - e, connect_retry_delay) + _logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'), + {'e': e, 'delay': connect_retry_delay}) time.sleep(connect_retry_delay) return self._send_request( @@ -411,8 +412,8 @@ class Session(object): try: location = resp.headers['location'] except KeyError: - _logger.warn("Failed to redirect request to %s as new " - "location was not provided.", resp.url) + _logger.warn(_LW("Failed to redirect request to %s as new " + "location was not provided."), resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. @@ -500,7 +501,8 @@ class Session(object): on the session. (optional) :type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin` - :raises AuthorizationFailure: if a new token fetch fails. + :raises keystoneclient.exceptions.AuthorizationFailure: if a new token + fetch fails. :returns: A valid token. :rtype: string @@ -509,13 +511,13 @@ class Session(object): auth = self.auth if not auth: - raise exceptions.MissingAuthPlugin("Token Required") + raise exceptions.MissingAuthPlugin(_("Token Required")) try: return auth.get_token(self) except exceptions.HttpError as exc: - raise exceptions.AuthorizationFailure("Authentication failure: " - "%s" % exc) + raise exceptions.AuthorizationFailure( + _("Authentication failure: %s") % exc) def get_endpoint(self, auth=None, **kwargs): """Get an endpoint as provided by the auth plugin. @@ -524,7 +526,8 @@ class Session(object): the session. (optional) :type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin` - :raises MissingAuthPlugin: if a plugin is not available. + :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not + available. :returns: An endpoint if available or None. :rtype: string @@ -533,8 +536,9 @@ class Session(object): auth = self.auth if not auth: - raise exceptions.MissingAuthPlugin('An auth plugin is required to ' - 'determine the endpoint URL.') + raise exceptions.MissingAuthPlugin( + _('An auth plugin is required to determine the endpoint ' + 'URL.')) return auth.get_endpoint(self, **kwargs) @@ -545,7 +549,7 @@ class Session(object): auth = self.auth if not auth: - msg = 'Auth plugin not available to invalidate' + msg = _('Auth plugin not available to invalidate') raise exceptions.MissingAuthPlugin(msg) return auth.invalidate() diff --git a/keystoneclient/shell.py b/keystoneclient/shell.py index ad8d127..be7330c 100644 --- a/keystoneclient/shell.py +++ b/keystoneclient/shell.py @@ -30,6 +30,7 @@ import logging import os import sys +from oslo.utils import encodeutils import six import keystoneclient @@ -37,7 +38,6 @@ from keystoneclient import access from keystoneclient.contrib.bootstrap import shell as shell_bootstrap from keystoneclient import exceptions as exc from keystoneclient.generic import shell as shell_generic -from keystoneclient.openstack.common import strutils from keystoneclient import session from keystoneclient import utils from keystoneclient.v2_0 import shell as shell_v2_0 @@ -463,7 +463,7 @@ def main(): OpenStackIdentityShell().main(sys.argv[1:]) except Exception as e: - print(strutils.safe_encode(six.text_type(e)), file=sys.stderr) + print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) sys.exit(1) diff --git a/keystoneclient/tests/auth/test_identity_common.py b/keystoneclient/tests/auth/test_identity_common.py index 9a369f7..4a0cf57 100644 --- a/keystoneclient/tests/auth/test_identity_common.py +++ b/keystoneclient/tests/auth/test_identity_common.py @@ -14,6 +14,7 @@ import abc import datetime import uuid +from oslo.utils import timeutils import six from keystoneclient import access @@ -21,7 +22,6 @@ from keystoneclient.auth import base from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient import fixture -from keystoneclient.openstack.common import timeutils from keystoneclient import session from keystoneclient.tests import utils diff --git a/keystoneclient/tests/client_fixtures.py b/keystoneclient/tests/client_fixtures.py index d58deb2..cadae11 100644 --- a/keystoneclient/tests/client_fixtures.py +++ b/keystoneclient/tests/client_fixtures.py @@ -15,12 +15,12 @@ import os import fixtures +from oslo.serialization import jsonutils +from oslo.utils import timeutils import six import testresources from keystoneclient.common import cms -from keystoneclient.openstack.common import jsonutils -from keystoneclient.openstack.common import timeutils from keystoneclient import utils diff --git a/keystoneclient/tests/generic/test_client.py b/keystoneclient/tests/generic/test_client.py index fc5816b..0f25f41 100644 --- a/keystoneclient/tests/generic/test_client.py +++ b/keystoneclient/tests/generic/test_client.py @@ -13,8 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo.serialization import jsonutils + from keystoneclient.generic import client -from keystoneclient.openstack.common import jsonutils from keystoneclient.tests import utils BASE_HOST = 'http://keystone.example.com' diff --git a/keystoneclient/tests/test_auth_token_middleware.py b/keystoneclient/tests/test_auth_token_middleware.py index 3c42db3..4e70b73 100644 --- a/keystoneclient/tests/test_auth_token_middleware.py +++ b/keystoneclient/tests/test_auth_token_middleware.py @@ -25,6 +25,8 @@ import uuid import fixtures import iso8601 import mock +from oslo.serialization import jsonutils +from oslo.utils import timeutils from requests_mock.contrib import fixture as mock_fixture from six.moves.urllib import parse as urlparse import testresources @@ -37,9 +39,7 @@ from keystoneclient.common import cms from keystoneclient import exceptions from keystoneclient import fixture from keystoneclient.middleware import auth_token -from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import memorycache -from keystoneclient.openstack.common import timeutils from keystoneclient.tests import client_fixtures from keystoneclient.tests import utils @@ -1007,7 +1007,7 @@ class CommonAuthTokenMiddlewareTest(object): token = self.token_dict['signed_token_scoped'] req.headers['X-Auth-Token'] = token req.environ.update(extra_environ) - timeutils_utcnow = 'keystoneclient.openstack.common.timeutils.utcnow' + timeutils_utcnow = 'oslo.utils.timeutils.utcnow' now = datetime.datetime.utcnow() with mock.patch(timeutils_utcnow) as mock_utcnow: mock_utcnow.return_value = now @@ -1759,7 +1759,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest): auth_token.confirm_token_not_expired, data) - @mock.patch('keystoneclient.openstack.common.timeutils.utcnow') + @mock.patch('oslo.utils.timeutils.utcnow') def test_v2_token_with_timezone_offset_not_expired(self, mock_utcnow): current_time = timeutils.parse_isotime('2000-01-01T00:01:10.000123Z') current_time = timeutils.normalize_time(current_time) @@ -1770,7 +1770,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest): actual_expires = auth_token.confirm_token_not_expired(data) self.assertEqual(actual_expires, expected_expires) - @mock.patch('keystoneclient.openstack.common.timeutils.utcnow') + @mock.patch('oslo.utils.timeutils.utcnow') def test_v2_token_with_timezone_offset_expired(self, mock_utcnow): current_time = timeutils.parse_isotime('2000-01-01T00:01:10.000123Z') current_time = timeutils.normalize_time(current_time) @@ -1794,7 +1794,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest): auth_token.confirm_token_not_expired, data) - @mock.patch('keystoneclient.openstack.common.timeutils.utcnow') + @mock.patch('oslo.utils.timeutils.utcnow') def test_v3_token_with_timezone_offset_not_expired(self, mock_utcnow): current_time = timeutils.parse_isotime('2000-01-01T00:01:10.000123Z') current_time = timeutils.normalize_time(current_time) @@ -1806,7 +1806,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest): actual_expires = auth_token.confirm_token_not_expired(data) self.assertEqual(actual_expires, expected_expires) - @mock.patch('keystoneclient.openstack.common.timeutils.utcnow') + @mock.patch('oslo.utils.timeutils.utcnow') def test_v3_token_with_timezone_offset_expired(self, mock_utcnow): current_time = timeutils.parse_isotime('2000-01-01T00:01:10.000123Z') current_time = timeutils.normalize_time(current_time) diff --git a/keystoneclient/tests/test_cms.py b/keystoneclient/tests/test_cms.py index 8cef987..9af3bd4 100644 --- a/keystoneclient/tests/test_cms.py +++ b/keystoneclient/tests/test_cms.py @@ -30,6 +30,18 @@ class CMSTest(utils.TestCase, testresources.ResourcedTestCase): resources = [('examples', client_fixtures.EXAMPLES_RESOURCE)] + def __init__(self, *args, **kwargs): + super(CMSTest, self).__init__(*args, **kwargs) + process = subprocess.Popen(['openssl', 'version'], + stdout=subprocess.PIPE) + out, err = process.communicate() + # Example output: 'OpenSSL 0.9.8za 5 Jun 2014' + openssl_version = out.split()[1] + + if err or openssl_version.startswith(b'0'): + raise Exception('Your version of OpenSSL is not supported. ' + 'You will need to update it to 1.0 or later.') + def test_cms_verify(self): self.assertRaises(exceptions.CertificateConfigError, cms.cms_verify, diff --git a/keystoneclient/tests/test_discovery.py b/keystoneclient/tests/test_discovery.py index 811e65c..a999a19 100644 --- a/keystoneclient/tests/test_discovery.py +++ b/keystoneclient/tests/test_discovery.py @@ -13,6 +13,7 @@ import re import uuid +from oslo.serialization import jsonutils import six from testtools import matchers @@ -22,7 +23,6 @@ from keystoneclient import client from keystoneclient import discover from keystoneclient import exceptions from keystoneclient import fixture -from keystoneclient.openstack.common import jsonutils from keystoneclient import session from keystoneclient.tests import utils from keystoneclient.v2_0 import client as v2_client diff --git a/keystoneclient/tests/test_keyring.py b/keystoneclient/tests/test_keyring.py index 0ad0587..4bdf731 100644 --- a/keystoneclient/tests/test_keyring.py +++ b/keystoneclient/tests/test_keyring.py @@ -13,10 +13,10 @@ import datetime import mock +from oslo.utils import timeutils from keystoneclient import access from keystoneclient import httpclient -from keystoneclient.openstack.common import timeutils from keystoneclient.tests import utils from keystoneclient.tests.v2_0 import client_fixtures diff --git a/keystoneclient/tests/test_s3_token_middleware.py b/keystoneclient/tests/test_s3_token_middleware.py index 0233e8b..ab77b79 100644 --- a/keystoneclient/tests/test_s3_token_middleware.py +++ b/keystoneclient/tests/test_s3_token_middleware.py @@ -13,13 +13,13 @@ # under the License. import mock +from oslo.serialization import jsonutils import requests import six import testtools import webob from keystoneclient.middleware import s3_token -from keystoneclient.openstack.common import jsonutils from keystoneclient.tests import utils diff --git a/keystoneclient/tests/test_session.py b/keystoneclient/tests/test_session.py index 99c9e6e..6a9d408 100644 --- a/keystoneclient/tests/test_session.py +++ b/keystoneclient/tests/test_session.py @@ -17,6 +17,7 @@ import uuid import mock from oslo.config import cfg from oslo.config import fixture as config +from oslo.serialization import jsonutils import requests import six from testtools import matchers @@ -24,7 +25,6 @@ from testtools import matchers from keystoneclient import adapter from keystoneclient.auth import base from keystoneclient import exceptions -from keystoneclient.openstack.common import jsonutils from keystoneclient import session as client_session from keystoneclient.tests import utils diff --git a/keystoneclient/tests/utils.py b/keystoneclient/tests/utils.py index 4465835..a6f06c5 100644 --- a/keystoneclient/tests/utils.py +++ b/keystoneclient/tests/utils.py @@ -18,14 +18,13 @@ import uuid import fixtures import mock from mox3 import mox +from oslo.serialization import jsonutils import requests from requests_mock.contrib import fixture import six from six.moves.urllib import parse as urlparse import testtools -from keystoneclient.openstack.common import jsonutils - class TestCase(testtools.TestCase): diff --git a/keystoneclient/tests/v2_0/test_access.py b/keystoneclient/tests/v2_0/test_access.py index f384473..1cd926a 100644 --- a/keystoneclient/tests/v2_0/test_access.py +++ b/keystoneclient/tests/v2_0/test_access.py @@ -13,11 +13,11 @@ import datetime import uuid +from oslo.utils import timeutils import testresources from keystoneclient import access from keystoneclient import fixture -from keystoneclient.openstack.common import timeutils from keystoneclient.tests import client_fixtures as token_data from keystoneclient.tests.v2_0 import client_fixtures from keystoneclient.tests.v2_0 import utils diff --git a/keystoneclient/tests/v2_0/test_auth.py b/keystoneclient/tests/v2_0/test_auth.py index cdf05f4..fd9ffc3 100644 --- a/keystoneclient/tests/v2_0/test_auth.py +++ b/keystoneclient/tests/v2_0/test_auth.py @@ -13,9 +13,10 @@ import copy import datetime +from oslo.serialization import jsonutils +from oslo.utils import timeutils + from keystoneclient import exceptions -from keystoneclient.openstack.common import jsonutils -from keystoneclient.openstack.common import timeutils from keystoneclient.tests.v2_0 import utils from keystoneclient.v2_0 import client diff --git a/keystoneclient/tests/v2_0/test_shell.py b/keystoneclient/tests/v2_0/test_shell.py index 0fafb71..57bbe9d 100644 --- a/keystoneclient/tests/v2_0/test_shell.py +++ b/keystoneclient/tests/v2_0/test_shell.py @@ -104,6 +104,8 @@ class ShellTests(utils.TestCase): self.stub_url('POST', ['users'], json={'user': {}}) with mock.patch('getpass.getpass') as mock_getpass: + del(os.environ['OS_PASSWORD']) + mock_stdin.isatty = lambda: True mock_getpass.return_value = 'newpass' self.run_command('user-create --name new-user --pass') diff --git a/keystoneclient/tests/v3/test_access.py b/keystoneclient/tests/v3/test_access.py index df2566d..0f0b0e2 100644 --- a/keystoneclient/tests/v3/test_access.py +++ b/keystoneclient/tests/v3/test_access.py @@ -13,9 +13,10 @@ import datetime import uuid +from oslo.utils import timeutils + from keystoneclient import access from keystoneclient import fixture -from keystoneclient.openstack.common import timeutils from keystoneclient.tests.v3 import client_fixtures from keystoneclient.tests.v3 import utils diff --git a/keystoneclient/tests/v3/test_auth.py b/keystoneclient/tests/v3/test_auth.py index fb079b6..14a762f 100644 --- a/keystoneclient/tests/v3/test_auth.py +++ b/keystoneclient/tests/v3/test_auth.py @@ -10,8 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo.serialization import jsonutils + from keystoneclient import exceptions -from keystoneclient.openstack.common import jsonutils from keystoneclient.tests.v3 import utils from keystoneclient.v3 import client diff --git a/keystoneclient/tests/v3/test_client.py b/keystoneclient/tests/v3/test_client.py index 640ee76..ddc7850 100644 --- a/keystoneclient/tests/v3/test_client.py +++ b/keystoneclient/tests/v3/test_client.py @@ -33,6 +33,7 @@ class KeystoneClientTest(utils.TestCase): self.assertFalse(c.auth_ref.project_scoped) self.assertEqual(c.auth_user_id, 'c4da488862bd435c9e6c0275a0d0e49a') + self.assertFalse(c.has_service_catalog()) def test_domain_scoped_init(self): self.stub_auth(json=client_fixtures.domain_scoped_token()) diff --git a/keystoneclient/tests/v3/test_oauth1.py b/keystoneclient/tests/v3/test_oauth1.py index 0dc9c2a..d12ffdd 100644 --- a/keystoneclient/tests/v3/test_oauth1.py +++ b/keystoneclient/tests/v3/test_oauth1.py @@ -14,11 +14,11 @@ import uuid import mock +from oslo.utils import timeutils import six from six.moves.urllib import parse as urlparse from testtools import matchers -from keystoneclient.openstack.common import timeutils from keystoneclient import session from keystoneclient.tests.v3 import client_fixtures from keystoneclient.tests.v3 import utils diff --git a/keystoneclient/tests/v3/test_trusts.py b/keystoneclient/tests/v3/test_trusts.py index 15e9348..0b8028f 100644 --- a/keystoneclient/tests/v3/test_trusts.py +++ b/keystoneclient/tests/v3/test_trusts.py @@ -13,8 +13,9 @@ import uuid +from oslo.utils import timeutils + from keystoneclient import exceptions -from keystoneclient.openstack.common import timeutils from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib import trusts diff --git a/keystoneclient/utils.py b/keystoneclient/utils.py index d3342f4..436b2f6 100644 --- a/keystoneclient/utils.py +++ b/keystoneclient/utils.py @@ -17,11 +17,11 @@ import inspect import logging import sys +from oslo.utils import encodeutils import prettytable import six from keystoneclient import exceptions -from keystoneclient.openstack.common import strutils logger = logging.getLogger(__name__) @@ -61,7 +61,7 @@ def print_list(objs, fields, formatters={}, order_by=None): if order_by is None: order_by = fields[0] - encoded = strutils.safe_encode(pt.get_string(sortby=order_by)) + encoded = encodeutils.safe_encode(pt.get_string(sortby=order_by)) if six.PY3: encoded = encoded.decode() print(encoded) @@ -88,7 +88,7 @@ def print_dict(d, wrap=0): value = '' value = _word_wrap(value, max_length=wrap) pt.add_row([prop, value]) - encoded = strutils.safe_encode(pt.get_string(sortby='Property')) + encoded = encodeutils.safe_encode(pt.get_string(sortby='Property')) if six.PY3: encoded = encoded.decode() print(encoded) diff --git a/keystoneclient/v2_0/__init__.py b/keystoneclient/v2_0/__init__.py index f99be3bb..30407e0 100644 --- a/keystoneclient/v2_0/__init__.py +++ b/keystoneclient/v2_0/__init__.py @@ -1,5 +1,4 @@ -# flake8: noqa -from keystoneclient.v2_0.client import Client +from keystoneclient.v2_0.client import Client # noqa __all__ = [ diff --git a/keystoneclient/v2_0/client.py b/keystoneclient/v2_0/client.py index 062678f..dc790e8 100644 --- a/keystoneclient/v2_0/client.py +++ b/keystoneclient/v2_0/client.py @@ -18,6 +18,7 @@ import logging from keystoneclient.auth.identity import v2 as v2_auth from keystoneclient import exceptions from keystoneclient import httpclient +from keystoneclient.i18n import _ from keystoneclient.v2_0 import ec2 from keystoneclient.v2_0 import endpoints from keystoneclient.v2_0 import extensions @@ -158,12 +159,12 @@ class Client(httpclient.HTTPClient): password. :returns: access.AccessInfo if authentication was successful. - :raises: AuthorizationFailure if unable to authenticate or validate - the existing authorization token + :raises keystoneclient.exceptions.AuthorizationFailure: if unable to + authenticate or validate the existing authorization token """ try: if auth_url is None: - raise ValueError("Cannot authenticate without an auth_url") + raise ValueError(_("Cannot authenticate without an auth_url")) new_kwargs = {'trust_id': trust_id, 'tenant_id': project_id or tenant_id, @@ -175,7 +176,7 @@ class Client(httpclient.HTTPClient): plugin = v2_auth.Password(auth_url, username, password, **new_kwargs) else: - msg = 'A username and password or token is required.' + msg = _('A username and password or token is required.') raise exceptions.AuthorizationFailure(msg) return plugin.get_auth_ref(self.session) @@ -183,8 +184,9 @@ class Client(httpclient.HTTPClient): _logger.debug("Authorization Failed.") raise except exceptions.EndpointNotFound: - msg = 'There was no suitable authentication url for this request' + msg = ( + _('There was no suitable authentication url for this request')) raise exceptions.AuthorizationFailure(msg) except Exception as e: - raise exceptions.AuthorizationFailure("Authorization Failed: " - "%s" % e) + raise exceptions.AuthorizationFailure( + _("Authorization Failed: %s") % e) diff --git a/keystoneclient/v2_0/shell.py b/keystoneclient/v2_0/shell.py index 5d8d265..a2cb1ab 100755 --- a/keystoneclient/v2_0/shell.py +++ b/keystoneclient/v2_0/shell.py @@ -26,9 +26,10 @@ import argparse import getpass import sys +from oslo.utils import strutils import six -from keystoneclient.openstack.common import strutils +from keystoneclient.i18n import _ from keystoneclient import utils from keystoneclient.v2_0 import client @@ -38,9 +39,9 @@ ASK_FOR_PASSWORD = object() def require_service_catalog(f): - msg = ('Configuration error: Client configured to run without a service ' - 'catalog. Run the client using --os-auth-url or OS_AUTH_URL, ' - 'instead of --os-endpoint or OS_SERVICE_ENDPOINT, for example.') + msg = _('Configuration error: Client configured to run without a service ' + 'catalog. Run the client using --os-auth-url or OS_AUTH_URL, ' + 'instead of --os-endpoint or OS_SERVICE_ENDPOINT, for example.') def wrapped(kc, args): if not kc.has_service_catalog(): @@ -121,15 +122,15 @@ def do_user_update(kc, args): kwargs['enabled'] = strutils.bool_from_string(args.enabled) if not len(kwargs): - print("User not updated, no arguments present.") + print(_("User not updated, no arguments present.")) return user = utils.find_resource(kc.users, args.user) try: kc.users.update(user, **kwargs) - print('User has been updated.') + print(_('User has been updated.')) except Exception as e: - print('Unable to update user: %s' % e) + print(_('Unable to update user: %s') % e) @utils.arg('--pass', metavar='<password>', dest='passwd', required=False, @@ -141,8 +142,8 @@ def do_user_password_update(kc, args): user = utils.find_resource(kc.users, args.user) new_passwd = args.passwd or utils.prompt_for_password() if new_passwd is None: - msg = ("\nPlease specify password using the --pass option " - "or using the prompt") + msg = (_("\nPlease specify password using the --pass option " + "or using the prompt")) sys.exit(msg) kc.users.update_password(user, new_passwd) @@ -163,20 +164,20 @@ def do_password_update(kc, args): if args.currentpasswd is not None: currentpasswd = args.currentpasswd if currentpasswd is None: - currentpasswd = getpass.getpass('Current Password: ') + currentpasswd = getpass.getpass(_('Current Password: ')) newpasswd = args.newpasswd while newpasswd is None: - passwd1 = getpass.getpass('New Password: ') - passwd2 = getpass.getpass('Repeat New Password: ') + passwd1 = getpass.getpass(_('New Password: ')) + passwd2 = getpass.getpass(_('Repeat New Password: ')) if passwd1 == passwd2: newpasswd = passwd1 kc.users.update_own_password(currentpasswd, newpasswd) if args.os_password != newpasswd: - print("You should update the password you are using to authenticate " - "to match your new password") + print(_("You should update the password you are using to authenticate " + "to match your new password")) @utils.arg('user', metavar='<user>', help='Name or ID of user to delete.') @@ -234,7 +235,7 @@ def do_tenant_update(kc, args): kwargs.update({'enabled': strutils.bool_from_string(args.enabled)}) if kwargs == {}: - print("Tenant not updated, no arguments present.") + print(_("Tenant not updated, no arguments present.")) return tenant.update(**kwargs) @@ -450,9 +451,9 @@ def do_ec2_credentials_delete(kc, args): args.user_id = kc.auth_user_id try: kc.ec2.delete(args.user_id, args.access) - print('Credential has been deleted.') + print(_('Credential has been deleted.')) except Exception as e: - print('Unable to delete credential: %s' % e) + print(_('Unable to delete credential: %s') % e) @utils.arg('--service', metavar='<service-type>', default=None, @@ -463,7 +464,7 @@ def do_catalog(kc, args): endpoints = kc.service_catalog.get_endpoints(service_type=args.service) for (service, service_endpoints) in six.iteritems(endpoints): if len(service_endpoints) > 0: - print("Service: %s" % service) + print(_("Service: %s") % service) for ep in service_endpoints: utils.print_dict(ep) @@ -489,7 +490,7 @@ def do_endpoint_get(kc, args): if args.attr and args.value: kwargs.update({'attr': args.attr, 'filter_value': args.value}) elif args.attr or args.value: - print('Both --attr and --value required.') + print(_('Both --attr and --value required.')) return url = kc.service_catalog.url_for(**kwargs) @@ -531,9 +532,9 @@ def do_endpoint_delete(kc, args): """Delete a service endpoint.""" try: kc.endpoints.delete(args.id) - print('Endpoint has been deleted.') + print(_('Endpoint has been deleted.')) except Exception: - print('Unable to delete endpoint.') + print(_('Unable to delete endpoint.')) @utils.arg('--wrap', metavar='<integer>', default=0, diff --git a/keystoneclient/v2_0/tokens.py b/keystoneclient/v2_0/tokens.py index e5a21d4..fb48738 100644 --- a/keystoneclient/v2_0/tokens.py +++ b/keystoneclient/v2_0/tokens.py @@ -13,6 +13,7 @@ from keystoneclient import auth from keystoneclient import base from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils @@ -45,7 +46,8 @@ class TokenManager(base.Manager): params = {"auth": {"passwordCredentials": {"username": username, "password": password}}} else: - raise ValueError('A username and password or token is required.') + raise ValueError( + _('A username and password or token is required.')) if tenant_id: params['auth']['tenantId'] = tenant_id elif tenant_name: diff --git a/keystoneclient/v3/__init__.py b/keystoneclient/v3/__init__.py index 4685f9f..56b26d3 100644 --- a/keystoneclient/v3/__init__.py +++ b/keystoneclient/v3/__init__.py @@ -1,5 +1,5 @@ -# flake8: noqa -from keystoneclient.v3.client import Client + +from keystoneclient.v3.client import Client # noqa __all__ = [ diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index a271db3..7d3f0fe 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -15,10 +15,12 @@ import logging +from oslo.serialization import jsonutils + from keystoneclient.auth.identity import v3 as v3_auth from keystoneclient import exceptions from keystoneclient import httpclient -from keystoneclient.openstack.common import jsonutils +from keystoneclient.i18n import _ from keystoneclient.v3.contrib import endpoint_filter from keystoneclient.v3.contrib import endpoint_policy from keystoneclient.v3.contrib import federation @@ -202,7 +204,7 @@ EndpointPolicyManager` if self.auth_ref.domain_scoped: if not self.auth_ref.domain_id: raise exceptions.AuthorizationFailure( - "Token didn't provide domain_id") + _("Token didn't provide domain_id")) self._process_management_url(kwargs.get('region_name')) self.domain_name = self.auth_ref.domain_name self.domain_id = self.auth_ref.domain_id @@ -227,14 +229,15 @@ EndpointPolicyManager` be used in the request. :returns: access.AccessInfo if authentication was successful. - :raises: AuthorizationFailure if unable to authenticate or validate - the existing authorization token - :raises: Unauthorized if authentication fails due to invalid token + :raises keystoneclient.exceptions.AuthorizationFailure: if unable to + authenticate or validate the existing authorization token. + :raises keystoneclient.exceptions.Unauthorized: if authentication fails + due to invalid token. """ try: if auth_url is None: - raise ValueError("Cannot authenticate without an auth_url") + raise ValueError(_("Cannot authenticate without an auth_url")) auth_methods = [] @@ -250,7 +253,7 @@ EndpointPolicyManager` auth_methods.append(m) if not auth_methods: - msg = 'A user and password or token is required.' + msg = _('A user and password or token is required.') raise exceptions.AuthorizationFailure(msg) plugin = v3_auth.Auth(auth_url, auth_methods, @@ -267,8 +270,9 @@ EndpointPolicyManager` _logger.debug('Authorization failed.') raise except exceptions.EndpointNotFound: - msg = 'There was no suitable authentication url for this request' + msg = _('There was no suitable authentication url for this' + ' request') raise exceptions.AuthorizationFailure(msg) except Exception as e: - raise exceptions.AuthorizationFailure('Authorization failed: ' - '%s' % e) + raise exceptions.AuthorizationFailure( + _('Authorization failed: %s') % e) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index c0a1eef..3e3b7ef 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -14,6 +14,7 @@ from keystoneclient import base from keystoneclient import exceptions +from keystoneclient.i18n import _ class EndpointFilterManager(base.Manager): @@ -31,7 +32,7 @@ class EndpointFilterManager(base.Manager): elif endpoint_id: api_path = '/endpoints/%s/projects' % (endpoint_id) else: - msg = 'Must specify a project, an endpoint, or both' + msg = _('Must specify a project, an endpoint, or both') raise exceptions.ValidationError(msg) return self.OS_EP_FILTER_EXT + api_path @@ -39,7 +40,7 @@ class EndpointFilterManager(base.Manager): def add_endpoint_to_project(self, project, endpoint): """Create a project-endpoint association.""" if not (project and endpoint): - raise ValueError('project and endpoint are required') + raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) @@ -48,7 +49,7 @@ class EndpointFilterManager(base.Manager): def delete_endpoint_from_project(self, project, endpoint): """Remove a project-endpoint association.""" if not (project and endpoint): - raise ValueError('project and endpoint are required') + raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) @@ -57,7 +58,7 @@ class EndpointFilterManager(base.Manager): def check_endpoint_in_project(self, project, endpoint): """Checks if project-endpoint association exist.""" if not (project and endpoint): - raise ValueError('project and endpoint are required') + raise ValueError(_('project and endpoint are required')) base_url = self._build_base_url(project=project, endpoint=endpoint) @@ -66,7 +67,7 @@ class EndpointFilterManager(base.Manager): def list_endpoints_for_project(self, project): """List all endpoints for a given project.""" if not project: - raise ValueError('project is required') + raise ValueError(_('project is required')) base_url = self._build_base_url(project=project) return super(EndpointFilterManager, self)._list( @@ -77,7 +78,7 @@ class EndpointFilterManager(base.Manager): def list_projects_for_endpoint(self, endpoint): """List all projects for a given endpoint.""" if not endpoint: - raise ValueError('endpoint is required') + raise ValueError(_('endpoint is required')) base_url = self._build_base_url(endpoint=endpoint) return super(EndpointFilterManager, self)._list( diff --git a/keystoneclient/v3/contrib/endpoint_policy.py b/keystoneclient/v3/contrib/endpoint_policy.py index 9d4d997..c473ad6 100644 --- a/keystoneclient/v3/contrib/endpoint_policy.py +++ b/keystoneclient/v3/contrib/endpoint_policy.py @@ -13,6 +13,7 @@ # under the License. from keystoneclient import base +from keystoneclient.i18n import _ from keystoneclient.v3 import policies @@ -24,7 +25,7 @@ class EndpointPolicyManager(base.Manager): def _act_on_policy_association_for_endpoint( self, policy, endpoint, action): if not (policy and endpoint): - raise ValueError('policy and endpoint are required') + raise ValueError(_('policy and endpoint are required')) policy_id = base.getid(policy) endpoint_id = base.getid(endpoint) @@ -52,7 +53,7 @@ class EndpointPolicyManager(base.Manager): def _act_on_policy_association_for_service(self, policy, service, action): if not (policy and service): - raise ValueError('policy and service are required') + raise ValueError(_('policy and service are required')) policy_id = base.getid(policy) service_id = base.getid(service) @@ -81,7 +82,7 @@ class EndpointPolicyManager(base.Manager): def _act_on_policy_association_for_region_and_service( self, policy, region, service, action): if not (policy and region and service): - raise ValueError('policy, region and service are required') + raise ValueError(_('policy, region and service are required')) policy_id = base.getid(policy) region_id = base.getid(region) @@ -121,7 +122,7 @@ class EndpointPolicyManager(base.Manager): """ if not endpoint: - raise ValueError('endpoint is required') + raise ValueError(_('endpoint is required')) endpoint_id = base.getid(endpoint) url = ('/endpoints/%(endpoint_id)s/%(ext_name)s/policy') % { @@ -141,7 +142,7 @@ class EndpointPolicyManager(base.Manager): """ if not policy: - raise ValueError('policy is required') + raise ValueError(_('policy is required')) policy_id = base.getid(policy) url = ('/policies/%(policy_id)s/%(ext_name)s/endpoints') % { diff --git a/keystoneclient/v3/contrib/federation/__init__.py b/keystoneclient/v3/contrib/federation/__init__.py index 8d31b75..ee9bcef 100644 --- a/keystoneclient/v3/contrib/federation/__init__.py +++ b/keystoneclient/v3/contrib/federation/__init__.py @@ -10,4 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -from keystoneclient.v3.contrib.federation.core import * # flake8: noqa +from keystoneclient.v3.contrib.federation.core import * # noqa diff --git a/keystoneclient/v3/contrib/federation/mappings.py b/keystoneclient/v3/contrib/federation/mappings.py index d0c033f..1cdb879 100644 --- a/keystoneclient/v3/contrib/federation/mappings.py +++ b/keystoneclient/v3/contrib/federation/mappings.py @@ -48,35 +48,30 @@ class MappingManager(base.CrudManager): :param mapping_id: user defined string identifier of the federation mapping. - :param rules: a JSON dictionary with list a list - of mapping rules. - - Example of the ``rules``:: - - { - "mapping": { - "rules": [ - { - "local": [ - { - "group": { - "id": "0cd5e9" - } - } - ], - "remote": [ - { - "type": "orgPersonType", - "not_any_of": [ - "Contractor", - "Guest" - ] - } - ] - } - ] - } - } + :param rules: a list of mapping rules. + + Example of the ``rules`` parameter:: + + [ + { + "local": [ + { + "group": { + "id": "0cd5e9" + } + } + ], + "remote": [ + { + "type": "orgPersonType", + "not_any_of": [ + "Contractor", + "Guest" + ] + } + ] + } + ] """ return self._build_url_and_put( @@ -112,35 +107,31 @@ class MappingManager(base.CrudManager): :param mapping: a Mapping type object with mapping id stored inside. - :param rules: a JSON dictionary with list a list - of mapping rules. - - Example of the ``rules``:: - - { - "mapping": { - "rules": [ - { - "local": [ - { - "group": { - "id": "0cd5e9" - } - } - ], - "remote": [ - { - "type": "orgPersonType", - "not_any_of": [ - "Contractor", - "Guest" - ] - } - ] - } - ] - } - } + :param rules: a list of mapping rules. + + Example of the ``rules`` parameter:: + + + [ + { + "local": [ + { + "group": { + "id": "0cd5e9" + } + } + ], + "remote": [ + { + "type": "orgPersonType", + "not_any_of": [ + "Contractor", + "Guest" + ] + } + ] + } + ] """ return super(MappingManager, self).update( diff --git a/keystoneclient/v3/contrib/oauth1/core.py b/keystoneclient/v3/contrib/oauth1/core.py index 36823f0..8d10325 100644 --- a/keystoneclient/v3/contrib/oauth1/core.py +++ b/keystoneclient/v3/contrib/oauth1/core.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneclient.i18n import _ from keystoneclient.v3.contrib.oauth1 import access_tokens from keystoneclient.v3.contrib.oauth1 import consumers from keystoneclient.v3.contrib.oauth1 import request_tokens @@ -59,6 +60,6 @@ class OAuthManagerOptionalImportProxy(object): def __getattribute__(self, name): if name in ('access_tokens', 'consumers', 'request_tokens'): raise NotImplementedError( - 'To use %r oauthlib must be installed' % name) + _('To use %r oauthlib must be installed') % name) return super(OAuthManagerOptionalImportProxy, self).__getattribute__(name) diff --git a/keystoneclient/v3/contrib/trusts.py b/keystoneclient/v3/contrib/trusts.py index ed199e6..0fb75ca 100644 --- a/keystoneclient/v3/contrib/trusts.py +++ b/keystoneclient/v3/contrib/trusts.py @@ -10,9 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo.utils import timeutils + from keystoneclient import base from keystoneclient import exceptions -from keystoneclient.openstack.common import timeutils +from keystoneclient.i18n import _ class Trust(base.Resource): @@ -74,8 +76,8 @@ class TrustManager(base.CrudManager): **kwargs) def update(self): - raise exceptions.MethodNotImplemented('Update not supported' - ' for trusts') + raise exceptions.MethodNotImplemented( + _('Update not supported for trusts')) def list(self, trustee_user=None, trustor_user=None, **kwargs): """List Trusts.""" diff --git a/keystoneclient/v3/credentials.py b/keystoneclient/v3/credentials.py index be28cc5..833a32f 100644 --- a/keystoneclient/v3/credentials.py +++ b/keystoneclient/v3/credentials.py @@ -15,6 +15,7 @@ # under the License. from keystoneclient import base +from keystoneclient.i18n import _ from keystoneclient import utils @@ -46,7 +47,7 @@ class CredentialManager(base.CrudManager): return data else: raise ValueError( - "Credential requires blob to be specified") + _("Credential requires blob to be specified")) @utils.positional(1, enforcement=utils.positional.WARN) def create(self, user, type, blob=None, data=None, project=None, **kwargs): diff --git a/keystoneclient/v3/endpoints.py b/keystoneclient/v3/endpoints.py index 3f9dfbd..eeddc4c 100644 --- a/keystoneclient/v3/endpoints.py +++ b/keystoneclient/v3/endpoints.py @@ -16,6 +16,7 @@ from keystoneclient import base from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils @@ -45,7 +46,7 @@ class EndpointManager(base.CrudManager): def _validate_interface(self, interface): if interface is not None and interface not in VALID_INTERFACES: - msg = '"interface" must be one of: %s' + msg = _('"interface" must be one of: %s') msg = msg % ', '.join(VALID_INTERFACES) raise exceptions.ValidationError(msg) diff --git a/keystoneclient/v3/role_assignments.py b/keystoneclient/v3/role_assignments.py index 5394c3d..6518e43 100644 --- a/keystoneclient/v3/role_assignments.py +++ b/keystoneclient/v3/role_assignments.py @@ -12,6 +12,7 @@ from keystoneclient import base from keystoneclient import exceptions +from keystoneclient.i18n import _ class RoleAssignment(base.Resource): @@ -37,12 +38,12 @@ class RoleAssignmentManager(base.CrudManager): def _check_not_user_and_group(self, user, group): if user and group: - msg = 'Specify either a user or group, not both' + msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) def _check_not_domain_and_project(self, domain, project): if domain and project: - msg = 'Specify either a domain or project, not both' + msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) def list(self, user=None, group=None, project=None, domain=None, role=None, @@ -87,25 +88,25 @@ class RoleAssignmentManager(base.CrudManager): return super(RoleAssignmentManager, self).list(**query_params) def create(self, **kwargs): - raise exceptions.MethodNotImplemented('Create not supported for' - ' role assignments') + raise exceptions.MethodNotImplemented( + _('Create not supported for role assignments')) def update(self, **kwargs): - raise exceptions.MethodNotImplemented('Update not supported for' - ' role assignments') + raise exceptions.MethodNotImplemented( + _('Update not supported for role assignments')) def get(self, **kwargs): - raise exceptions.MethodNotImplemented('Get not supported for' - ' role assignments') + raise exceptions.MethodNotImplemented( + _('Get not supported for role assignments')) def find(self, **kwargs): - raise exceptions.MethodNotImplemented('Find not supported for' - ' role assignments') + raise exceptions.MethodNotImplemented( + _('Find not supported for role assignments')) def put(self, **kwargs): - raise exceptions.MethodNotImplemented('Put not supported for' - ' role assignments') + raise exceptions.MethodNotImplemented( + _('Put not supported for role assignments')) def delete(self, **kwargs): - raise exceptions.MethodNotImplemented('Delete not supported for' - ' role assignments') + raise exceptions.MethodNotImplemented( + _('Delete not supported for role assignments')) diff --git a/keystoneclient/v3/roles.py b/keystoneclient/v3/roles.py index 40c624a..3eb68d1 100644 --- a/keystoneclient/v3/roles.py +++ b/keystoneclient/v3/roles.py @@ -16,6 +16,7 @@ from keystoneclient import base from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils @@ -59,18 +60,18 @@ class RoleManager(base.CrudManager): def _require_domain_xor_project(self, domain, project): if domain and project: - msg = 'Specify either a domain or project, not both' + msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) elif not (domain or project): - msg = 'Must specify either a domain or project' + msg = _('Must specify either a domain or project') raise exceptions.ValidationError(msg) def _require_user_xor_group(self, user, group): if user and group: - msg = 'Specify either a user or group, not both' + msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) elif not (user or group): - msg = 'Must specify either a user or group' + msg = _('Must specify either a user or group') raise exceptions.ValidationError(msg) @utils.positional(1, enforcement=utils.positional.WARN) diff --git a/keystoneclient/v3/users.py b/keystoneclient/v3/users.py index 140c785..3343d50 100644 --- a/keystoneclient/v3/users.py +++ b/keystoneclient/v3/users.py @@ -18,6 +18,7 @@ import logging from keystoneclient import base from keystoneclient import exceptions +from keystoneclient.i18n import _, _LW from keystoneclient import utils LOG = logging.getLogger(__name__) @@ -41,7 +42,7 @@ class UserManager(base.CrudManager): def _require_user_and_group(self, user, group): if not (user and group): - msg = 'Specify both a user and a group' + msg = _('Specify both a user and a group') raise exceptions.ValidationError(msg) @utils.positional(1, enforcement=utils.positional.WARN) @@ -58,8 +59,8 @@ class UserManager(base.CrudManager): will be used. """ if project: - LOG.warning("The project argument is deprecated, " - "use default_project instead.") + LOG.warning(_LW("The project argument is deprecated, " + "use default_project instead.")) default_project_id = base.getid(default_project) or base.getid(project) user_data = base.filter_none(name=name, domain_id=base.getid(domain), @@ -92,8 +93,8 @@ class UserManager(base.CrudManager): will be used. """ if project: - LOG.warning("The project argument is deprecated, " - "use default_project instead.") + LOG.warning(_LW("The project argument is deprecated, " + "use default_project instead.")) default_project_id = base.getid(default_project) or base.getid(project) if group: base_url = '/groups/%s' % base.getid(group) @@ -124,8 +125,8 @@ class UserManager(base.CrudManager): will be used. """ if project: - LOG.warning("The project argument is deprecated, " - "use default_project instead.") + LOG.warning(_LW("The project argument is deprecated, " + "use default_project instead.")) default_project_id = base.getid(default_project) or base.getid(project) user_data = base.filter_none(name=name, domain_id=base.getid(domain), @@ -145,11 +146,11 @@ class UserManager(base.CrudManager): def update_password(self, old_password, new_password): """Update the password for the user the token belongs to.""" if not (old_password and new_password): - msg = 'Specify both the current password and a new password' + msg = _('Specify both the current password and a new password') raise exceptions.ValidationError(msg) if old_password == new_password: - msg = 'Old password and new password must be different.' + msg = _('Old password and new password must be different.') raise exceptions.ValidationError(msg) params = {'user': {'password': new_password, diff --git a/openstack-common.conf b/openstack-common.conf index 586aac3..10f7b9b 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -3,10 +3,7 @@ # The list of modules to copy from oslo-incubator module=apiclient module=install_venv_common -module=jsonutils module=memorycache -module=strutils -module=timeutils # The base module to hold the copy of openstack.common base=keystoneclient diff --git a/requirements.txt b/requirements.txt index 5a08b75..e970640 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,10 @@ Babel>=1.3 iso8601>=0.1.9 netaddr>=0.7.12 oslo.config>=1.4.0 # Apache-2.0 +oslo.i18n>=1.0.0 # Apache-2.0 +oslo.serialization>=1.0.0 # Apache-2.0 +oslo.utils>=1.0.0 # Apache-2.0 PrettyTable>=0.7,<0.8 -requests>=1.2.1,!=2.4.0 +requests>=2.2.0,!=2.4.0 six>=1.7.0 -stevedore>=1.0.0 # Apache-2.0 +stevedore>=1.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index e0387f0..be0eba4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,10 +13,11 @@ mock>=1.0 mox3>=0.7.0 oauthlib>=0.6 oslosphinx>=2.2.0 # Apache-2.0 +oslotest>=1.2.0 # Apache-2.0 pycrypto>=2.6 -requests-mock>=0.4.0 # Apache-2.0 -sphinx>=1.1.2,!=1.2.0,<1.3 +requests-mock>=0.5.1 # Apache-2.0 +sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 testrepository>=0.0.18 testresources>=0.2.4 -testtools>=0.9.34 +testtools>=0.9.36 WebOb>=1.2.3 diff --git a/tools/debug_helper.sh b/tools/debug_helper.sh deleted file mode 100755 index b80b10c..0000000 --- a/tools/debug_helper.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -TMP_DIR=`mktemp -d` || exit 1 -trap "rm -rf $TMP_DIR" EXIT - -ALL_TESTS=$TMP_DIR/all_tests -TESTS_TO_RUN=$TMP_DIR/ksc_to_run - -python -m testtools.run discover -t ./ ./keystoneclient/tests --list > $ALL_TESTS - -if [ "$1" ] -then - grep "$1" < $ALL_TESTS > $TESTS_TO_RUN -else - mv $ALL_TESTS $TESTS_TO_RUN -fi - -python -m testtools.run discover --load-list $TESTS_TO_RUN @@ -28,9 +28,7 @@ commands = python setup.py testr --coverage --testr-args='{posargs}' downloadcache = ~/cache/pip [testenv:debug] - -commands = - {toxinidir}/tools/debug_helper.sh {posargs} +commands = oslo_debug_helper -t keystoneclient/tests {posargs} [flake8] # F821: undefined name @@ -47,3 +45,6 @@ exclude = .venv,.tox,dist,doc,*egg,build,*openstack/common* commands= python setup.py build_sphinx +[hacking] +import_exceptions = + keystoneclient.i18n |