diff options
57 files changed, 1063 insertions, 443 deletions
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index b7139ec..604d3ac 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,7 +1,7 @@ If you would like to contribute to the development of OpenStack, you must follow the steps documented at: - http://docs.openstack.org/infra/manual/developers.html + https://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the @@ -9,7 +9,7 @@ development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: - http://docs.openstack.org/infra/manual/developers.html#development-workflow + https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. diff --git a/HACKING.rst b/HACKING.rst index 0dfef99..1ab4a87 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -2,7 +2,7 @@ Keystone Style Commandments =========================== - Step 1: Read the OpenStack Style Commandments - http://docs.openstack.org/developer/hacking/ + https://docs.openstack.org/developer/hacking/ - Step 2: Read on Exceptions @@ -17,7 +17,7 @@ Testing python-keystoneclient uses testtools and testr for its unittest suite and its test runner. Basic workflow around our use of tox and testr can -be found at http://wiki.openstack.org/testr. If you'd like to learn more +be found at https://wiki.openstack.org/testr. If you'd like to learn more in depth: https://testtools.readthedocs.org/ @@ -2,8 +2,8 @@ Team and repository tags ======================== -.. image:: http://governance.openstack.org/badges/python-keystoneclient.svg - :target: http://governance.openstack.org/reference/tags/index.html +.. image:: https://governance.openstack.org/badges/python-keystoneclient.svg + :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on @@ -33,14 +33,14 @@ OpenStack's Identity Service. For command line interface support, use * `How to Contribute`_ .. _PyPi: https://pypi.python.org/pypi/python-keystoneclient -.. _Online Documentation: http://docs.openstack.org/developer/python-keystoneclient +.. _Online Documentation: https://docs.openstack.org/developer/python-keystoneclient .. _Launchpad project: https://launchpad.net/python-keystoneclient .. _Blueprints: https://blueprints.launchpad.net/python-keystoneclient .. _Bugs: https://bugs.launchpad.net/python-keystoneclient .. _Source: https://git.openstack.org/cgit/openstack/python-keystoneclient .. _OpenStackClient: https://pypi.python.org/pypi/python-openstackclient -.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html -.. _Specs: http://specs.openstack.org/openstack/keystone-specs/ +.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html +.. _Specs: https://specs.openstack.org/openstack/keystone-specs/ .. contents:: Contents: :local: @@ -1,5 +1,5 @@ # This is a cross-platform list tracking distribution packages needed by tests; -# see http://docs.openstack.org/infra/bindep/ for additional information. +# see https://docs.openstack.org/infra/bindep/ for additional information. gettext libssl-dev diff --git a/doc/source/conf.py b/doc/source/conf.py index 1d5cb10..cda0c77 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # python-keystoneclient documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # @@ -229,9 +227,9 @@ latex_documents = [ # If false, no module index is generated. #latex_use_modindex = True -keystoneauth_url = 'http://docs.openstack.org/developer/keystoneauth/' +keystoneauth_url = 'https://docs.openstack.org/developer/keystoneauth/' intersphinx_mapping = { - 'python': ('http://docs.python.org/', None), - 'osloconfig': ('http://docs.openstack.org/developer/oslo.config/', None), + 'python': ('https://docs.python.org/', None), + 'osloconfig': ('https://docs.openstack.org/developer/oslo.config/', None), 'keystoneauth1': (keystoneauth_url, None), } diff --git a/doc/source/index.rst b/doc/source/index.rst index c5f5260..74613cd 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -21,8 +21,8 @@ Related Identity Projects In addition to creating the Python client library, the Keystone team also provides `Identity Service`_, as well as `WSGI Middleware`_. -.. _`Identity Service`: http://docs.openstack.org/developer/keystone/ -.. _`WSGI Middleware`: http://docs.openstack.org/developer/keystonemiddleware/ +.. _`Identity Service`: https://docs.openstack.org/developer/keystone/ +.. _`WSGI Middleware`: https://docs.openstack.org/developer/keystonemiddleware/ Release Notes ============= @@ -41,7 +41,7 @@ using `Gerrit`_. .. _on GitHub: https://github.com/openstack/python-keystoneclient .. _Launchpad: https://launchpad.net/python-keystoneclient -.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow +.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox``. diff --git a/keystoneclient/auth/base.py b/keystoneclient/auth/base.py index df7521c..0036b8c 100644 --- a/keystoneclient/auth/base.py +++ b/keystoneclient/auth/base.py @@ -252,7 +252,7 @@ class BaseAuthPlugin(object): :returns: A list of Param objects describing available plugin parameters. - :rtype: list + :rtype: List """ return [] diff --git a/keystoneclient/auth/cli.py b/keystoneclient/auth/cli.py index d8cc820..1b8e054 100644 --- a/keystoneclient/auth/cli.py +++ b/keystoneclient/auth/cli.py @@ -32,7 +32,7 @@ def register_argparse_arguments(parser, argv, default=None): the options required for that specific plugin if available. :param argparse.ArgumentParser: the parser to attach argparse options to. - :param list argv: the arguments provided to the application. + :param List argv: the arguments provided to the application. :param str/class default: a default plugin name or a plugin object to use if one isn't specified by the CLI. default: None. diff --git a/keystoneclient/auth/identity/v3/base.py b/keystoneclient/auth/identity/v3/base.py index d82c289..3576045 100644 --- a/keystoneclient/auth/identity/v3/base.py +++ b/keystoneclient/auth/identity/v3/base.py @@ -33,7 +33,7 @@ class BaseAuth(base.BaseIdentityPlugin): """Identity V3 Authentication Plugin. :param string auth_url: Identity service endpoint for authentication. - :param list auth_methods: A collection of methods to authenticate with. + :param List auth_methods: A collection of methods to authenticate with. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. @@ -111,7 +111,7 @@ class Auth(BaseAuth): """Identity V3 Authentication Plugin. :param string auth_url: Identity service endpoint for authentication. - :param list auth_methods: A collection of methods to authenticate with. + :param List auth_methods: A collection of methods to authenticate with. :param string trust_id: Trust ID for trust scoping. :param string domain_id: Domain ID for domain scoping. :param string domain_name: Domain name for domain scoping. @@ -235,7 +235,7 @@ class AuthMethod(object): :param session: The communication session. :type session: keystoneclient.session.Session - :param Auth auth: The auth plugin calling the method. + :param base.Auth auth: The auth plugin calling the method. :param dict headers: The headers that will be sent with the auth request if a plugin needs to add to them. :return: The identifier of this plugin and a dict of authentication diff --git a/keystoneclient/base.py b/keystoneclient/base.py index 4e393c6..dc449f7 100644 --- a/keystoneclient/base.py +++ b/keystoneclient/base.py @@ -353,7 +353,10 @@ class CrudManager(Manager): return self._head(self.build_url(dict_args_in_out=kwargs)) def _build_query(self, params): - return '?%s' % urllib.parse.urlencode(params) if params else '' + if params is None: + return '' + else: + return '?%s' % urllib.parse.urlencode(params, doseq=True) def build_key_only_query(self, params_list): """Build a query that does not include values, just keys. diff --git a/keystoneclient/contrib/ec2/utils.py b/keystoneclient/contrib/ec2/utils.py index f7fb8a1..dcd3ff5 100644 --- a/keystoneclient/contrib/ec2/utils.py +++ b/keystoneclient/contrib/ec2/utils.py @@ -225,7 +225,7 @@ class Ec2Signer(object): # port if we detect an old boto version. FIXME: remove when all # distros package boto >= 2.9.3, this is a transitional workaround user_agent = headers_lower.get('user-agent', '') - strip_port = re.match('Boto/2.[0-9].[0-2]', user_agent) + strip_port = re.match('Boto/2\.[0-9]\.[0-2]', user_agent) header_list = [] sh_str = auth_param('SignedHeaders') diff --git a/keystoneclient/discover.py b/keystoneclient/discover.py index 85b0875..a70b409 100644 --- a/keystoneclient/discover.py +++ b/keystoneclient/discover.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import logging import warnings from debtcollector import removals @@ -26,9 +25,6 @@ from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client -_logger = logging.getLogger(__name__) - - _CLIENT_VERSIONS = {2: v2_client.Client, 3: v3_client.Client} @@ -231,7 +227,7 @@ class Discover(_discover.Discover): :returns: The endpoints returned from the server that match the criteria. - :rtype: list + :rtype: List Example:: diff --git a/keystoneclient/generic/client.py b/keystoneclient/generic/client.py index aee7f67..e6b5833 100644 --- a/keystoneclient/generic/client.py +++ b/keystoneclient/generic/client.py @@ -15,6 +15,7 @@ import logging +from debtcollector import removals from six.moves.urllib import parse as urlparse from keystoneclient import exceptions @@ -25,6 +26,11 @@ from keystoneclient.i18n import _ _logger = logging.getLogger(__name__) +# NOTE(jamielennox): To be removed after Pike. +@removals.removed_class('keystoneclient.generic.client.Client', + message='Use keystoneauth discovery', + version='3.9.0', + removal_version='4.0.0') class Client(httpclient.HTTPClient): """Client for the OpenStack Keystone pre-version calls API. diff --git a/keystoneclient/i18n.py b/keystoneclient/i18n.py index 21879ab..fdbf183 100644 --- a/keystoneclient/i18n.py +++ b/keystoneclient/i18n.py @@ -14,7 +14,7 @@ """oslo.i18n integration module. -See http://docs.openstack.org/developer/oslo.i18n/usage.html . +See https://docs.openstack.org/developer/oslo.i18n/usage.html . """ diff --git a/keystoneclient/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml b/keystoneclient/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml new file mode 100644 index 0000000..61b9d17 --- /dev/null +++ b/keystoneclient/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml @@ -0,0 +1,6 @@ +--- +deprecations: + - Deprecate the `keystoneclient.generic` client. This client used to be able + to determine available API versions and some basics around installed + extensions however the APIs were never upgraded for the v3 API. It doesn't + seem to be used in the openstack ecosystem. diff --git a/keystoneclient/session.py b/keystoneclient/session.py index 1c3f30b..6d7faaa 100644 --- a/keystoneclient/session.py +++ b/keystoneclient/session.py @@ -37,6 +37,11 @@ osprofiler_web = importutils.try_import("osprofiler.web") USER_AGENT = 'python-keystoneclient' +# NOTE(jamielennox): Clients will likely want to print more than json. Please +# propose a patch if you have a content type you think is reasonable to print +# here and we'll add it to the list as required. +_LOG_CONTENT_TYPES = set(['application/json']) + _logger = logging.getLogger(__name__) @@ -164,7 +169,7 @@ class Session(object): def _process_header(header): """Redact the secure headers to be logged.""" secure_headers = ('authorization', 'x-auth-token', - 'x-subject-token',) + 'x-subject-token', 'x-service-token') if header[0].lower() in secure_headers: token_hasher = hashlib.sha1() token_hasher.update(header[1].encode('utf-8')) @@ -201,6 +206,11 @@ class Session(object): % self._process_header(header)) if data: + if isinstance(data, six.binary_type): + try: + data = data.decode("ascii") + except UnicodeDecodeError: + data = "<binary_data>" string_parts.append("-d '%s'" % data) try: logger.debug(' '.join(string_parts)) @@ -216,7 +226,24 @@ class Session(object): if not logger.isEnabledFor(logging.DEBUG): return - text = _remove_service_catalog(response.text) + # NOTE(samueldmq): If the response does not provide enough info about + # the content type to decide whether it is useful and safe to log it + # or not, just do not log the body. Trying to# read the response body + # anyways may result on reading a long stream of bytes and getting an + # unexpected MemoryError. See bug 1616105 for further details. + content_type = response.headers.get('content-type', None) + + # NOTE(lamt): Per [1], the Content-Type header can be of the form + # Content-Type := type "/" subtype *[";" parameter] + # [1] https://www.w3.org/Protocols/rfc1341/4_Content-Type.html + for log_type in _LOG_CONTENT_TYPES: + if content_type is not None and content_type.startswith(log_type): + text = _remove_service_catalog(response.text) + break + else: + text = ('Omitted, Content-Type is set to %s. Only ' + '%s responses have their bodies logged.') + text = text % (content_type, ', '.join(_LOG_CONTENT_TYPES)) string_parts = [ 'RESP:', @@ -224,9 +251,7 @@ class Session(object): ] for header in six.iteritems(response.headers): string_parts.append('%s: %s' % self._process_header(header)) - if text: - string_parts.append('\nRESP BODY: %s\n' % - strutils.mask_password(text)) + string_parts.append('\nRESP BODY: %s\n' % strutils.mask_password(text)) logger.debug(' '.join(string_parts)) diff --git a/keystoneclient/tests/functional/v3/client_fixtures.py b/keystoneclient/tests/functional/v3/client_fixtures.py index 37da4a4..dd7209a 100644 --- a/keystoneclient/tests/functional/v3/client_fixtures.py +++ b/keystoneclient/tests/functional/v3/client_fixtures.py @@ -114,8 +114,8 @@ class InferenceRule(Base): self.ref = {'prior_role': self.prior_role, 'implied_role': self.implied_role} - self.entity = self.client.roles.create_implied(**self.ref) - self.addCleanup(self.client.roles.delete_implied, self.prior_role, + self.entity = self.client.inference_rules.create(**self.ref) + self.addCleanup(self.client.inference_rules.delete, self.prior_role, self.implied_role) @@ -178,6 +178,18 @@ class Endpoint(Base): self.addCleanup(self.client.endpoints.delete, self.entity) +class EndpointGroup(Base): + + def setUp(self): + super(EndpointGroup, self).setUp() + + self.ref = {'name': RESOURCE_NAME_PREFIX + uuid.uuid4().hex, + 'filters': {'interface': 'public'}, + 'description': uuid.uuid4().hex} + self.entity = self.client.endpoint_groups.create(**self.ref) + self.addCleanup(self.client.endpoint_groups.delete, self.entity) + + class Credential(Base): def __init__(self, client, user, type, project=None): diff --git a/keystoneclient/tests/functional/v3/test_credentials.py b/keystoneclient/tests/functional/v3/test_credentials.py index d428f10..a5d00b1 100644 --- a/keystoneclient/tests/functional/v3/test_credentials.py +++ b/keystoneclient/tests/functional/v3/test_credentials.py @@ -20,6 +20,11 @@ from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class CredentialsTestCase(base.V3ClientTestCase): + def setUp(self): + super(CredentialsTestCase, self).setUp() + self.test_domain = fixtures.Domain(self.client) + self.useFixture(self.test_domain) + def check_credential(self, credential, credential_ref=None): self.assertIsNotNone(credential.id) self.assertIn('self', credential.links) @@ -46,7 +51,7 @@ class CredentialsTestCase(base.V3ClientTestCase): self.assertIsNotNone(credential.project_id) def test_create_credential_of_cert_type(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, @@ -58,7 +63,7 @@ class CredentialsTestCase(base.V3ClientTestCase): self.check_credential(credential, credential_ref) def test_create_credential_of_ec2_type(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) # project is mandatory attribute if the credential type is ec2 @@ -70,7 +75,7 @@ class CredentialsTestCase(base.V3ClientTestCase): self.client.credentials.create, **credential_ref) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) credential_ref = {'user': user.id, @@ -84,7 +89,7 @@ class CredentialsTestCase(base.V3ClientTestCase): self.check_credential(credential, credential_ref) def test_create_credential_of_totp_type(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) credential_ref = {'user': user.id, @@ -96,9 +101,9 @@ class CredentialsTestCase(base.V3ClientTestCase): self.check_credential(credential, credential_ref) def test_get_credential(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: @@ -111,14 +116,14 @@ class CredentialsTestCase(base.V3ClientTestCase): self.check_credential(credential_ret, credential.ref) def test_list_credentials(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) cert_credential = fixtures.Credential(self.client, user=user.id, type='cert') self.useFixture(cert_credential) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) ec2_credential = fixtures.Credential(self.client, user=user.id, type='ec2', project=project.id) @@ -139,12 +144,12 @@ class CredentialsTestCase(base.V3ClientTestCase): self.assertIn(totp_credential.entity, credentials) def test_update_credential(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) - new_user = fixtures.User(self.client, self.project_domain_id) + new_user = fixtures.User(self.client, self.test_domain.id) self.useFixture(new_user) - new_project = fixtures.Project(self.client, self.project_domain_id) + new_project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(new_project) credential = fixtures.Credential(self.client, user=user.id, @@ -166,9 +171,9 @@ class CredentialsTestCase(base.V3ClientTestCase): self.check_credential(credential_ret, credential.ref) def test_delete_credential(self): - user = fixtures.User(self.client, self.project_domain_id) + user = fixtures.User(self.client, self.test_domain.id) self.useFixture(user) - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) for credential_type in ['cert', 'ec2', 'totp']: diff --git a/keystoneclient/tests/functional/v3/test_endpoint_groups.py b/keystoneclient/tests/functional/v3/test_endpoint_groups.py new file mode 100644 index 0000000..10eccfb --- /dev/null +++ b/keystoneclient/tests/functional/v3/test_endpoint_groups.py @@ -0,0 +1,120 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from keystoneauth1.exceptions import http + +from keystoneclient.tests.functional import base +from keystoneclient.tests.functional.v3 import client_fixtures as fixtures + + +class EndpointGroupsTestCase(base.V3ClientTestCase): + + def check_endpoint_group(self, endpoint_group, endpoint_group_ref=None): + self.assertIsNotNone(endpoint_group.id) + self.assertIn('self', endpoint_group.links) + self.assertIn('/endpoint_groups/' + endpoint_group.id, + endpoint_group.links['self']) + + if endpoint_group_ref: + self.assertEqual(endpoint_group_ref['name'], endpoint_group.name) + self.assertEqual(endpoint_group_ref['filters'], + endpoint_group.filters) + + # There is no guarantee description is present in endpoint groups + if hasattr(endpoint_group_ref, 'description'): + self.assertEqual(endpoint_group_ref['description'], + endpoint_group.description) + else: + # Only check remaining mandatory attributes + self.assertIsNotNone(endpoint_group.name) + self.assertIsNotNone(endpoint_group.filters) + + def test_create_endpoint_group(self): + endpoint_group_ref = { + 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, + 'filters': {'interface': 'internal'}, + 'description': uuid.uuid4().hex} + endpoint_group = self.client.endpoint_groups.create( + **endpoint_group_ref) + + self.addCleanup(self.client.endpoint_groups.delete, endpoint_group) + self.check_endpoint_group(endpoint_group, endpoint_group_ref) + + def test_get_endpoint_group(self): + endpoint_group = fixtures.EndpointGroup(self.client) + self.useFixture(endpoint_group) + + endpoint_ret = self.client.endpoint_groups.get(endpoint_group.id) + self.check_endpoint_group(endpoint_ret, endpoint_group.ref) + + self.assertRaises(http.NotFound, + self.client.endpoint_groups.get, + uuid.uuid4().hex) + + def test_check_endpoint_group(self): + endpoint_group = fixtures.EndpointGroup(self.client) + self.useFixture(endpoint_group) + + self.client.endpoint_groups.check(endpoint_group.id) + self.assertRaises(http.NotFound, + self.client.endpoint_groups.check, + uuid.uuid4().hex) + + def test_list_endpoint_groups(self): + endpoint_group_one = fixtures.EndpointGroup(self.client) + self.useFixture(endpoint_group_one) + + endpoint_group_two = fixtures.EndpointGroup(self.client) + self.useFixture(endpoint_group_two) + + endpoint_groups = self.client.endpoint_groups.list() + + # All endpoints are valid + for endpoint_group in endpoint_groups: + self.check_endpoint_group(endpoint_group) + + self.assertIn(endpoint_group_one.entity, endpoint_groups) + self.assertIn(endpoint_group_two.entity, endpoint_groups) + + def test_update_endpoint_group(self): + endpoint_group = fixtures.EndpointGroup(self.client) + self.useFixture(endpoint_group) + + new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex + new_filters = {'interface': 'public'} + new_description = uuid.uuid4().hex + + endpoint_group_ret = self.client.endpoint_groups.update( + endpoint_group, + name=new_name, + filters=new_filters, + description=new_description) + + endpoint_group.ref.update({'name': new_name, 'filters': new_filters, + 'description': new_description}) + self.check_endpoint_group(endpoint_group_ret, endpoint_group.ref) + + def test_delete_endpoint_group(self): + endpoint_group = self.client.endpoint_groups.create( + name=fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, + filters={'interface': 'admin'}, + description=uuid.uuid4().hex) + + self.client.endpoint_groups.delete(endpoint_group.id) + self.assertRaises(http.NotFound, + self.client.endpoint_groups.check, + endpoint_group.id) + self.assertRaises(http.NotFound, + self.client.endpoint_groups.get, + endpoint_group.id) diff --git a/keystoneclient/tests/functional/v3/test_implied_roles.py b/keystoneclient/tests/functional/v3/test_implied_roles.py index b2f743c..0d5dbc5 100644 --- a/keystoneclient/tests/functional/v3/test_implied_roles.py +++ b/keystoneclient/tests/functional/v3/test_implied_roles.py @@ -48,11 +48,12 @@ class TestImpliedRoles(base.V3ClientTestCase): super(TestImpliedRoles, self).setUp() def test_implied_roles(self): - initial_rule_count = len(self.client.roles.list_role_inferences()) + initial_rule_count = ( + len(self.client.inference_rules.list_inference_roles())) self.create_roles() self.create_rules() - rule_count = len(self.client.roles.list_role_inferences()) + rule_count = len(self.client.inference_rules.list_inference_roles()) self.assertEqual(initial_rule_count + len(inference_rules), rule_count) diff --git a/keystoneclient/tests/functional/v3/test_projects.py b/keystoneclient/tests/functional/v3/test_projects.py index d06aaa8..0fa631d 100644 --- a/keystoneclient/tests/functional/v3/test_projects.py +++ b/keystoneclient/tests/functional/v3/test_projects.py @@ -20,6 +20,14 @@ from keystoneclient.tests.functional.v3 import client_fixtures as fixtures class ProjectsTestCase(base.V3ClientTestCase): + def setUp(self): + super(ProjectsTestCase, self).setUp() + self.test_domain = fixtures.Domain(self.client) + self.useFixture(self.test_domain) + + self.test_project = fixtures.Project(self.client, self.test_domain.id) + self.useFixture(self.test_project) + def check_project(self, project, project_ref=None): self.assertIsNotNone(project.id) self.assertIn('self', project.links) @@ -46,10 +54,10 @@ class ProjectsTestCase(base.V3ClientTestCase): def test_create_subproject(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, - 'domain': self.project_domain_id, + 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex, - 'parent': self.project_id} + 'parent': self.test_project.id} project = self.client.projects.create(**project_ref) self.addCleanup(self.client.projects.delete, project) @@ -58,7 +66,7 @@ class ProjectsTestCase(base.V3ClientTestCase): def test_create_project(self): project_ref = { 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, - 'domain': self.project_domain_id, + 'domain': self.test_domain.id, 'enabled': True, 'description': uuid.uuid4().hex} @@ -67,31 +75,25 @@ class ProjectsTestCase(base.V3ClientTestCase): self.check_project(project, project_ref) def test_get_project(self): - project = fixtures.Project(self.client, self.project_domain_id) - self.useFixture(project) - - project_ret = self.client.projects.get(project.id) - self.check_project(project_ret, project.ref) + project_ret = self.client.projects.get(self.test_project.id) + self.check_project(project_ret, self.test_project.ref) def test_get_project_invalid_params(self): self.assertRaises(exceptions.ValidationError, self.client.projects.get, - self.project_id, + self.test_project.id, subtree_as_list=True, subtree_as_ids=True) self.assertRaises(exceptions.ValidationError, self.client.projects.get, - self.project_id, + self.test_project.id, parents_as_list=True, parents_as_ids=True) def test_get_hierarchy_as_list(self): - parent_project = fixtures.Project(self.client, self.project_domain_id) - self.useFixture(parent_project) - - project = fixtures.Project(self.client, self.project_domain_id, - parent=parent_project.id) + project = fixtures.Project(self.client, self.test_domain.id, + parent=self.test_project.id) self.useFixture(project) - child_project = fixtures.Project(self.client, self.project_domain_id, + child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) @@ -101,8 +103,9 @@ class ProjectsTestCase(base.V3ClientTestCase): role = fixtures.Role(self.client) self.useFixture(role) self.client.roles.grant(role.id, user=self.user_id, - project=parent_project.id) - self.client.roles.grant(role.id, user=self.user_id, project=project.id) + project=self.test_project.id) + self.client.roles.grant(role.id, user=self.user_id, + project=project.id) self.client.roles.grant(role.id, user=self.user_id, project=child_project.id) @@ -111,20 +114,19 @@ class ProjectsTestCase(base.V3ClientTestCase): parents_as_list=True) self.check_project(project_ret, project.ref) - self.assertItemsEqual([{'project': parent_project.entity.to_dict()}], - project_ret.parents) - self.assertItemsEqual([{'project': child_project.entity.to_dict()}], - project_ret.subtree) + self.assertItemsEqual( + [{'project': self.test_project.entity.to_dict()}], + project_ret.parents) + self.assertItemsEqual( + [{'project': child_project.entity.to_dict()}], + project_ret.subtree) def test_get_hierarchy_as_ids(self): - parent_project = fixtures.Project(self.client, self.project_domain_id) - self.useFixture(parent_project) - - project = fixtures.Project(self.client, self.project_domain_id, - parent=parent_project.id) + project = fixtures.Project(self.client, self.test_domain.id, + parent=self.test_project.id) self.useFixture(project) - child_project = fixtures.Project(self.client, self.project_domain_id, + child_project = fixtures.Project(self.client, self.test_domain.id, parent=project.id) self.useFixture(child_project) @@ -132,14 +134,14 @@ class ProjectsTestCase(base.V3ClientTestCase): subtree_as_ids=True, parents_as_ids=True) - self.assertItemsEqual([parent_project.id], project_ret.parents) + self.assertItemsEqual([self.test_project.id], project_ret.parents) self.assertItemsEqual([child_project.id], project_ret.subtree) def test_list_projects(self): - project_one = fixtures.Project(self.client, self.project_domain_id) + project_one = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_one) - project_two = fixtures.Project(self.client, self.project_domain_id) + project_two = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project_two) projects = self.client.projects.list() @@ -152,7 +154,7 @@ class ProjectsTestCase(base.V3ClientTestCase): self.assertIn(project_two.entity, projects) def test_update_project(self): - project = fixtures.Project(self.client, self.project_domain_id) + project = fixtures.Project(self.client, self.test_domain.id) self.useFixture(project) new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex @@ -167,19 +169,16 @@ class ProjectsTestCase(base.V3ClientTestCase): self.check_project(project_ret, project.ref) def test_update_project_domain_not_allowed(self): - project = fixtures.Project(self.client) - self.useFixture(project) - domain = fixtures.Domain(self.client) self.useFixture(domain) # Cannot update domain after project is created. self.assertRaises(http.BadRequest, self.client.projects.update, - project.id, domain=domain.id) + self.test_project.id, domain=domain.id) def test_delete_project(self): project = self.client.projects.create(name=uuid.uuid4().hex, - domain=self.project_domain_id, + domain=self.test_domain.id, enabled=True) self.client.projects.delete(project.id) diff --git a/keystoneclient/tests/functional/v3/test_users.py b/keystoneclient/tests/functional/v3/test_users.py index b39c7f9..780ddba 100644 --- a/keystoneclient/tests/functional/v3/test_users.py +++ b/keystoneclient/tests/functional/v3/test_users.py @@ -76,6 +76,29 @@ class UsersTestCase(base.V3ClientTestCase): self.assertIn(user_one.entity, users) self.assertIn(user_two.entity, users) + def test_list_users_with_filters(self): + suffix = uuid.uuid4().hex + user1_ref = { + 'name': 'test_user' + suffix, + 'domain': self.project_domain_id, + 'default_project': self.project_id, + 'password': uuid.uuid4().hex, + 'description': uuid.uuid4().hex} + + user2_ref = { + 'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex, + 'domain': self.project_domain_id, + 'default_project': self.project_id, + 'password': uuid.uuid4().hex, + 'description': uuid.uuid4().hex} + + user1 = self.client.users.create(**user1_ref) + self.client.users.create(**user2_ref) + + users = self.client.users.list(name__contains=['test_user', suffix]) + self.assertEqual(1, len(users)) + self.assertIn(user1, users) + def test_update_user(self): user = fixtures.User(self.client, self.project_domain_id) self.useFixture(user) diff --git a/keystoneclient/tests/hacking/__init__.py b/keystoneclient/tests/hacking/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/keystoneclient/tests/hacking/__init__.py +++ /dev/null diff --git a/keystoneclient/tests/hacking/checks.py b/keystoneclient/tests/hacking/checks.py deleted file mode 100644 index 4282698..0000000 --- a/keystoneclient/tests/hacking/checks.py +++ /dev/null @@ -1,37 +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. - -"""python-keystoneclient's pep8 extensions. - -In order to make the review process faster and easier for core devs we are -adding some python-keystoneclient specific pep8 checks. This will catch common -errors so that core devs don't have to. - -""" - - -import re - - -def check_oslo_namespace_imports(logical_line, blank_before, filename): - oslo_namespace_imports = re.compile( - r"(((from)|(import))\s+oslo\.)|(from\s+oslo\s+import\s+)") - - if re.match(oslo_namespace_imports, logical_line): - msg = ("K333: '%s' must be used instead of '%s'.") % ( - logical_line.replace('oslo.', 'oslo_'), - logical_line) - yield(0, msg) - - -def factory(register): - register(check_oslo_namespace_imports) diff --git a/keystoneclient/tests/unit/apiclient/test_exceptions.py b/keystoneclient/tests/unit/apiclient/test_exceptions.py index 4a803c7..ddf8d98 100644 --- a/keystoneclient/tests/unit/apiclient/test_exceptions.py +++ b/keystoneclient/tests/unit/apiclient/test_exceptions.py @@ -40,7 +40,7 @@ class ExceptionsArgsTest(utils.TestCase): method, url) self.assertIsInstance(ex, ex_cls) - self.assertEqual(ex.message, json_data["error"]["message"]) + self.assertIn(json_data["error"]["message"], ex.message) self.assertEqual(ex.details, json_data["error"]["details"]) self.assertEqual(ex.method, method) self.assertEqual(ex.url, url) diff --git a/keystoneclient/tests/unit/client_fixtures.py b/keystoneclient/tests/unit/client_fixtures.py index b03f428..0e00545 100644 --- a/keystoneclient/tests/unit/client_fixtures.py +++ b/keystoneclient/tests/unit/client_fixtures.py @@ -709,67 +709,6 @@ class Examples(fixtures.Fixture): EXAMPLES_RESOURCE = testresources.FixtureResource(Examples()) -class HackingCode(fixtures.Fixture): - """A fixture to house the various code examples. - - Examples contains various keystoneclient hacking style checks. - """ - - oslo_namespace_imports = { - 'code': """ - import oslo.utils - import oslo_utils - import oslo.utils.encodeutils - import oslo_utils.encodeutils - from oslo import utils - from oslo.utils import encodeutils - from oslo_utils import encodeutils - - import oslo.serialization - import oslo_serialization - import oslo.serialization.jsonutils - import oslo_serialization.jsonutils - from oslo import serialization - from oslo.serialization import jsonutils - from oslo_serialization import jsonutils - - import oslo.config - import oslo_config - import oslo.config.cfg - import oslo_config.cfg - from oslo import config - from oslo.config import cfg - from oslo_config import cfg - - import oslo.i18n - import oslo_i18n - import oslo.i18n.log - import oslo_i18n.log - from oslo import i18n - from oslo.i18n import log - from oslo_i18n import log - """, - 'expected_errors': [ - (1, 0, 'K333'), - (3, 0, 'K333'), - (5, 0, 'K333'), - (6, 0, 'K333'), - (9, 0, 'K333'), - (11, 0, 'K333'), - (13, 0, 'K333'), - (14, 0, 'K333'), - (17, 0, 'K333'), - (19, 0, 'K333'), - (21, 0, 'K333'), - (22, 0, 'K333'), - (25, 0, 'K333'), - (27, 0, 'K333'), - (29, 0, 'K333'), - (30, 0, 'K333'), - ], - } - - class Deprecations(fixtures.Fixture): def setUp(self): super(Deprecations, self).setUp() diff --git a/keystoneclient/tests/unit/generic/test_client.py b/keystoneclient/tests/unit/generic/test_client.py index a3690fb..5c27b6e 100644 --- a/keystoneclient/tests/unit/generic/test_client.py +++ b/keystoneclient/tests/unit/generic/test_client.py @@ -22,7 +22,8 @@ BASE_HOST = 'http://keystone.example.com' BASE_URL = "%s:5000/" % BASE_HOST V2_URL = "%sv2.0" % BASE_URL -EXTENSION_NAMESPACE = "http://docs.openstack.org/identity/api/ext/OS-FAKE/v1.0" +EXTENSION_NAMESPACE = ("https://docs.openstack.org/identity/api/ext/OS-FAKE/" + "v1.0") EXTENSION_DESCRIBED = {"href": "https://github.com/openstack/identity-api", "rel": "describedby", "type": "text/html"} @@ -58,6 +59,7 @@ class ClientDiscoveryTests(utils.TestCase): def test_discover_extensions_v2(self): self.requests_mock.get("%s/extensions" % V2_URL, text=EXTENSION_LIST) # Creating a HTTPClient not using session is deprecated. + # creating a generic client at all is deprecated. with self.deprecations.expect_deprecations_here(): extensions = client.Client().discover_extensions(url=V2_URL) self.assertIn(EXTENSION_ALIAS_FOO, extensions) diff --git a/keystoneclient/tests/unit/test_base.py b/keystoneclient/tests/unit/test_base.py index f6ca651..0a0fde1 100644 --- a/keystoneclient/tests/unit/test_base.py +++ b/keystoneclient/tests/unit/test_base.py @@ -11,9 +11,9 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures from keystoneauth1.identity import v2 from keystoneauth1 import session -from oslotest import mockpatch from keystoneclient import base from keystoneclient.tests.unit import utils @@ -44,7 +44,7 @@ class BaseTest(utils.TestCase): session_ = session.Session(auth=auth) self.client = client.Client(session=session_) - self.useFixture(mockpatch.PatchObject( + self.useFixture(fixtures.MockPatchObject( self.client._adapter, 'get', side_effect=AttributeError, autospec=True)) @@ -127,7 +127,7 @@ class ManagerTest(utils.TestCase): self.assertEqual(self.mgr.api, self.client) def test_get(self): - get_mock = self.useFixture(mockpatch.PatchObject( + get_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'get', autospec=True, return_value=(None, self.body)) ).mock rsrc = self.mgr._get(self.url, "hello") @@ -135,7 +135,7 @@ class ManagerTest(utils.TestCase): self.assertEqual(rsrc.hi, 1) def test_post(self): - post_mock = self.useFixture(mockpatch.PatchObject( + post_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'post', autospec=True, return_value=(None, self.body)) ).mock @@ -150,7 +150,7 @@ class ManagerTest(utils.TestCase): self.assertEqual(rsrc["hi"], 1) def test_put(self): - put_mock = self.useFixture(mockpatch.PatchObject( + put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock @@ -165,7 +165,7 @@ class ManagerTest(utils.TestCase): self.assertEqual(rsrc.hello["hi"], 1) def test_patch(self): - patch_mock = self.useFixture(mockpatch.PatchObject( + patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock @@ -181,12 +181,12 @@ class ManagerTest(utils.TestCase): self.assertEqual(rsrc.hello["hi"], 1) def test_update(self): - patch_mock = self.useFixture(mockpatch.PatchObject( + patch_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'patch', autospec=True, return_value=(None, self.body)) ).mock - put_mock = self.useFixture(mockpatch.PatchObject( + put_mock = self.useFixture(fixtures.MockPatchObject( self.client, 'put', autospec=True, return_value=(None, self.body)) ).mock diff --git a/keystoneclient/tests/unit/test_ec2utils.py b/keystoneclient/tests/unit/test_ec2utils.py index 5102f6b..1d8809b 100644 --- a/keystoneclient/tests/unit/test_ec2utils.py +++ b/keystoneclient/tests/unit/test_ec2utils.py @@ -265,3 +265,42 @@ class Ec2SignerTest(testtools.TestCase): expected = ('26dd92ea79aaa49f533d13b1055acdc' 'd7d7321460d64621f96cc79c4f4d4ab2b') self.assertEqual(expected, signature) + + def test_generate_v4_port_malformed_version(self): + """Test v4 generator with host:port format for malformed boto version. + + Validate for malformed version of boto, where the port should + not be stripped. + """ + # Create a new signer object with the AWS example key + secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' + signer = utils.Ec2Signer(secret) + + body_hash = ('b6359072c78d70ebee1e81adcbab4f0' + '1bf2c23245fa365ef83fe8f1f955085e2') + auth_str = ('AWS4-HMAC-SHA256 ' + 'Credential=AKIAIOSFODNN7EXAMPLE/20110909/' + 'us-east-1/iam/aws4_request,' + 'SignedHeaders=content-type;host;x-amz-date,') + headers = {'Content-type': + 'application/x-www-form-urlencoded; charset=utf-8', + 'X-Amz-Date': '20110909T233600Z', + 'Host': 'foo:8000', + 'Authorization': auth_str, + 'User-Agent': 'Boto/2.922 (linux2)'} + # Note the example in the AWS docs is inconsistent, previous + # examples specify no query string, but the final POST example + # does, apparently incorrectly since an empty parameter list + # aligns all steps and the final signature with the examples + params = {} + credentials = {'host': 'foo:8000', + 'verb': 'POST', + 'path': '/', + 'params': params, + 'headers': headers, + 'body_hash': body_hash} + signature = signer.generate(credentials) + + expected = ('26dd92ea79aaa49f533d13b1055acdc' + 'd7d7321460d64621f96cc79c4f4d4ab2b') + self.assertEqual(expected, signature) diff --git a/keystoneclient/tests/unit/test_hacking_checks.py b/keystoneclient/tests/unit/test_hacking_checks.py deleted file mode 100644 index f1e81c9..0000000 --- a/keystoneclient/tests/unit/test_hacking_checks.py +++ /dev/null @@ -1,50 +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 textwrap - -import mock -import pep8 -import testtools - -from keystoneclient.tests.hacking import checks -from keystoneclient.tests.unit import client_fixtures - - -class TestCheckOsloNamespaceImports(testtools.TestCase): - def setUp(self): - super(TestCheckOsloNamespaceImports, self).setUp() - self.useFixture(client_fixtures.Deprecations()) - - # We are patching pep8 so that only the check under test is actually - # installed. - @mock.patch('pep8._checks', - {'physical_line': {}, 'logical_line': {}, 'tree': {}}) - def run_check(self, code): - pep8.register_check(checks.check_oslo_namespace_imports) - - lines = textwrap.dedent(code).strip().splitlines(True) - - checker = pep8.Checker(lines=lines) - checker.check_all() - checker.report._deferred_print.sort() - return checker.report._deferred_print - - def assert_has_errors(self, code, expected_errors=None): - actual_errors = [e[:3] for e in self.run_check(code)] - self.assertEqual(expected_errors or [], actual_errors) - - def test(self): - code_ex = self.useFixture(client_fixtures.HackingCode()) - code = code_ex.oslo_namespace_imports['code'] - errors = code_ex.oslo_namespace_imports['expected_errors'] - self.assert_has_errors(code, expected_errors=errors) diff --git a/keystoneclient/tests/unit/test_http.py b/keystoneclient/tests/unit/test_http.py index 56f116c..0282f1a 100644 --- a/keystoneclient/tests/unit/test_http.py +++ b/keystoneclient/tests/unit/test_http.py @@ -97,7 +97,7 @@ class ClientTest(utils.TestCase): cl.get('/hi') except exceptions.BadRequest as exc: exc_raised = True - self.assertEqual(exc.message, "Error message string") + self.assertEqual(exc.message, "Error message string (HTTP 400)") self.assertTrue(exc_raised, 'Exception not raised.') def test_post(self): @@ -166,22 +166,24 @@ class BasicRequestTests(utils.TestCase): self.addCleanup(self.logger.setLevel, level) def request(self, method='GET', response='Test Response', status_code=200, - url=None, **kwargs): + url=None, headers={}, **kwargs): if not url: url = self.url self.requests_mock.register_uri(method, url, text=response, - status_code=status_code) + status_code=status_code, + headers=headers) with self.deprecations.expect_deprecations_here(): - return httpclient.request(url, method, **kwargs) + return httpclient.request(url, method, headers=headers, **kwargs) def test_basic_params(self): method = 'GET' response = 'Test Response' status = 200 - self.request(method=method, status_code=status, response=response) + self.request(method=method, status_code=status, response=response, + headers={'Content-Type': 'application/json'}) self.assertEqual(self.requests_mock.last_request.method, method) @@ -209,7 +211,8 @@ class BasicRequestTests(utils.TestCase): def test_body(self): data = "BODY DATA" - self.request(response=data) + self.request(response=data, + headers={'Content-Type': 'application/json'}) logger_message = self.logger_message.getvalue() self.assertThat(logger_message, matchers.Contains('BODY:')) self.assertThat(logger_message, matchers.Contains(data)) diff --git a/keystoneclient/tests/unit/test_session.py b/keystoneclient/tests/unit/test_session.py index 8fb364a..7a3c57d 100644 --- a/keystoneclient/tests/unit/test_session.py +++ b/keystoneclient/tests/unit/test_session.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # 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 @@ -149,12 +151,14 @@ class SessionTests(utils.TestCase): in order to redact secure headers while debug is true. """ session = client_session.Session(verify=False) - headers = {'HEADERA': 'HEADERVALB'} + headers = {'HEADERA': 'HEADERVALB', + 'Content-Type': 'application/json'} security_headers = {'Authorization': uuid.uuid4().hex, 'X-Auth-Token': uuid.uuid4().hex, - 'X-Subject-Token': uuid.uuid4().hex, } - body = 'BODYRESPONSE' - data = 'BODYDATA' + 'X-Subject-Token': uuid.uuid4().hex, + 'X-Service-Token': uuid.uuid4().hex} + body = '{"a": "b"}' + data = '{"c": "d"}' all_headers = dict( itertools.chain(headers.items(), security_headers.items())) self.stub_url('POST', text=body, headers=all_headers) @@ -181,20 +185,65 @@ class SessionTests(utils.TestCase): def test_logs_failed_output(self): """Test that output is logged even for failed requests.""" session = client_session.Session() - body = uuid.uuid4().hex + body = {uuid.uuid4().hex: uuid.uuid4().hex} - self.stub_url('GET', text=body, status_code=400) + self.stub_url('GET', json=body, status_code=400, + headers={'Content-Type': 'application/json'}) resp = session.get(self.TEST_URL, raise_exc=False) self.assertEqual(resp.status_code, 400) + self.assertIn(list(body.keys())[0], self.logger.output) + self.assertIn(list(body.values())[0], self.logger.output) + + def test_logging_body_only_for_specified_content_types(self): + """Verify response body is only logged in specific content types. + + Response bodies are logged only when the response's Content-Type header + is set to application/json. This prevents us to get an unexpected + MemoryError when reading arbitrary responses, such as streams. + """ + OMITTED_BODY = ('Omitted, Content-Type is set to %s. Only ' + 'application/json responses have their bodies logged.') + session = client_session.Session(verify=False) + + # Content-Type is not set + body = jsonutils.dumps({'token': {'id': '...'}}) + self.stub_url('POST', text=body) + session.post(self.TEST_URL) + self.assertNotIn(body, self.logger.output) + self.assertIn(OMITTED_BODY % None, self.logger.output) + + # Content-Type is set to text/xml + body = '<token><id>...</id></token>' + self.stub_url('POST', text=body, headers={'Content-Type': 'text/xml'}) + session.post(self.TEST_URL) + self.assertNotIn(body, self.logger.output) + self.assertIn(OMITTED_BODY % 'text/xml', self.logger.output) + + # Content-Type is set to application/json + body = jsonutils.dumps({'token': {'id': '...'}}) + self.stub_url('POST', text=body, + headers={'Content-Type': 'application/json'}) + session.post(self.TEST_URL) + self.assertIn(body, self.logger.output) + self.assertNotIn(OMITTED_BODY % 'application/json', self.logger.output) + + # Content-Type is set to application/json; charset=UTF-8 + body = jsonutils.dumps({'token': {'id': '...'}}) + self.stub_url( + 'POST', text=body, + headers={'Content-Type': 'application/json; charset=UTF-8'}) + session.post(self.TEST_URL) self.assertIn(body, self.logger.output) + self.assertNotIn(OMITTED_BODY % 'application/json; charset=UTF-8', + self.logger.output) def test_unicode_data_in_debug_output(self): """Verify that ascii-encodable data is logged without modification.""" session = client_session.Session(verify=False) body = 'RESP' - data = u'unicode_data' + data = u'αβγδ' self.stub_url('POST', text=body) session.post(self.TEST_URL, data=data) @@ -219,12 +268,7 @@ class SessionTests(utils.TestCase): # raise a UnicodeDecodeError) session.post(unicode(self.TEST_URL), data=data) - self.assertIn("Replaced characters that could not be decoded" - " in log output", self.logger.output) - - # Our data payload should have changed to - # include the replacement char - self.assertIn(u"-d 'my data\ufffd'", self.logger.output) + self.assertNotIn('my data', self.logger.output) def test_logging_cacerts(self): path_to_certs = '/path/to/certs' @@ -315,7 +359,8 @@ class SessionTests(utils.TestCase): "auth_username": "verybadusername", "auth_method": "CHAP"}}} body_json = jsonutils.dumps(body) - response = mock.Mock(text=body_json, status_code=200, headers={}) + response = mock.Mock(text=body_json, status_code=200, + headers={'content-type': 'application/json'}) session._http_log_response(response, logger) self.assertEqual(1, logger.debug.call_count) @@ -768,22 +813,24 @@ class SessionAuthTests(utils.TestCase): auth = AuthPlugin() sess = client_session.Session(auth=auth) - response = uuid.uuid4().hex + response = {uuid.uuid4().hex: uuid.uuid4().hex} self.stub_url('GET', - text=response, - headers={'Content-Type': 'text/html'}) + json=response, + headers={'Content-Type': 'application/json'}) resp = sess.get(self.TEST_URL, logger=logger) - self.assertEqual(response, resp.text) + self.assertEqual(response, resp.json()) output = io.getvalue() self.assertIn(self.TEST_URL, output) - self.assertIn(response, output) + self.assertIn(list(response.keys())[0], output) + self.assertIn(list(response.values())[0], output) self.assertNotIn(self.TEST_URL, self.logger.output) - self.assertNotIn(response, self.logger.output) + self.assertNotIn(list(response.keys())[0], self.logger.output) + self.assertNotIn(list(response.values())[0], self.logger.output) class AdapterTest(utils.TestCase): @@ -965,21 +1012,23 @@ class AdapterTest(utils.TestCase): sess = client_session.Session(auth=auth) adpt = adapter.Adapter(sess, auth=auth, logger=logger) - response = uuid.uuid4().hex + response = {uuid.uuid4().hex: uuid.uuid4().hex} - self.stub_url('GET', text=response, - headers={'Content-Type': 'text/html'}) + self.stub_url('GET', json=response, + headers={'Content-Type': 'application/json'}) resp = adpt.get(self.TEST_URL, logger=logger) - self.assertEqual(response, resp.text) + self.assertEqual(response, resp.json()) output = io.getvalue() self.assertIn(self.TEST_URL, output) - self.assertIn(response, output) + self.assertIn(list(response.keys())[0], output) + self.assertIn(list(response.values())[0], output) self.assertNotIn(self.TEST_URL, self.logger.output) - self.assertNotIn(response, self.logger.output) + self.assertNotIn(list(response.keys())[0], self.logger.output) + self.assertNotIn(list(response.values())[0], self.logger.output) class ConfLoadingTests(utils.TestCase): diff --git a/keystoneclient/tests/unit/v2_0/test_client.py b/keystoneclient/tests/unit/v2_0/test_client.py index 90b6450..fc9bf14 100644 --- a/keystoneclient/tests/unit/v2_0/test_client.py +++ b/keystoneclient/tests/unit/v2_0/test_client.py @@ -218,4 +218,4 @@ class KeystoneClientTest(utils.TestCase): # authenticated sess = auth_session.Session() cl = client.Client(session=sess) - self.assertEqual(None, cl.service_catalog) + self.assertIsNone(cl.service_catalog) diff --git a/keystoneclient/tests/unit/v2_0/test_discovery.py b/keystoneclient/tests/unit/v2_0/test_discovery.py index 5afe59a..a3700e0 100644 --- a/keystoneclient/tests/unit/v2_0/test_discovery.py +++ b/keystoneclient/tests/unit/v2_0/test_discovery.py @@ -29,11 +29,11 @@ class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/content/", }, {"rel": "describedby", "type": "application/pdf", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", }, {"rel": "describedby", diff --git a/keystoneclient/tests/unit/v2_0/test_extensions.py b/keystoneclient/tests/unit/v2_0/test_extensions.py index 662d380..3927bc0 100644 --- a/keystoneclient/tests/unit/v2_0/test_extensions.py +++ b/keystoneclient/tests/unit/v2_0/test_extensions.py @@ -22,7 +22,7 @@ class ExtensionTests(utils.ClientTestCase): "values": [ { 'name': 'OpenStack Keystone User CRUD', - 'namespace': 'http://docs.openstack.org/' + 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-KSCRUD/v1.0', 'updated': '2013-07-07T12:00:0-00:00', 'alias': 'OS-KSCRUD', @@ -36,7 +36,7 @@ class ExtensionTests(utils.ClientTestCase): }, { 'name': 'OpenStack EC2 API', - 'namespace': 'http://docs.openstack.org/' + 'namespace': 'https://docs.openstack.org/' 'identity/api/ext/OS-EC2/v1.0', 'updated': '2013-09-07T12:00:0-00:00', 'alias': 'OS-EC2', diff --git a/keystoneclient/tests/unit/v3/test_client.py b/keystoneclient/tests/unit/v3/test_client.py index 42004ff..29a2818 100644 --- a/keystoneclient/tests/unit/v3/test_client.py +++ b/keystoneclient/tests/unit/v3/test_client.py @@ -268,4 +268,4 @@ class KeystoneClientTest(utils.TestCase): # authenticated sess = auth_session.Session() cl = client.Client(session=sess) - self.assertEqual(None, cl.service_catalog) + self.assertIsNone(cl.service_catalog) diff --git a/keystoneclient/tests/unit/v3/test_discover.py b/keystoneclient/tests/unit/v3/test_discover.py index 898d46b..f54b2f9 100644 --- a/keystoneclient/tests/unit/v3/test_discover.py +++ b/keystoneclient/tests/unit/v3/test_discover.py @@ -27,12 +27,12 @@ class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): "href": "http://127.0.0.1:5000/v3.0/", }, {"rel": "describedby", "type": "text/html", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "content/", }, {"rel": "describedby", "type": "application/pdf", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/3/" "identity-dev-guide-3.pdf", }, ]}, @@ -44,12 +44,12 @@ class DiscoverKeystoneTests(utils.UnauthenticatedTestCase): "href": "http://127.0.0.1:5000/v2.0/", }, {"rel": "describedby", "type": "text/html", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "content/", }, {"rel": "describedby", "type": "application/pdf", - "href": "http://docs.openstack.org/api/" + "href": "https://docs.openstack.org/api/" "openstack-identity-service/2.0/" "identity-dev-guide-2.0.pdf", } ]}], diff --git a/keystoneclient/tests/unit/v3/test_endpoint_groups.py b/keystoneclient/tests/unit/v3/test_endpoint_groups.py new file mode 100644 index 0000000..364fd53 --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_endpoint_groups.py @@ -0,0 +1,34 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from keystoneclient.tests.unit.v3 import utils +from keystoneclient.v3 import endpoint_groups + + +class EndpointGroupTests(utils.ClientTestCase, utils.CrudTests): + + def setUp(self): + super(EndpointGroupTests, self).setUp() + self.key = 'endpoint_group' + self.collection_key = 'endpoint_groups' + self.model = endpoint_groups.EndpointGroup + self.manager = self.client.endpoint_groups + self.path_prefix = 'OS-EP-FILTER' + + def new_ref(self, **kwargs): + kwargs.setdefault('id', uuid.uuid4().hex) + kwargs.setdefault('name', uuid.uuid4().hex) + kwargs.setdefault('filters', '{"interface": "public"}') + kwargs.setdefault('description', uuid.uuid4().hex) + return kwargs diff --git a/keystoneclient/tests/unit/v3/test_roles.py b/keystoneclient/tests/unit/v3/test_roles.py index 7dfd7f2..51d9fc7 100644 --- a/keystoneclient/tests/unit/v3/test_roles.py +++ b/keystoneclient/tests/unit/v3/test_roles.py @@ -599,70 +599,226 @@ class RoleTests(utils.ClientTestCase, utils.CrudTests): group=group_id, user=user_id) - def test_implied_role_check(self): + +class DeprecatedImpliedRoleTests(utils.ClientTestCase): + def setUp(self): + super(DeprecatedImpliedRoleTests, self).setUp() + self.key = 'role' + self.collection_key = 'roles' + self.model = roles.Role + self.manager = self.client.roles + + def test_implied_create(self): + prior_id = uuid.uuid4().hex + prior_name = uuid.uuid4().hex + implied_id = uuid.uuid4().hex + implied_name = uuid.uuid4().hex + + mock_response = { + "role_inference": { + "implies": { + "id": implied_id, + "links": {"self": "http://host/v3/roles/%s" % implied_id}, + "name": implied_name + }, + "prior_role": { + "id": prior_id, + "links": {"self": "http://host/v3/roles/%s" % prior_id}, + "name": prior_name + } + } + } + + self.stub_url('PUT', + ['roles', prior_id, 'implies', implied_id], + json=mock_response, + status_code=201) + + with self.deprecations.expect_deprecations_here(): + manager_result = self.manager.create_implied(prior_id, implied_id) + self.assertIsInstance(manager_result, roles.InferenceRule) + self.assertEqual(mock_response['role_inference']['implies'], + manager_result.implies) + self.assertEqual(mock_response['role_inference']['prior_role'], + manager_result.prior_role) + + +class ImpliedRoleTests(utils.ClientTestCase, utils.CrudTests): + def setUp(self): + super(ImpliedRoleTests, self).setUp() + self.key = 'role_inference' + self.collection_key = 'role_inferences' + self.model = roles.InferenceRule + self.manager = self.client.inference_rules + + def test_check(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex self.stub_url('HEAD', ['roles', prior_role_id, 'implies', implied_role_id], status_code=200) - self.manager.check_implied(prior_role_id, implied_role_id) + result = self.manager.check(prior_role_id, implied_role_id) + self.assertTrue(result) + + def test_get(self): + prior_id = uuid.uuid4().hex + prior_name = uuid.uuid4().hex + implied_id = uuid.uuid4().hex + implied_name = uuid.uuid4().hex + + mock_response = { + "role_inference": { + "implies": { + "id": implied_id, + "links": {"self": "http://host/v3/roles/%s" % implied_id}, + "name": implied_name + }, + "prior_role": { + "id": prior_id, + "links": {"self": "http://host/v3/roles/%s" % prior_id}, + "name": prior_name + } + } + } - def test_implied_role_get(self): - prior_role_id = uuid.uuid4().hex - implied_role_id = uuid.uuid4().hex self.stub_url('GET', - ['roles', prior_role_id, 'implies', implied_role_id], - json={'role': {}}, - status_code=204) + ['roles', prior_id, 'implies', implied_id], + json=mock_response, + status_code=200) - self.manager.get_implied(prior_role_id, implied_role_id) + manager_result = self.manager.get(prior_id, implied_id) + self.assertIsInstance(manager_result, roles.InferenceRule) + self.assertEqual(mock_response['role_inference']['implies'], + manager_result.implies) + self.assertEqual(mock_response['role_inference']['prior_role'], + manager_result.prior_role) - def test_implied_role_create(self): - prior_role_id = uuid.uuid4().hex - implied_role_id = uuid.uuid4().hex - test_json = { + def test_create(self): + prior_id = uuid.uuid4().hex + prior_name = uuid.uuid4().hex + implied_id = uuid.uuid4().hex + implied_name = uuid.uuid4().hex + + mock_response = { "role_inference": { - "prior_role": { - "id": prior_role_id, - "links": {}, - "name": "prior role name" - }, "implies": { - "id": implied_role_id, - "links": {}, - "name": "implied role name" + "id": implied_id, + "links": {"self": "http://host/v3/roles/%s" % implied_id}, + "name": implied_name + }, + "prior_role": { + "id": prior_id, + "links": {"self": "http://host/v3/roles/%s" % prior_id}, + "name": prior_name } - }, - "links": {} + } } self.stub_url('PUT', - ['roles', prior_role_id, 'implies', implied_role_id], - json=test_json, - status_code=200) + ['roles', prior_id, 'implies', implied_id], + json=mock_response, + status_code=201) - returned_rule = self.manager.create_implied( - prior_role_id, implied_role_id) + manager_result = self.manager.create(prior_id, implied_id) - self.assertEqual(test_json['role_inference']['implies'], - returned_rule.implies) - self.assertEqual(test_json['role_inference']['prior_role'], - returned_rule.prior_role) + self.assertIsInstance(manager_result, roles.InferenceRule) + self.assertEqual(mock_response['role_inference']['implies'], + manager_result.implies) + self.assertEqual(mock_response['role_inference']['prior_role'], + manager_result.prior_role) - def test_implied_role_delete(self): + def test_delete(self): prior_role_id = uuid.uuid4().hex implied_role_id = uuid.uuid4().hex self.stub_url('DELETE', ['roles', prior_role_id, 'implies', implied_role_id], - status_code=200) + status_code=204) - self.manager.delete_implied(prior_role_id, implied_role_id) + status, body = self.manager.delete(prior_role_id, implied_role_id) + self.assertEqual(204, status.status_code) + self.assertIsNone(body) + + def test_list_role_inferences(self): + prior_id = uuid.uuid4().hex + prior_name = uuid.uuid4().hex + implied_id = uuid.uuid4().hex + implied_name = uuid.uuid4().hex + + mock_response = { + "role_inferences": [{ + "implies": [{ + "id": implied_id, + "links": {"self": "http://host/v3/roles/%s" % implied_id}, + "name": implied_name + }], + "prior_role": { + "id": prior_id, + "links": {"self": "http://host/v3/roles/%s" % prior_id}, + "name": prior_name + } + }] + } - def test_list_role_inferences(self, **kwargs): self.stub_url('GET', - ['role_inferences', ''], - json={'role_inferences': {}}, - status_code=204) + ['role_inferences'], + json=mock_response, + status_code=200) + manager_result = self.manager.list_inference_roles() + self.assertEqual(1, len(manager_result)) + self.assertIsInstance(manager_result[0], roles.InferenceRule) + self.assertEqual(mock_response['role_inferences'][0]['implies'], + manager_result[0].implies) + self.assertEqual(mock_response['role_inferences'][0]['prior_role'], + manager_result[0].prior_role) + + def test_list(self): + prior_id = uuid.uuid4().hex + prior_name = uuid.uuid4().hex + implied_id = uuid.uuid4().hex + implied_name = uuid.uuid4().hex + + mock_response = { + "role_inference": { + "implies": [{ + "id": implied_id, + "links": {"self": "http://host/v3/roles/%s" % implied_id}, + "name": implied_name + }], + "prior_role": { + "id": prior_id, + "links": {"self": "http://host/v3/roles/%s" % prior_id}, + "name": prior_name + } + }, + "links": {"self": "http://host/v3/roles/%s/implies" % prior_id} + } + + self.stub_url('GET', + ['roles', prior_id, 'implies'], + json=mock_response, + status_code=200) - self.manager.list_role_inferences() + manager_result = self.manager.list(prior_id) + self.assertIsInstance(manager_result, roles.InferenceRule) + self.assertEqual(1, len(manager_result.implies)) + self.assertEqual(mock_response['role_inference']['implies'], + manager_result.implies) + self.assertEqual(mock_response['role_inference']['prior_role'], + manager_result.prior_role) + + def test_update(self): + # Update not supported for rule inferences + self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) + + def test_find(self): + # Find not supported for rule inferences + self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) + + def test_put(self): + # Put not supported for rule inferences + self.assertRaises(exceptions.MethodNotImplemented, self.manager.put) + + def test_list_params(self): + # Put not supported for rule inferences + self.skipTest("list params not supported by rule inferences") diff --git a/keystoneclient/utils.py b/keystoneclient/utils.py index cf77bd4..8e8dd7c 100644 --- a/keystoneclient/utils.py +++ b/keystoneclient/utils.py @@ -12,7 +12,6 @@ import getpass import hashlib -import logging import sys from keystoneauth1 import exceptions as ksa_exceptions @@ -25,9 +24,6 @@ import six from keystoneclient import exceptions as ksc_exceptions -logger = logging.getLogger(__name__) - - def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" # first try the entity as a string diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 62c0c8d..2ca180a 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -33,6 +33,7 @@ from keystoneclient.v3 import credentials from keystoneclient.v3 import domain_configs from keystoneclient.v3 import domains from keystoneclient.v3 import ec2 +from keystoneclient.v3 import endpoint_groups from keystoneclient.v3 import endpoints from keystoneclient.v3 import groups from keystoneclient.v3 import policies @@ -130,6 +131,11 @@ class Client(httpclient.HTTPClient): :py:class:`keystoneclient.v3.contrib.endpoint_filter.\ EndpointFilterManager` + .. py:attribute:: endpoint_groups + + :py:class:`keystoneclient.v3.endpoint_groups.\ + EndpointGroupManager` + .. py:attribute:: endpoint_policy :py:class:`keystoneclient.v3.contrib.endpoint_policy.\ @@ -211,6 +217,8 @@ class Client(httpclient.HTTPClient): self.ec2 = ec2.EC2Manager(self._adapter) self.endpoint_filter = endpoint_filter.EndpointFilterManager( self._adapter) + self.endpoint_groups = endpoint_groups.EndpointGroupManager( + self._adapter) self.endpoint_policy = endpoint_policy.EndpointPolicyManager( self._adapter) self.endpoints = endpoints.EndpointManager(self._adapter) @@ -225,6 +233,7 @@ class Client(httpclient.HTTPClient): self.role_assignments = ( role_assignments.RoleAssignmentManager(self._adapter)) self.roles = roles.RoleManager(self._adapter) + self.inference_rules = roles.InferenceRuleManager(self._adapter) self.services = services.ServiceManager(self._adapter) self.simple_cert = simple_cert.SimpleCertManager(self._adapter) self.tokens = tokens.TokenManager(self._adapter) diff --git a/keystoneclient/v3/contrib/federation/identity_providers.py b/keystoneclient/v3/contrib/federation/identity_providers.py index 85c4a73..4675ca3 100644 --- a/keystoneclient/v3/contrib/federation/identity_providers.py +++ b/keystoneclient/v3/contrib/federation/identity_providers.py @@ -79,7 +79,7 @@ class IdentityProviderManager(base.CrudManager): GET /OS-FEDERATION/identity_providers :returns: a list of IdentityProvider resource objects. - :rtype: list + :rtype: List """ return super(IdentityProviderManager, self).list(**kwargs) diff --git a/keystoneclient/v3/endpoint_groups.py b/keystoneclient/v3/endpoint_groups.py new file mode 100644 index 0000000..f8b47c4 --- /dev/null +++ b/keystoneclient/v3/endpoint_groups.py @@ -0,0 +1,136 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from keystoneclient import base + + +class EndpointGroup(base.Resource): + """Represents an identity endpoint group. + + Attributes: + * id: a UUID that identifies the endpoint group + * name: the endpoint group name + * description: the endpoint group description + * filters: representation of filters in the format of JSON that define + what endpoint entities are part of the group + + """ + + pass + + +class EndpointGroupManager(base.CrudManager): + """Manager class for Endpoint Groups.""" + + resource_class = EndpointGroup + collection_key = 'endpoint_groups' + key = 'endpoint_group' + base_url = 'OS-EP-FILTER' + + def create(self, name, filters, description=None, **kwargs): + """Create an endpoint group. + + :param str name: the name of the endpoint group. + :param str filters: representation of filters in the format of JSON + that define what endpoint entities are part of the + group. + :param str description: a description of the endpoint group. + :param kwargs: any other attribute provided will be passed to the + server. + + :returns: the created endpoint group returned from server. + :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` + + """ + return super(EndpointGroupManager, self).create( + name=name, + filters=filters, + description=description, + **kwargs) + + def get(self, endpoint_group): + """Retrieve an endpoint group. + + :param endpoint_group: the endpoint group to be retrieved from the + server. + :type endpoint_group: + str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` + + :returns: the specified endpoint group returned from server. + :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` + + """ + return super(EndpointGroupManager, self).get( + endpoint_group_id=base.getid(endpoint_group)) + + def check(self, endpoint_group): + """Check if an endpoint group exists. + + :param endpoint_group: the endpoint group to be checked against the + server. + :type endpoint_group: + str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` + + :returns: none if the specified endpoint group exists. + + """ + return super(EndpointGroupManager, self).head( + endpoint_group_id=base.getid(endpoint_group)) + + def list(self, **kwargs): + """List endpoint groups. + + Any parameter provided will be passed to the server. + + :returns: a list of endpoint groups. + :rtype: list of + :class:`keystoneclient.v3.endpoint_groups.EndpointGroup`. + + """ + return super(EndpointGroupManager, self).list(**kwargs) + + def update(self, endpoint_group, name=None, filters=None, + description=None, **kwargs): + """Update an endpoint group. + + :param str name: the new name of the endpoint group. + :param str filters: the new representation of filters in the format of + JSON that define what endpoint entities are part of + the group. + :param str description: the new description of the endpoint group. + :param kwargs: any other attribute provided will be passed to the + server. + + :returns: the updated endpoint group returned from server. + :rtype: :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` + + """ + return super(EndpointGroupManager, self).update( + endpoint_group_id=base.getid(endpoint_group), + name=name, + filters=filters, + description=description, + **kwargs) + + def delete(self, endpoint_group): + """Delete an endpoint group. + + :param endpoint_group: the endpoint group to be deleted on the server. + :type endpoint_group: + str or :class:`keystoneclient.v3.endpoint_groups.EndpointGroup` + + :returns: Response object with 204 status. + :rtype: :class:`requests.models.Response` + + """ + return super(EndpointGroupManager, self).delete( + endpoint_group_id=base.getid(endpoint_group)) diff --git a/keystoneclient/v3/roles.py b/keystoneclient/v3/roles.py index c083856..d5439ff 100644 --- a/keystoneclient/v3/roles.py +++ b/keystoneclient/v3/roles.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from debtcollector import removals from positional import positional from keystoneclient import base @@ -35,7 +36,7 @@ class Role(base.Resource): class InferenceRule(base.Resource): - """Represents an Rule that states one ROle implies another. + """Represents a rule that states one role implies another. Attributes: * prior_role: this role implies the other @@ -52,6 +53,7 @@ class RoleManager(base.CrudManager): resource_class = Role collection_key = 'roles' key = 'role' + deprecation_msg = 'keystoneclient.v3.roles.InferenceRuleManager' def _role_grants_base_url(self, user, group, domain, project, use_inherit_extension): @@ -118,92 +120,6 @@ class RoleManager(base.CrudManager): domain_id=domain_id, **kwargs) - def _implied_role_url_tail(self, prior_role, implied_role): - base_url = ('/%(prior_role_id)s/implies/%(implied_role_id)s' % - {'prior_role_id': base.getid(prior_role), - 'implied_role_id': base.getid(implied_role)}) - return base_url - - def create_implied(self, prior_role, implied_role, **kwargs): - """Create an inference rule. - - :param prior_role: the role which implies ``implied_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param implied_role: the role which is implied by ``prior_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param kwargs: any other attribute provided will be passed to the - server. - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - resp, body = self.client.put("/roles" + url_tail, **kwargs) - return self.resource_class(self, body['role_inference']) - - def delete_implied(self, prior_role, implied_role, **kwargs): - """Delete an inference rule. - - :param prior_role: the role which implies ``implied_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param implied_role: the role which is implied by ``prior_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param kwargs: any other attribute provided will be passed to the - server. - - :returns: Response object with 204 status. - :rtype: :class:`requests.models.Response` - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - return super(RoleManager, self).delete(tail=url_tail, **kwargs) - - def get_implied(self, prior_role, implied_role, **kwargs): - """Retrieve an inference rule. - - :param prior_role: the role which implies ``implied_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param implied_role: the role which is implied by ``prior_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param kwargs: any other attribute provided will be passed to the - server. - - :returns: the specified role inference returned from server. - :rtype: :class:`keystoneclient.v3.roles.InferenceRule` - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - return super(RoleManager, self).get(tail=url_tail, **kwargs) - - def check_implied(self, prior_role, implied_role, **kwargs): - """Check if an inference rule exists. - - :param prior_role: the role which implies ``implied_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param implied_role: the role which is implied by ``prior_role``. - :type role: str or :class:`keystoneclient.v3.roles.Role` - :param kwargs: any other attribute provided will be passed to the - server. - - :returns: response object with 200 status returned from server. - :rtype: :class:`requests.models.Response` - - """ - url_tail = self._implied_role_url_tail(prior_role, implied_role) - return super(RoleManager, self).head(tail=url_tail, **kwargs) - - def list_role_inferences(self, **kwargs): - """List role inferences. - - :param kwargs: attributes provided will be passed to the server. - - :returns: a list of roles inferences. - :rtype: list of :class:`keystoneclient.v3.roles.InferenceRule` - - """ - resp, body = self.client.get('/role_inferences/', **kwargs) - obj_class = InferenceRule - return [obj_class(self, res, loaded=True) - for res in body['role_inferences']] - def get(self, role): """Retrieve a role. @@ -440,3 +356,181 @@ class RoleManager(base.CrudManager): role_id=base.getid(role), os_inherit_extension_inherited=os_inherit_extension_inherited, **kwargs) + + @removals.remove(message='Use %s.create instead.' % deprecation_msg, + version='3.9.0', removal_version='4.0.0') + def create_implied(self, prior_role, implied_role, **kwargs): + return InferenceRuleManager(self.client).create(prior_role, + implied_role) + + @removals.remove(message='Use %s.delete instead.' % deprecation_msg, + version='3.9.0', removal_version='4.0.0') + def delete_implied(self, prior_role, implied_role, **kwargs): + return InferenceRuleManager(self.client).delete(prior_role, + implied_role) + + @removals.remove(message='Use %s.get instead.' % deprecation_msg, + version='3.9.0', removal_version='4.0.0') + def get_implied(self, prior_role, implied_role, **kwargs): + return InferenceRuleManager(self.client).get(prior_role, + implied_role) + + @removals.remove(message='Use %s.check instead.' % deprecation_msg, + version='3.9.0', removal_version='4.0.0') + def check_implied(self, prior_role, implied_role, **kwargs): + return InferenceRuleManager(self.client).check(prior_role, + implied_role) + + @removals.remove(message='Use %s.list_inference_roles' % deprecation_msg, + version='3.9.0', removal_version='4.0.0') + def list_role_inferences(self, **kwargs): + return InferenceRuleManager(self.client).list_inference_roles() + + +class InferenceRuleManager(base.CrudManager): + """Manager class for manipulating Identity inference rules.""" + + resource_class = InferenceRule + collection_key = 'role_inferences' + key = 'role_inference' + + def _implied_role_url_tail(self, prior_role, implied_role): + base_url = ('/%(prior_role_id)s/implies/%(implied_role_id)s' % + {'prior_role_id': base.getid(prior_role), + 'implied_role_id': base.getid(implied_role)}) + return base_url + + def create(self, prior_role, implied_role): + """Create an inference rule. + + An inference rule is comprised of two roles, a prior role and an + implied role. The prior role will imply the implied role. + + Valid HTTP return codes: + + * 201: Resource is created successfully + * 404: A role cannot be found + * 409: The inference rule already exists + + :param prior_role: the role which implies ``implied_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + :param implied_role: the role which is implied by ``prior_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + + :returns: a newly created role inference returned from server. + :rtype: :class:`keystoneclient.v3.roles.InferenceRule` + + """ + url_tail = self._implied_role_url_tail(prior_role, implied_role) + _resp, body = self.client.put("/roles" + url_tail) + return self.resource_class(self, body['role_inference']) + + def delete(self, prior_role, implied_role): + """Delete an inference rule. + + When deleting an inference rule, both roles are required. Note that + neither role is deleted, only the inference relationship is dissolved. + + Valid HTTP return codes: + + * 204: Delete request is accepted + * 404: A role cannot be found + + :param prior_role: the role which implies ``implied_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + :param implied_role: the role which is implied by ``prior_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + + :returns: Response object with 204 status. + :rtype: :class:`requests.models.Response` + + """ + url_tail = self._implied_role_url_tail(prior_role, implied_role) + return self.client.delete("/roles" + url_tail) + + def get(self, prior_role, implied_role): + """Retrieve an inference rule. + + Valid HTTP return codes: + + * 200: Inference rule is returned + * 404: A role cannot be found + + :param prior_role: the role which implies ``implied_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + :param implied_role: the role which is implied by ``prior_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + + :returns: the specified role inference returned from server. + :rtype: :class:`keystoneclient.v3.roles.InferenceRule` + + """ + url_tail = self._implied_role_url_tail(prior_role, implied_role) + _resp, body = self.client.get("/roles" + url_tail) + return self.resource_class(self, body['role_inference']) + + def list(self, prior_role): + """List all roles that a role may imply. + + Valid HTTP return codes: + + * 200: List of inference rules are returned + * 404: A role cannot be found + + :param prior_role: the role which implies ``implied_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + + :returns: the specified role inference returned from server. + :rtype: :class:`keystoneclient.v3.roles.InferenceRule` + + """ + url_tail = ('/%s/implies' % base.getid(prior_role)) + _resp, body = self.client.get("/roles" + url_tail) + return self.resource_class(self, body['role_inference']) + + def check(self, prior_role, implied_role): + """Check if an inference rule exists. + + Valid HTTP return codes: + + * 204: The rule inference exists + * 404: A role cannot be found + + :param prior_role: the role which implies ``implied_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + :param implied_role: the role which is implied by ``prior_role``. + :type role: str or :class:`keystoneclient.v3.roles.Role` + + :returns: response object with 204 status returned from server. + :rtype: :class:`requests.models.Response` + + """ + url_tail = self._implied_role_url_tail(prior_role, implied_role) + return self.client.head("/roles" + url_tail) + + def list_inference_roles(self): + """List all rule inferences. + + Valid HTTP return codes: + + * 200: All inference rules are returned + + :param kwargs: attributes provided will be passed to the server. + + :returns: a list of inference rules. + :rtype: list of :class:`keystoneclient.v3.roles.InferenceRule` + + """ + return super(InferenceRuleManager, self).list() + + def update(self, **kwargs): + raise exceptions.MethodNotImplemented( + _('Update not supported for rule inferences')) + + def find(self, **kwargs): + raise exceptions.MethodNotImplemented( + _('Find not supported for rule inferences')) + + def put(self, **kwargs): + raise exceptions.MethodNotImplemented( + _('Put not supported for rule inferences')) diff --git a/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml b/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml new file mode 100644 index 0000000..2699a7f --- /dev/null +++ b/releasenotes/notes/bp-pci-dss-query-password-expired-users-b0c4b1bbdcf33f16.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added ability to filter on multiple values with the same parameter key. + For example, we can now filter on user names that contain both ``test`` and + ``user`` using ``keystone.users.list(name__contains=['test', 'user'])``. diff --git a/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml b/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml new file mode 100644 index 0000000..e9c1c9c --- /dev/null +++ b/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - > + [`bug 1616105 <https://bugs.launchpad.net/keystoneauth/+bug/1616105>`_] + Only log the response body when the ``Content-Type`` header is set to + ``application/json``. This avoids logging large binary objects (such as + images). Other ``Content-Type`` will not be logged. Additional + ``Content-Type`` strings can be added as required. diff --git a/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml b/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml new file mode 100644 index 0000000..5d066e9 --- /dev/null +++ b/releasenotes/notes/bug-1654847-d2e9df994c7b617f.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The ``X-Service-Token`` header value is now properly masked, and is + displayed as a hash value, in the log. diff --git a/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml b/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml index 82a723d..fbb3a47 100644 --- a/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml +++ b/releasenotes/notes/deprecated_auth-d2a2bf537bdb88d3.yaml @@ -3,10 +3,10 @@ deprecations: - > [`blueprint deprecate-to-ksa <https://blueprints.launchpad.net/python-keystoneclient/+spec/deprecate-to-ksa>`_] Several modules related to authentication in keystoneclient have been - deprecated in favor of [`keystoneauth <http://docs.openstack.org/developer/keystoneauth/>`_] + deprecated in favor of [`keystoneauth <https://docs.openstack.org/developer/keystoneauth/>`_] These modules include: ``keystoneclient.session``, ``keystoneclient.adapter``, ``keystoneclient.httpclient``, ``keystoneclient.auth.base``, ``keystoneclient.auth.cli``, ``keystoneclient.auth.conf``, ``keystoneclient.auth.identity.base``, and ``keystoneclient.auth.token_endpoint``. Tips for migrating to `keystoneauth` have been - [`documented <http://docs.openstack.org/developer/keystoneauth/migrating.html>`_]. + [`documented <https://docs.openstack.org/developer/keystoneauth/migrating.html>`_]. diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index e316bc5..a2e09fc 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # 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 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index e288920..10af637 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,5 +6,6 @@ :maxdepth: 1 unreleased + ocata newton mitaka diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst new file mode 100644 index 0000000..9515f6c --- /dev/null +++ b/releasenotes/source/ocata.rst @@ -0,0 +1,6 @@ +============================ + Ocata Series Release Notes +============================ + +.. release-notes:: + :branch: origin/stable/ocata diff --git a/requirements.txt b/requirements.txt index 42f0343..a30ce16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,15 +2,15 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.8 # Apache-2.0 +pbr!=2.1.0,>=2.0.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 -keystoneauth1>=2.14.0 # Apache-2.0 -oslo.config!=3.18.0,>=3.14.0 # Apache-2.0 +keystoneauth1>=2.20.0 # Apache-2.0 +oslo.config>=3.22.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.18.0 # Apache-2.0 +oslo.utils>=3.20.0 # Apache-2.0 positional>=1.1.1 # Apache-2.0 -requests!=2.12.2,>=2.10.0 # Apache-2.0 +requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 six>=1.9.0 # MIT -stevedore>=1.17.1 # Apache-2.0 +stevedore>=1.20.0 # Apache-2.0 @@ -5,7 +5,7 @@ description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://docs.openstack.org/developer/python-keystoneclient +home-page = https://docs.openstack.org/developer/python-keystoneclient classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -16,7 +16,6 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 [files] @@ -25,5 +25,5 @@ except ImportError: pass setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index e29ae1d..5b58c4f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,18 +5,18 @@ hacking<0.11,>=0.10.0 flake8-docstrings==0.2.1.post1 # MIT -coverage>=4.0 # Apache-2.0 +coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD keyring>=5.5.1 # MIT/PSF -lxml>=2.3 # BSD +lxml!=3.7.0,>=2.3 # BSD mock>=2.0 # BSD oauthlib>=0.6 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx!=1.3b1,<1.4,>=1.2.1 # BSD -tempest>=12.1.0 # Apache-2.0 +sphinx>=1.5.1 # BSD +tempest>=14.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testresources>=0.2.4 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD diff --git a/tools/tox_install.sh b/tools/tox_install.sh new file mode 100755 index 0000000..e61b63a --- /dev/null +++ b/tools/tox_install.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should remove the version pin in +# the constraints file before applying it for from-source installation. + +CONSTRAINTS_FILE="$1" +shift 1 + +set -e + +# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get +# published to logs.openstack.org for easy debugging. +localfile="$VIRTUAL_ENV/log/upper-constraints.txt" + +if [[ "$CONSTRAINTS_FILE" != http* ]]; then + CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" +fi +# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep +curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" + +pip install -c"$localfile" openstack-requirements + +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints "$localfile" -- "$CLIENT_NAME" + +pip install -c"$localfile" -U "$@" +exit $? @@ -1,12 +1,14 @@ [tox] -minversion = 1.6 +minversion = 2.0 skipsdist = True -envlist = py34,py27,pep8,releasenotes +envlist = py35,py27,pep8,releasenotes [testenv] usedevelop = True -install_command = pip install -U {opts} {packages} +install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = VIRTUAL_ENV={envdir} + BRANCH_NAME=master + CLIENT_NAME=python-keystoneclient OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False @@ -37,7 +39,8 @@ commands = python setup.py testr --coverage --testr-args='{posargs}' commands = oslo_debug_helper -t keystoneclient/tests {posargs} [testenv:functional] -setenv = OS_TEST_PATH=./keystoneclient/tests/functional +setenv = {[testenv]setenv} + OS_TEST_PATH=./keystoneclient/tests/functional passenv = OS_* [flake8] @@ -61,7 +64,6 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen [hacking] import_exceptions = keystoneclient.i18n -local-check-factory = keystoneclient.tests.hacking.checks.factory [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if |