summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/conf.py3
-rw-r--r--keystoneclient/_discover.py19
-rw-r--r--keystoneclient/auth/identity/base.py10
-rw-r--r--keystoneclient/auth/identity/generic/base.py8
-rw-r--r--keystoneclient/base.py6
-rw-r--r--keystoneclient/common/cms.py10
-rw-r--r--keystoneclient/contrib/ec2/utils.py2
-rw-r--r--keystoneclient/discover.py3
-rw-r--r--keystoneclient/exceptions.py2
-rw-r--r--keystoneclient/generic/client.py6
-rw-r--r--keystoneclient/httpclient.py10
-rw-r--r--keystoneclient/i18n.py10
-rw-r--r--keystoneclient/session.py12
-rw-r--r--keystoneclient/tests/functional/v3/test_endpoint_filters.py86
-rw-r--r--keystoneclient/tests/functional/v3/test_endpoint_groups.py5
-rw-r--r--keystoneclient/tests/functional/v3/test_projects.py21
-rw-r--r--keystoneclient/tests/unit/apiclient/test_exceptions.py4
-rw-r--r--keystoneclient/tests/unit/auth/test_identity_common.py2
-rw-r--r--keystoneclient/tests/unit/auth/test_loading.py3
-rw-r--r--keystoneclient/tests/unit/auth/utils.py3
-rw-r--r--keystoneclient/tests/unit/client_fixtures.py2
-rw-r--r--keystoneclient/tests/unit/test_base.py16
-rw-r--r--keystoneclient/tests/unit/test_discovery.py2
-rw-r--r--keystoneclient/tests/unit/test_fixtures.py3
-rw-r--r--keystoneclient/tests/unit/test_http.py4
-rw-r--r--keystoneclient/tests/unit/test_session.py4
-rw-r--r--keystoneclient/tests/unit/utils.py3
-rw-r--r--keystoneclient/tests/unit/v2_0/test_client.py3
-rw-r--r--keystoneclient/tests/unit/v3/test_client.py3
-rw-r--r--keystoneclient/tests/unit/v3/test_endpoint_filter.py144
-rw-r--r--keystoneclient/tests/unit/v3/test_trusts.py16
-rw-r--r--keystoneclient/tests/unit/v3/utils.py3
-rw-r--r--keystoneclient/v2_0/tenants.py5
-rw-r--r--keystoneclient/v3/contrib/endpoint_filter.py77
-rw-r--r--keystoneclient/v3/contrib/trusts.py13
-rw-r--r--releasenotes/notes/bug-1641674-4862454115265e76.yaml8
-rw-r--r--releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml (renamed from keystoneclient/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml)0
-rw-r--r--requirements.txt10
-rw-r--r--setup.cfg2
-rw-r--r--test-requirements.txt6
40 files changed, 430 insertions, 119 deletions
diff --git a/doc/source/conf.py b/doc/source/conf.py
index ce6d5b7..010a259 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -151,8 +151,7 @@ modindex_common_prefix = ['keystoneclient.']
git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
"-n1"]
try:
- html_last_updated_fmt = subprocess.Popen(git_cmd,
- stdout=subprocess.PIPE).communicate()[0]
+ html_last_updated_fmt = subprocess.check_output(git_cmd).decode('utf-8')
except Exception:
warnings.warn('Cannot get last updated time from git repository. '
'Not setting "html_last_updated_fmt".')
diff --git a/keystoneclient/_discover.py b/keystoneclient/_discover.py
index a6d5727..69eed9a 100644
--- a/keystoneclient/_discover.py
+++ b/keystoneclient/_discover.py
@@ -27,7 +27,7 @@ import re
from positional import positional
from keystoneclient import exceptions
-from keystoneclient.i18n import _, _LI, _LW
+from keystoneclient.i18n import _
_LOGGER = logging.getLogger(__name__)
@@ -167,8 +167,8 @@ class Discover(object):
try:
status = v['status']
except KeyError:
- _LOGGER.warning(_LW('Skipping over invalid version data. '
- 'No stability status in version.'))
+ _LOGGER.warning('Skipping over invalid version data. '
+ 'No stability status in version.')
continue
status = status.lower()
@@ -210,14 +210,13 @@ class Discover(object):
try:
version_str = v['id']
except KeyError:
- _LOGGER.info(_LI('Skipping invalid version data. Missing ID.'))
+ _LOGGER.info('Skipping invalid version data. Missing ID.')
continue
try:
links = v['links']
except KeyError:
- _LOGGER.info(
- _LI('Skipping invalid version data. Missing links'))
+ _LOGGER.info('Skipping invalid version data. Missing links')
continue
version_number = normalize_version_number(version_str)
@@ -227,15 +226,15 @@ class Discover(object):
rel = link['rel']
url = link['href']
except (KeyError, TypeError):
- _LOGGER.info(_LI('Skipping invalid version link. '
- 'Missing link URL or relationship.'))
+ _LOGGER.info('Skipping invalid version link. '
+ 'Missing link URL or relationship.')
continue
if rel.lower() == 'self':
break
else:
- _LOGGER.info(_LI('Skipping invalid version data. '
- 'Missing link to endpoint.'))
+ _LOGGER.info('Skipping invalid version data. '
+ 'Missing link to endpoint.')
continue
versions.append({'version': version_number,
diff --git a/keystoneclient/auth/identity/base.py b/keystoneclient/auth/identity/base.py
index 29ab121..b20d3e2 100644
--- a/keystoneclient/auth/identity/base.py
+++ b/keystoneclient/auth/identity/base.py
@@ -22,7 +22,6 @@ import six
from keystoneclient import _discover
from keystoneclient.auth import base
from keystoneclient import exceptions
-from keystoneclient.i18n import _LW
LOG = logging.getLogger(__name__)
@@ -317,10 +316,10 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
else:
if not service_type:
- LOG.warning(_LW(
+ LOG.warning(
'Plugin cannot return an endpoint without knowing the '
'service type that is required. Add service_type to '
- 'endpoint filtering data.'))
+ 'endpoint filtering data.')
return None
if not interface:
@@ -353,10 +352,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.warning(_LW(
+ LOG.warning(
'Failed to contact the endpoint at %s for discovery. Fallback '
- 'to using that endpoint as the base url.'),
- url)
+ 'to using that endpoint as the base url.', url)
else:
url = disc.url_for(version)
diff --git a/keystoneclient/auth/identity/generic/base.py b/keystoneclient/auth/identity/generic/base.py
index 85207ac..98680ef 100644
--- a/keystoneclient/auth/identity/generic/base.py
+++ b/keystoneclient/auth/identity/generic/base.py
@@ -20,7 +20,7 @@ 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
+from keystoneclient.i18n import _
LOG = logging.getLogger(__name__)
@@ -140,9 +140,9 @@ class BaseGenericPlugin(base.BaseIdentityPlugin):
except (exceptions.DiscoveryFailure,
exceptions.HTTPError,
exceptions.ConnectionError):
- LOG.warning(_LW('Discovering versions from the identity service '
- 'failed when creating the password plugin. '
- 'Attempting to determine version from URL.'))
+ LOG.warning('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()
diff --git a/keystoneclient/base.py b/keystoneclient/base.py
index dc449f7..80e09a0 100644
--- a/keystoneclient/base.py
+++ b/keystoneclient/base.py
@@ -52,14 +52,14 @@ def getid(obj):
def filter_none(**kwargs):
"""Remove any entries from a dictionary where the value is None."""
- return dict((k, v) for k, v in six.iteritems(kwargs) if v is not None)
+ return dict((k, v) for k, v in kwargs.items() if v is not None)
def filter_kwargs(f):
@functools.wraps(f)
def func(*args, **kwargs):
new_kwargs = {}
- for key, ref in six.iteritems(kwargs):
+ for key, ref in kwargs.items():
if ref is None:
# drop null values
continue
@@ -481,7 +481,7 @@ class Resource(object):
return None
def _add_details(self, info):
- for (k, v) in six.iteritems(info):
+ for (k, v) in info.items():
try:
try:
setattr(self, k, v)
diff --git a/keystoneclient/common/cms.py b/keystoneclient/common/cms.py
index fb30602..9c3e0bd 100644
--- a/keystoneclient/common/cms.py
+++ b/keystoneclient/common/cms.py
@@ -29,7 +29,7 @@ from debtcollector import removals
import six
from keystoneclient import exceptions
-from keystoneclient.i18n import _, _LE
+from keystoneclient.i18n import _
subprocess = None
@@ -376,11 +376,11 @@ def cms_sign_data(data_to_sign, signing_cert_file_name, signing_key_file_name,
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"'))
+ LOG.error('Signing error: Unable to load certificate - '
+ 'ensure you have configured PKI with '
+ '"keystone-manage pki_setup"')
else:
- LOG.error(_LE('Signing error: %s'), err)
+ LOG.error('Signing error: %s', err)
raise subprocess.CalledProcessError(retcode, 'openssl')
if outform == PKI_ASN1_FORM:
return output.decode('utf-8')
diff --git a/keystoneclient/contrib/ec2/utils.py b/keystoneclient/contrib/ec2/utils.py
index dcd3ff5..1ef5df4 100644
--- a/keystoneclient/contrib/ec2/utils.py
+++ b/keystoneclient/contrib/ec2/utils.py
@@ -218,7 +218,7 @@ class Ec2Signer(object):
# - the Authorization header (SignedHeaders key)
# - the X-Amz-SignedHeaders query parameter
headers_lower = dict((k.lower().strip(), v.strip())
- for (k, v) in six.iteritems(headers))
+ for (k, v) in headers.items())
# Boto versions < 2.9.3 strip the port component of the host:port
# header, so detect the user-agent via the header and strip the
diff --git a/keystoneclient/discover.py b/keystoneclient/discover.py
index a70b409..823c94c 100644
--- a/keystoneclient/discover.py
+++ b/keystoneclient/discover.py
@@ -15,7 +15,6 @@ import warnings
from debtcollector import removals
from keystoneauth1 import plugin
from positional import positional
-import six
from keystoneclient import _discover
from keystoneclient import exceptions
@@ -296,7 +295,7 @@ class Discover(_discover.Discover):
raise exceptions.DiscoveryFailure(msg)
# kwargs should take priority over stored kwargs.
- for k, v in six.iteritems(self._client_kwargs):
+ for k, v in self._client_kwargs.items():
kwargs.setdefault(k, v)
# restore the url to either auth_url or endpoint depending on what
diff --git a/keystoneclient/exceptions.py b/keystoneclient/exceptions.py
index 2e3270a..5b06be9 100644
--- a/keystoneclient/exceptions.py
+++ b/keystoneclient/exceptions.py
@@ -415,7 +415,7 @@ An alias of :py:exc:`keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin`
class UnsupportedParameters(ClientException):
"""A parameter that was provided or returned is not supported.
- :param list(str) names: Names of the unsupported parameters.
+ :param List(str) names: Names of the unsupported parameters.
.. py:attribute:: names
diff --git a/keystoneclient/generic/client.py b/keystoneclient/generic/client.py
index 2f1ffca..e6b5833 100644
--- a/keystoneclient/generic/client.py
+++ b/keystoneclient/generic/client.py
@@ -20,7 +20,7 @@ from six.moves.urllib import parse as urlparse
from keystoneclient import exceptions
from keystoneclient import httpclient
-from keystoneclient.i18n import _, _LE
+from keystoneclient.i18n import _
_logger = logging.getLogger(__name__)
@@ -131,7 +131,7 @@ class Client(httpclient.HTTPClient):
else:
raise exceptions.from_response(resp, "GET", url)
except Exception:
- _logger.exception(_LE('Failed to detect available versions.'))
+ _logger.exception('Failed to detect available versions.')
def discover_extensions(self, url=None):
"""Discover Keystone extensions supported.
@@ -175,7 +175,7 @@ class Client(httpclient.HTTPClient):
raise exceptions.from_response(
resp, "GET", "%sextensions" % url)
except Exception:
- _logger.exception(_LE('Failed to check keystone extensions.'))
+ _logger.exception('Failed to check keystone extensions.')
@staticmethod
def _get_version_info(version, root_url):
diff --git a/keystoneclient/httpclient.py b/keystoneclient/httpclient.py
index 4d893c3..e6813f3 100644
--- a/keystoneclient/httpclient.py
+++ b/keystoneclient/httpclient.py
@@ -54,7 +54,7 @@ from keystoneclient import access
from keystoneclient.auth import base
from keystoneclient import baseclient
from keystoneclient import exceptions
-from keystoneclient.i18n import _, _LW
+from keystoneclient.i18n import _
from keystoneclient import session as client_session
@@ -393,7 +393,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
# keyring setup
if use_keyring and keyring is None:
- _logger.warning(_LW('Failed to load keyring modules.'))
+ _logger.warning('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
@@ -633,8 +633,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
auth_ref = None
except Exception as e:
auth_ref = None
- _logger.warning(
- _LW('Unable to retrieve token from keyring %s'), e)
+ _logger.warning('Unable to retrieve token from keyring %s', e)
return (keyring_key, auth_ref)
def store_auth_ref_into_keyring(self, keyring_key):
@@ -646,8 +645,7 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
pickle.dumps(self.auth_ref)) # nosec
# (cjschaef): see bug 1534288
except Exception as e:
- _logger.warning(
- _LW("Failed to store token into keyring %s"), e)
+ _logger.warning("Failed to store token into keyring %s", e)
def _process_management_url(self, region_name):
try:
diff --git a/keystoneclient/i18n.py b/keystoneclient/i18n.py
index a196472..fdbf183 100644
--- a/keystoneclient/i18n.py
+++ b/keystoneclient/i18n.py
@@ -25,13 +25,3 @@ _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/session.py b/keystoneclient/session.py
index 17b1d7f..9faedac 100644
--- a/keystoneclient/session.py
+++ b/keystoneclient/session.py
@@ -31,7 +31,7 @@ import six
from six.moves import urllib
from keystoneclient import exceptions
-from keystoneclient.i18n import _, _LI, _LW
+from keystoneclient.i18n import _
osprofiler_web = importutils.try_import("osprofiler.web")
@@ -201,7 +201,7 @@ class Session(object):
string_parts.append(url)
if headers:
- for header in six.iteritems(headers):
+ for header in headers.items():
string_parts.append('-H "%s: %s"'
% self._process_header(header))
@@ -249,7 +249,7 @@ class Session(object):
'RESP:',
'[%s]' % response.status_code
]
- for header in six.iteritems(response.headers):
+ for header in response.headers.items():
string_parts.append('%s: %s' % self._process_header(header))
string_parts.append('\nRESP BODY: %s\n' % strutils.mask_password(text))
@@ -476,7 +476,7 @@ class Session(object):
if connect_retries <= 0:
raise
- logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'),
+ logger.info('Failure: %(e)s. Retrying in %(delay).1fs.',
{'e': e, 'delay': connect_retry_delay})
time.sleep(connect_retry_delay)
@@ -503,8 +503,8 @@ class Session(object):
try:
location = resp.headers['location']
except KeyError:
- logger.warning(_LW("Failed to redirect request to %s as new "
- "location was not provided."), resp.url)
+ logger.warning("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.
diff --git a/keystoneclient/tests/functional/v3/test_endpoint_filters.py b/keystoneclient/tests/functional/v3/test_endpoint_filters.py
new file mode 100644
index 0000000..d8956be
--- /dev/null
+++ b/keystoneclient/tests/functional/v3/test_endpoint_filters.py
@@ -0,0 +1,86 @@
+# 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 keystoneauth1.exceptions import http
+
+from keystoneclient.tests.functional import base
+from keystoneclient.tests.functional.v3 import client_fixtures as fixtures
+from keystoneclient.tests.functional.v3 import test_endpoint_groups
+from keystoneclient.tests.functional.v3 import test_projects
+
+
+class EndpointFiltersTestCase(base.V3ClientTestCase,
+ test_endpoint_groups.EndpointGroupsTestMixin,
+ test_projects.ProjectsTestMixin):
+
+ def setUp(self):
+ super(EndpointFiltersTestCase, self).setUp()
+
+ self.project = fixtures.Project(self.client)
+ self.endpoint_group = fixtures.EndpointGroup(self.client)
+ self.useFixture(self.project)
+ self.useFixture(self.endpoint_group)
+
+ self.client.endpoint_filter.add_endpoint_group_to_project(
+ self.endpoint_group, self.project)
+
+ def test_add_endpoint_group_to_project(self):
+ project = fixtures.Project(self.client)
+ endpoint_group = fixtures.EndpointGroup(self.client)
+ self.useFixture(project)
+ self.useFixture(endpoint_group)
+
+ self.client.endpoint_filter.add_endpoint_group_to_project(
+ endpoint_group, project)
+ self.client.endpoint_filter.check_endpoint_group_in_project(
+ endpoint_group, project)
+
+ def test_delete_endpoint_group_from_project(self):
+ self.client.endpoint_filter.delete_endpoint_group_from_project(
+ self.endpoint_group, self.project)
+ self.assertRaises(
+ http.NotFound,
+ self.client.endpoint_filter.check_endpoint_group_in_project,
+ self.endpoint_group, self.project)
+
+ def test_list_endpoint_groups_for_project(self):
+ endpoint_group_two = fixtures.EndpointGroup(self.client)
+ self.useFixture(endpoint_group_two)
+ self.client.endpoint_filter.add_endpoint_group_to_project(
+ endpoint_group_two, self.project)
+
+ endpoint_groups = (
+ self.client.endpoint_filter.list_endpoint_groups_for_project(
+ self.project
+ )
+ )
+
+ for endpoint_group in endpoint_groups:
+ self.check_endpoint_group(endpoint_group)
+
+ self.assertIn(self.endpoint_group.entity, endpoint_groups)
+ self.assertIn(endpoint_group_two.entity, endpoint_groups)
+
+ def test_list_projects_for_endpoint_group(self):
+ project_two = fixtures.Project(self.client)
+ self.useFixture(project_two)
+ self.client.endpoint_filter.add_endpoint_group_to_project(
+ self.endpoint_group, project_two)
+
+ f = self.client.endpoint_filter.list_projects_for_endpoint_group
+ projects = f(self.endpoint_group)
+
+ for project in projects:
+ self.check_project(project)
+
+ self.assertIn(self.project.entity, projects)
+ self.assertIn(project_two.entity, projects)
diff --git a/keystoneclient/tests/functional/v3/test_endpoint_groups.py b/keystoneclient/tests/functional/v3/test_endpoint_groups.py
index 10eccfb..52fcf72 100644
--- a/keystoneclient/tests/functional/v3/test_endpoint_groups.py
+++ b/keystoneclient/tests/functional/v3/test_endpoint_groups.py
@@ -18,7 +18,7 @@ from keystoneclient.tests.functional import base
from keystoneclient.tests.functional.v3 import client_fixtures as fixtures
-class EndpointGroupsTestCase(base.V3ClientTestCase):
+class EndpointGroupsTestMixin(object):
def check_endpoint_group(self, endpoint_group, endpoint_group_ref=None):
self.assertIsNotNone(endpoint_group.id)
@@ -40,6 +40,9 @@ class EndpointGroupsTestCase(base.V3ClientTestCase):
self.assertIsNotNone(endpoint_group.name)
self.assertIsNotNone(endpoint_group.filters)
+
+class EndpointGroupsTestCase(base.V3ClientTestCase, EndpointGroupsTestMixin):
+
def test_create_endpoint_group(self):
endpoint_group_ref = {
'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
diff --git a/keystoneclient/tests/functional/v3/test_projects.py b/keystoneclient/tests/functional/v3/test_projects.py
index 0fa631d..4b8d749 100644
--- a/keystoneclient/tests/functional/v3/test_projects.py
+++ b/keystoneclient/tests/functional/v3/test_projects.py
@@ -18,15 +18,7 @@ from keystoneclient.tests.functional import base
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)
+class ProjectsTestMixin(object):
def check_project(self, project, project_ref=None):
self.assertIsNotNone(project.id)
@@ -51,6 +43,17 @@ class ProjectsTestCase(base.V3ClientTestCase):
self.assertIsNotNone(project.domain_id)
self.assertIsNotNone(project.enabled)
+
+class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin):
+
+ 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 test_create_subproject(self):
project_ref = {
'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
diff --git a/keystoneclient/tests/unit/apiclient/test_exceptions.py b/keystoneclient/tests/unit/apiclient/test_exceptions.py
index ddf8d98..65cf080 100644
--- a/keystoneclient/tests/unit/apiclient/test_exceptions.py
+++ b/keystoneclient/tests/unit/apiclient/test_exceptions.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
-
from keystoneclient import exceptions
from keystoneclient.tests.unit import utils
@@ -23,7 +21,7 @@ class FakeResponse(object):
json_data = {}
def __init__(self, **kwargs):
- for key, value in six.iteritems(kwargs):
+ for key, value in kwargs.items():
setattr(self, key, value)
def json(self):
diff --git a/keystoneclient/tests/unit/auth/test_identity_common.py b/keystoneclient/tests/unit/auth/test_identity_common.py
index 5793672..be8a062 100644
--- a/keystoneclient/tests/unit/auth/test_identity_common.py
+++ b/keystoneclient/tests/unit/auth/test_identity_common.py
@@ -461,7 +461,7 @@ class GenericAuthPluginTests(utils.TestCase):
self.assertEqual(text, resp.text)
- for k, v in six.iteritems(self.auth.headers):
+ for k, v in self.auth.headers.items():
self.assertRequestHeaderEqual(k, v)
with self.deprecations.expect_deprecations_here():
diff --git a/keystoneclient/tests/unit/auth/test_loading.py b/keystoneclient/tests/unit/auth/test_loading.py
index f8ef3b7..3c2689d 100644
--- a/keystoneclient/tests/unit/auth/test_loading.py
+++ b/keystoneclient/tests/unit/auth/test_loading.py
@@ -12,7 +12,6 @@
import uuid
-import six
from keystoneclient.tests.unit.auth import utils
@@ -39,7 +38,7 @@ class TestOtherLoading(utils.TestCase):
self.assertEqual(set(vals), set(called_opts))
- for k, v in six.iteritems(vals):
+ for k, v in vals.items():
# replace - to _ because it's the dest used to create kwargs
self.assertEqual(v, p[k.replace('-', '_')])
diff --git a/keystoneclient/tests/unit/auth/utils.py b/keystoneclient/tests/unit/auth/utils.py
index 6c8be8c..b693172 100644
--- a/keystoneclient/tests/unit/auth/utils.py
+++ b/keystoneclient/tests/unit/auth/utils.py
@@ -16,7 +16,6 @@ import uuid
from keystoneauth1 import fixture
import mock
from oslo_config import cfg
-import six
from keystoneclient import access
from keystoneclient.auth import base
@@ -88,7 +87,7 @@ class TestCase(utils.TestCase):
'a_bool': a_bool}
def assertTestVals(self, plugin, vals=TEST_VALS):
- for k, v in six.iteritems(vals):
+ for k, v in vals.items():
self.assertEqual(v, plugin[k])
diff --git a/keystoneclient/tests/unit/client_fixtures.py b/keystoneclient/tests/unit/client_fixtures.py
index 0e00545..6da259c 100644
--- a/keystoneclient/tests/unit/client_fixtures.py
+++ b/keystoneclient/tests/unit/client_fixtures.py
@@ -703,7 +703,7 @@ class Examples(fixtures.Fixture):
self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY])
self.JSON_TOKEN_RESPONSES = dict([(k, jsonutils.dumps(v)) for k, v in
- six.iteritems(self.TOKEN_RESPONSES)])
+ self.TOKEN_RESPONSES.items()])
EXAMPLES_RESOURCE = testresources.FixtureResource(Examples())
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_discovery.py b/keystoneclient/tests/unit/test_discovery.py
index cc7fb0f..f9d5dbf 100644
--- a/keystoneclient/tests/unit/test_discovery.py
+++ b/keystoneclient/tests/unit/test_discovery.py
@@ -243,7 +243,7 @@ class AvailableVersionsTests(utils.TestCase):
'cinder': jsonutils.dumps(CINDER_EXAMPLES),
'glance': jsonutils.dumps(GLANCE_EXAMPLES)}
- for path, text in six.iteritems(examples):
+ for path, text in examples.items():
url = "%s%s" % (BASE_URL, path)
self.requests_mock.get(url, status_code=300, text=text)
diff --git a/keystoneclient/tests/unit/test_fixtures.py b/keystoneclient/tests/unit/test_fixtures.py
index 345ae45..d7fd26e 100644
--- a/keystoneclient/tests/unit/test_fixtures.py
+++ b/keystoneclient/tests/unit/test_fixtures.py
@@ -12,7 +12,6 @@
import uuid
-import six
from keystoneclient import fixture
from keystoneclient.tests.unit import utils
@@ -246,7 +245,7 @@ class V3TokenTests(utils.TestCase):
# the endpoint content below easier.
self.assertTrue(endpoint.pop('id'))
- for interface, url in six.iteritems(endpoints):
+ for interface, url in endpoints.items():
endpoint = {'interface': interface, 'url': url,
'region': region, 'region_id': region}
self.assertIn(endpoint, service['endpoints'])
diff --git a/keystoneclient/tests/unit/test_http.py b/keystoneclient/tests/unit/test_http.py
index 0282f1a..af9058f 100644
--- a/keystoneclient/tests/unit/test_http.py
+++ b/keystoneclient/tests/unit/test_http.py
@@ -202,10 +202,10 @@ class BasicRequestTests(utils.TestCase):
self.request(headers=headers)
- for k, v in six.iteritems(headers):
+ for k, v in headers.items():
self.assertRequestHeaderEqual(k, v)
- for header in six.iteritems(headers):
+ for header in headers.items():
self.assertThat(self.logger_message.getvalue(),
matchers.Contains('-H "%s: %s"' % header))
diff --git a/keystoneclient/tests/unit/test_session.py b/keystoneclient/tests/unit/test_session.py
index 7a3c57d..27d224d 100644
--- a/keystoneclient/tests/unit/test_session.py
+++ b/keystoneclient/tests/unit/test_session.py
@@ -171,13 +171,13 @@ class SessionTests(utils.TestCase):
self.assertIn(body, self.logger.output)
self.assertIn("'%s'" % data, self.logger.output)
- for k, v in six.iteritems(headers):
+ for k, v in headers.items():
self.assertIn(k, self.logger.output)
self.assertIn(v, self.logger.output)
# Assert that response headers contains actual values and
# only debug logs has been masked
- for k, v in six.iteritems(security_headers):
+ for k, v in security_headers.items():
self.assertIn('%s: {SHA1}' % k, self.logger.output)
self.assertEqual(v, resp.headers[k])
self.assertNotIn(v, self.logger.output)
diff --git a/keystoneclient/tests/unit/utils.py b/keystoneclient/tests/unit/utils.py
index 378f912..6921b4b 100644
--- a/keystoneclient/tests/unit/utils.py
+++ b/keystoneclient/tests/unit/utils.py
@@ -19,7 +19,6 @@ from oslo_serialization import jsonutils
import requests
import requests_mock
from requests_mock.contrib import fixture
-import six
from six.moves.urllib import parse as urlparse
import testscenarios
import testtools
@@ -97,7 +96,7 @@ class TestCase(testtools.TestCase):
parts = urlparse.urlparse(self.requests_mock.last_request.url)
qs = urlparse.parse_qs(parts.query, keep_blank_values=True)
- for k, v in six.iteritems(kwargs):
+ for k, v in kwargs.items():
self.assertIn(k, qs)
self.assertIn(v, qs[k])
diff --git a/keystoneclient/tests/unit/v2_0/test_client.py b/keystoneclient/tests/unit/v2_0/test_client.py
index fc9bf14..cddac4d 100644
--- a/keystoneclient/tests/unit/v2_0/test_client.py
+++ b/keystoneclient/tests/unit/v2_0/test_client.py
@@ -14,7 +14,6 @@ import json
import uuid
from keystoneauth1 import fixture
-import six
from keystoneauth1 import session as auth_session
from keystoneclient.auth import token_endpoint
@@ -207,7 +206,7 @@ class KeystoneClientTest(utils.TestCase):
cl = client.Client(session=sess, **opts)
- for k, v in six.iteritems(opts):
+ for k, v in opts.items():
self.assertEqual(v, getattr(cl._adapter, k))
self.assertEqual('identity', cl._adapter.service_type)
diff --git a/keystoneclient/tests/unit/v3/test_client.py b/keystoneclient/tests/unit/v3/test_client.py
index 29a2818..feb921a 100644
--- a/keystoneclient/tests/unit/v3/test_client.py
+++ b/keystoneclient/tests/unit/v3/test_client.py
@@ -14,7 +14,6 @@ import copy
import json
import uuid
-import six
from keystoneauth1 import session as auth_session
from keystoneclient.auth import token_endpoint
@@ -257,7 +256,7 @@ class KeystoneClientTest(utils.TestCase):
cl = client.Client(session=sess, **opts)
- for k, v in six.iteritems(opts):
+ for k, v in opts.items():
self.assertEqual(v, getattr(cl._adapter, k))
self.assertEqual('identity', cl._adapter.service_type)
diff --git a/keystoneclient/tests/unit/v3/test_endpoint_filter.py b/keystoneclient/tests/unit/v3/test_endpoint_filter.py
index 2eed705..62e89cb 100644
--- a/keystoneclient/tests/unit/v3/test_endpoint_filter.py
+++ b/keystoneclient/tests/unit/v3/test_endpoint_filter.py
@@ -36,6 +36,13 @@ class EndpointTestUtils(object):
kwargs.setdefault('url', uuid.uuid4().hex)
return kwargs
+ def new_endpoint_group_ref(self, **kwargs):
+ kwargs.setdefault('id', uuid.uuid4().hex)
+ kwargs.setdefault('name', uuid.uuid4().hex)
+ kwargs.setdefault('description', uuid.uuid4().hex)
+ kwargs.setdefault('filters')
+ return kwargs
+
class EndpointFilterTests(utils.ClientTestCase, EndpointTestUtils):
"""Test project-endpoint associations (a.k.a. EndpointFilter Extension).
@@ -147,3 +154,140 @@ class EndpointFilterTests(utils.ClientTestCase, EndpointTestUtils):
project['id'] for project in projects['projects']]
actual_project_ids = [project.id for project in projects_resp]
self.assertEqual(expected_project_ids, actual_project_ids)
+
+ def test_list_projects_for_endpoint_group(self):
+ endpoint_group_id = uuid.uuid4().hex
+ projects = {'projects': [self.new_project_ref(),
+ self.new_project_ref()]}
+ self.stub_url('GET',
+ [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
+ endpoint_group_id, 'projects'],
+ json=projects,
+ status_code=200)
+
+ projects_resp = self.manager.list_projects_for_endpoint_group(
+ endpoint_group=endpoint_group_id)
+
+ expected_project_ids = [
+ project['id'] for project in projects['projects']]
+ actual_project_ids = [project.id for project in projects_resp]
+ self.assertEqual(expected_project_ids, actual_project_ids)
+
+ def test_list_projects_for_endpoint_group_value_error(self):
+ self.assertRaises(ValueError,
+ self.manager.list_projects_for_endpoint_group,
+ endpoint_group='')
+ self.assertRaises(ValueError,
+ self.manager.list_projects_for_endpoint_group,
+ endpoint_group=None)
+
+ def test_list_endpoint_groups_for_project(self):
+ project_id = uuid.uuid4().hex
+ endpoint_groups = {
+ 'endpoint_groups': [self.new_endpoint_group_ref(),
+ self.new_endpoint_group_ref()]}
+ self.stub_url('GET',
+ [self.manager.OS_EP_FILTER_EXT, 'projects',
+ project_id, 'endpoint_groups'],
+ json=endpoint_groups,
+ status_code=200)
+
+ endpoint_groups_resp = self.manager.list_endpoint_groups_for_project(
+ project=project_id)
+
+ expected_endpoint_group_ids = [
+ endpoint_group['id'] for endpoint_group
+ in endpoint_groups['endpoint_groups']
+ ]
+ actual_endpoint_group_ids = [
+ endpoint_group.id for endpoint_group in endpoint_groups_resp
+ ]
+ self.assertEqual(expected_endpoint_group_ids,
+ actual_endpoint_group_ids)
+
+ def test_list_endpoint_groups_for_project_value_error(self):
+ for value in ('', None):
+ self.assertRaises(ValueError,
+ self.manager.list_endpoint_groups_for_project,
+ project=value)
+
+ def test_add_endpoint_group_to_project(self):
+ endpoint_group_id = uuid.uuid4().hex
+ project_id = uuid.uuid4().hex
+
+ self.stub_url('PUT',
+ [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
+ endpoint_group_id, 'projects', project_id],
+ status_code=201)
+
+ self.manager.add_endpoint_group_to_project(
+ project=project_id, endpoint_group=endpoint_group_id)
+
+ def test_add_endpoint_group_to_project_value_error(self):
+ for value in ('', None):
+ self.assertRaises(ValueError,
+ self.manager.add_endpoint_group_to_project,
+ project=value,
+ endpoint_group=value)
+ self.assertRaises(ValueError,
+ self.manager.add_endpoint_group_to_project,
+ project=uuid.uuid4().hex,
+ endpoint_group=value)
+ self.assertRaises(ValueError,
+ self.manager.add_endpoint_group_to_project,
+ project=value,
+ endpoint_group=uuid.uuid4().hex)
+
+ def test_check_endpoint_group_in_project(self):
+ endpoint_group_id = uuid.uuid4().hex
+ project_id = uuid.uuid4().hex
+
+ self.stub_url('HEAD',
+ [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
+ endpoint_group_id, 'projects', project_id],
+ status_code=201)
+
+ self.manager.check_endpoint_group_in_project(
+ project=project_id, endpoint_group=endpoint_group_id)
+
+ def test_check_endpoint_group_in_project_value_error(self):
+ for value in ('', None):
+ self.assertRaises(ValueError,
+ self.manager.check_endpoint_group_in_project,
+ project=value,
+ endpoint_group=value)
+ self.assertRaises(ValueError,
+ self.manager.check_endpoint_group_in_project,
+ project=uuid.uuid4().hex,
+ endpoint_group=value)
+ self.assertRaises(ValueError,
+ self.manager.check_endpoint_group_in_project,
+ project=value,
+ endpoint_group=uuid.uuid4().hex)
+
+ def test_delete_endpoint_group_from_project(self):
+ endpoint_group_id = uuid.uuid4().hex
+ project_id = uuid.uuid4().hex
+
+ self.stub_url('DELETE',
+ [self.manager.OS_EP_FILTER_EXT, 'endpoint_groups',
+ endpoint_group_id, 'projects', project_id],
+ status_code=201)
+
+ self.manager.delete_endpoint_group_from_project(
+ project=project_id, endpoint_group=endpoint_group_id)
+
+ def test_delete_endpoint_group_from_project_value_error(self):
+ for value in ('', None):
+ self.assertRaises(ValueError,
+ self.manager.delete_endpoint_group_from_project,
+ project=value,
+ endpoint_group=value)
+ self.assertRaises(ValueError,
+ self.manager.delete_endpoint_group_from_project,
+ project=uuid.uuid4().hex,
+ endpoint_group=value)
+ self.assertRaises(ValueError,
+ self.manager.delete_endpoint_group_from_project,
+ project=value,
+ endpoint_group=uuid.uuid4().hex)
diff --git a/keystoneclient/tests/unit/v3/test_trusts.py b/keystoneclient/tests/unit/v3/test_trusts.py
index 72fb5b7..1c74ac9 100644
--- a/keystoneclient/tests/unit/v3/test_trusts.py
+++ b/keystoneclient/tests/unit/v3/test_trusts.py
@@ -64,6 +64,22 @@ class TrustTests(utils.ClientTestCase, utils.CrudTests):
req_ref['roles'] = [{'name': 'atestrole'}]
super(TrustTests, self).test_create(ref=ref, req_ref=req_ref)
+ def test_create_role_id_and_names(self):
+ ref = self.new_ref()
+ ref['trustor_user_id'] = uuid.uuid4().hex
+ ref['trustee_user_id'] = uuid.uuid4().hex
+ ref['impersonation'] = False
+ req_ref = ref.copy()
+ req_ref.pop('id')
+
+ # Note the TrustManager takes a list of role_names, and converts
+ # internally to the slightly odd list-of-dict API format, so we
+ # have to pass the expected request data to allow correct stubbing
+ ref['role_names'] = ['atestrole']
+ ref['role_ids'] = [uuid.uuid4().hex]
+ req_ref['roles'] = [{'name': 'atestrole'}, {'id': ref['role_ids'][0]}]
+ super(TrustTests, self).test_create(ref=ref, req_ref=req_ref)
+
def test_create_expires(self):
ref = self.new_ref()
ref['trustor_user_id'] = uuid.uuid4().hex
diff --git a/keystoneclient/tests/unit/v3/utils.py b/keystoneclient/tests/unit/v3/utils.py
index 2c9c86d..d9cb5a4 100644
--- a/keystoneclient/tests/unit/v3/utils.py
+++ b/keystoneclient/tests/unit/v3/utils.py
@@ -12,7 +12,6 @@
import uuid
-import six
from six.moves.urllib import parse as urlparse
from keystoneclient.tests.unit import client_fixtures
@@ -301,7 +300,7 @@ class CrudTests(object):
qs_args = self.requests_mock.last_request.qs
qs_args_expected = expected_query or filter_kwargs
- for key, value in six.iteritems(qs_args_expected):
+ for key, value in qs_args_expected.items():
self.assertIn(key, qs_args)
# The querystring value is a list. Note we convert the value to a
# string and lower, as the query string is always a string and the
diff --git a/keystoneclient/v2_0/tenants.py b/keystoneclient/v2_0/tenants.py
index d375da6..1b43990 100644
--- a/keystoneclient/v2_0/tenants.py
+++ b/keystoneclient/v2_0/tenants.py
@@ -15,7 +15,6 @@
# under the License.
from keystoneauth1 import plugin
-import six
from six.moves import urllib
from keystoneclient import base
@@ -92,7 +91,7 @@ class TenantManager(base.ManagerWithFind):
"enabled": enabled}}
# Allow Extras Passthru and ensure we don't clobber primary arguments.
- for k, v in six.iteritems(kwargs):
+ for k, v in kwargs.items():
if k not in params['tenant']:
params['tenant'][k] = v
@@ -142,7 +141,7 @@ class TenantManager(base.ManagerWithFind):
body['tenant']['description'] = description
# Allow Extras Passthru and ensure we don't clobber primary arguments.
- for k, v in six.iteritems(kwargs):
+ for k, v in kwargs.items():
if k not in body['tenant']:
body['tenant'][k] = v
diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py
index 586a74a..26d5a87 100644
--- a/keystoneclient/v3/contrib/endpoint_filter.py
+++ b/keystoneclient/v3/contrib/endpoint_filter.py
@@ -15,12 +15,18 @@
from keystoneclient import base
from keystoneclient import exceptions
from keystoneclient.i18n import _
+from keystoneclient.v3 import endpoint_groups
from keystoneclient.v3 import endpoints
from keystoneclient.v3 import projects
class EndpointFilterManager(base.Manager):
- """Manager class for manipulating project-endpoint associations."""
+ """Manager class for manipulating project-endpoint associations.
+
+ Project-endpoint associations can be with endpoints directly or via
+ endpoint groups.
+
+ """
OS_EP_FILTER_EXT = '/OS-EP-FILTER'
@@ -40,6 +46,23 @@ class EndpointFilterManager(base.Manager):
return self.OS_EP_FILTER_EXT + api_path
+ def _build_group_base_url(self, project=None, endpoint_group=None):
+ project_id = base.getid(project)
+ endpoint_group_id = base.getid(endpoint_group)
+
+ if project_id and endpoint_group_id:
+ api_path = '/endpoint_groups/%s/projects/%s' % (
+ endpoint_group_id, project_id)
+ elif project_id:
+ api_path = '/projects/%s/endpoint_groups' % (project_id)
+ elif endpoint_group_id:
+ api_path = '/endpoint_groups/%s/projects' % (endpoint_group_id)
+ else:
+ msg = _('Must specify a project, an endpoint group, or both')
+ raise exceptions.ValidationError(msg)
+
+ return self.OS_EP_FILTER_EXT + api_path
+
def add_endpoint_to_project(self, project, endpoint):
"""Create a project-endpoint association."""
if not (project and endpoint):
@@ -59,7 +82,7 @@ class EndpointFilterManager(base.Manager):
return super(EndpointFilterManager, self)._delete(url=base_url)
def check_endpoint_in_project(self, project, endpoint):
- """Check if project-endpoint association exist."""
+ """Check if project-endpoint association exists."""
if not (project and endpoint):
raise ValueError(_('project and endpoint are required'))
@@ -88,3 +111,53 @@ class EndpointFilterManager(base.Manager):
base_url,
projects.ProjectManager.collection_key,
obj_class=projects.ProjectManager.resource_class)
+
+ def add_endpoint_group_to_project(self, endpoint_group, project):
+ """Create a project-endpoint group association."""
+ if not (project and endpoint_group):
+ raise ValueError(_('project and endpoint_group are required'))
+
+ base_url = self._build_group_base_url(project=project,
+ endpoint_group=endpoint_group)
+ return super(EndpointFilterManager, self)._put(url=base_url)
+
+ def delete_endpoint_group_from_project(self, endpoint_group, project):
+ """Remove a project-endpoint group association."""
+ if not (project and endpoint_group):
+ raise ValueError(_('project and endpoint_group are required'))
+
+ base_url = self._build_group_base_url(project=project,
+ endpoint_group=endpoint_group)
+ return super(EndpointFilterManager, self)._delete(url=base_url)
+
+ def check_endpoint_group_in_project(self, endpoint_group, project):
+ """Check if project-endpoint group association exists."""
+ if not (project and endpoint_group):
+ raise ValueError(_('project and endpoint_group are required'))
+
+ base_url = self._build_group_base_url(project=project,
+ endpoint_group=endpoint_group)
+ return super(EndpointFilterManager, self)._head(url=base_url)
+
+ def list_endpoint_groups_for_project(self, project):
+ """List all endpoint groups for a given project."""
+ if not project:
+ raise ValueError(_('project is required'))
+
+ base_url = self._build_group_base_url(project=project)
+
+ return super(EndpointFilterManager, self)._list(
+ base_url,
+ 'endpoint_groups',
+ obj_class=endpoint_groups.EndpointGroupManager.resource_class)
+
+ def list_projects_for_endpoint_group(self, endpoint_group):
+ """List all projects associated with a given endpoint group."""
+ if not endpoint_group:
+ raise ValueError(_('endpoint_group is required'))
+
+ base_url = self._build_group_base_url(endpoint_group=endpoint_group)
+ return super(EndpointFilterManager, self)._list(
+ base_url,
+ projects.ProjectManager.collection_key,
+ obj_class=projects.ProjectManager.resource_class)
diff --git a/keystoneclient/v3/contrib/trusts.py b/keystoneclient/v3/contrib/trusts.py
index e236188..a8ef579 100644
--- a/keystoneclient/v3/contrib/trusts.py
+++ b/keystoneclient/v3/contrib/trusts.py
@@ -39,13 +39,14 @@ class TrustManager(base.CrudManager):
base_url = '/OS-TRUST'
def create(self, trustee_user, trustor_user, role_names=None,
- project=None, impersonation=False, expires_at=None,
- remaining_uses=None, **kwargs):
+ role_ids=None, project=None, impersonation=False,
+ expires_at=None, remaining_uses=None, **kwargs):
"""Create a Trust.
:param string trustee_user: user who is capable of consuming the trust
:param string trustor_user: user who's authorization is being delegated
:param string role_names: subset of trustor's roles to be granted
+ :param string role_ids: subset of trustor's roles to be granted
:param string project: project which the trustor is delegating
:param boolean impersonation: enable explicit impersonation
:param datetime.datetime expires_at: expiry time
@@ -55,9 +56,13 @@ class TrustManager(base.CrudManager):
"""
# Convert role_names list into list-of-dict API format
+ roles = []
if role_names:
- roles = [{'name': n} for n in role_names]
- else:
+ roles.extend([{'name': n} for n in role_names])
+ if role_ids:
+ roles.extend([{'id': i} for i in role_ids])
+
+ if not roles:
roles = None
# Convert datetime.datetime expires_at to iso format string
diff --git a/releasenotes/notes/bug-1641674-4862454115265e76.yaml b/releasenotes/notes/bug-1641674-4862454115265e76.yaml
new file mode 100644
index 0000000..19c8ecc
--- /dev/null
+++ b/releasenotes/notes/bug-1641674-4862454115265e76.yaml
@@ -0,0 +1,8 @@
+---
+prelude: >
+ Keystone Client now supports endpoint group filtering.
+features:
+ - |
+ Support for handling the relationship between endpoint groups and projects
+ has been added. It is now possible to list, associate, check and
+ disassociate endpoint groups that have access to a project.
diff --git a/keystoneclient/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml b/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml
index 61b9d17..61b9d17 100644
--- a/keystoneclient/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml
+++ b/releasenotes/notes/removed-generic-client-ff505b2b01bc9302.yaml
diff --git a/requirements.txt b/requirements.txt
index 537de0f..8f6e64e 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>=2.0.0 # Apache-2.0
+pbr!=2.1.0,>=2.0.0 # Apache-2.0
debtcollector>=1.2.0 # Apache-2.0
-keystoneauth1>=2.18.0 # Apache-2.0
-oslo.config>=3.22.0 # Apache-2.0
-oslo.i18n>=2.1.0 # Apache-2.0
+keystoneauth1>=2.21.0 # Apache-2.0
+oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
+oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
positional>=1.1.1 # Apache-2.0
-requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
+requests>=2.14.2 # Apache-2.0
six>=1.9.0 # MIT
stevedore>=1.20.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 9e913a7..e1e9851 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -40,9 +40,9 @@ keystoneclient.auth.plugin =
source-dir = doc/source
build-dir = doc/build
all_files = 1
+warning-is-error = 1
[pbr]
-warnerrors = True
autodoc_tree_index_modules = True
autodoc_tree_excludes =
setup.py
diff --git a/test-requirements.txt b/test-requirements.txt
index e5dcda8..a358611 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,7 +5,7 @@
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!=3.7.0,>=2.3 # BSD
@@ -13,9 +13,9 @@ 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
+reno!=2.3.1,>=1.8.0 # Apache-2.0
requests-mock>=1.1 # Apache-2.0
-sphinx>=1.5.1 # BSD
+sphinx>=1.6.2 # BSD
tempest>=14.0.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testresources>=0.2.4 # Apache-2.0/BSD