Merge "Implement pagination for users and groups"
This commit is contained in:
@@ -40,6 +40,10 @@ Parameters
|
|||||||
|
|
||||||
- name: group_name_query
|
- name: group_name_query
|
||||||
- domain_id: domain_id_query
|
- domain_id: domain_id_query
|
||||||
|
- limit: limit_query
|
||||||
|
- marker: marker_query
|
||||||
|
- sort_key: sort_key
|
||||||
|
- sort_dir: sort_dir
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
@@ -464,6 +464,19 @@ service_type_query:
|
|||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
sort_dir:
|
||||||
|
description: |
|
||||||
|
Sort direction. A valid value is asc (ascending) or desc
|
||||||
|
(descending).
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
sort_key:
|
||||||
|
description: |
|
||||||
|
Sorts resources by attribute
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
subtree_as_ids:
|
subtree_as_ids:
|
||||||
description: |
|
description: |
|
||||||
The entire child hierarchy will be included as nested dictionaries
|
The entire child hierarchy will be included as nested dictionaries
|
||||||
|
@@ -41,6 +41,10 @@ Parameters
|
|||||||
- password_expires_at: password_expires_at_query
|
- password_expires_at: password_expires_at_query
|
||||||
- protocol_id: protocol_id_query
|
- protocol_id: protocol_id_query
|
||||||
- unique_id: unique_id_query
|
- unique_id: unique_id_query
|
||||||
|
- limit: limit_query
|
||||||
|
- marker: marker_query
|
||||||
|
- sort_key: sort_key
|
||||||
|
- sort_dir: sort_dir
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
@@ -118,3 +118,14 @@ id_string: dict[str, Any] = {
|
|||||||
"maxLength": 64,
|
"maxLength": 64,
|
||||||
"pattern": r"^[a-zA-Z0-9-]+$",
|
"pattern": r"^[a-zA-Z0-9-]+$",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort_key: dict[str, Any] = {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Sorts resources by attribute.",
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_dir: dict[str, Any] = {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Sort direction. A valid value is asc (ascending) or desc (descending).",
|
||||||
|
"enum": ["asc", "desc"],
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from oslo_db import api as oslo_db_api
|
from oslo_db import api as oslo_db_api
|
||||||
|
from oslo_db.sqlalchemy import utils as sqlalchemyutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
@@ -186,8 +187,39 @@ class Identity(base.IdentityDriverBase):
|
|||||||
query, hints = self._create_password_expires_query(
|
query, hints = self._create_password_expires_query(
|
||||||
session, query, hints
|
session, query, hints
|
||||||
)
|
)
|
||||||
user_refs = sql.filter_limit_query(model.User, query, hints)
|
query = sql.filter_query(model.User, query, hints)
|
||||||
return [base.filter_user(x.to_dict()) for x in user_refs]
|
marker_row = None
|
||||||
|
if hints.marker is not None:
|
||||||
|
marker_row = (
|
||||||
|
session.query(model.User)
|
||||||
|
.filter_by(id=hints.marker)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if not marker_row:
|
||||||
|
raise exception.MarkerNotFound(marker=hints.marker)
|
||||||
|
|
||||||
|
user_refs = sqlalchemyutils.paginate_query(
|
||||||
|
query,
|
||||||
|
model.User,
|
||||||
|
hints.get_limit_or_max(),
|
||||||
|
["id"],
|
||||||
|
marker=marker_row,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = user_refs.all()
|
||||||
|
if hints.limit:
|
||||||
|
# the `common.manager.response_truncated` decorator expects
|
||||||
|
# that when driver truncates results it should also raise
|
||||||
|
# 'truncated' flag to indicate that. Since we do not really
|
||||||
|
# know whether there are more records once we applied filters
|
||||||
|
# we can only "assume" and set the flag when count of records
|
||||||
|
# is equal to what we have limited to.
|
||||||
|
# NOTE(gtema) get rid of that once proper pagination is
|
||||||
|
# enabled for all resources
|
||||||
|
if len(data) >= hints.limit["limit"]:
|
||||||
|
hints.limit["truncated"] = True
|
||||||
|
|
||||||
|
return [base.filter_user(x.to_dict()) for x in data]
|
||||||
|
|
||||||
def unset_default_project_id(self, project_id):
|
def unset_default_project_id(self, project_id):
|
||||||
with sql.session_for_write() as session:
|
with sql.session_for_write() as session:
|
||||||
@@ -441,8 +473,41 @@ class Identity(base.IdentityDriverBase):
|
|||||||
def list_groups(self, hints):
|
def list_groups(self, hints):
|
||||||
with sql.session_for_read() as session:
|
with sql.session_for_read() as session:
|
||||||
query = session.query(model.Group)
|
query = session.query(model.Group)
|
||||||
refs = sql.filter_limit_query(model.Group, query, hints)
|
|
||||||
return [ref.to_dict() for ref in refs]
|
query = sql.filter_query(model.Group, query, hints)
|
||||||
|
|
||||||
|
marker_row = None
|
||||||
|
if hints.marker is not None:
|
||||||
|
marker_row = (
|
||||||
|
session.query(model.Group)
|
||||||
|
.filter_by(id=hints.marker)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if not marker_row:
|
||||||
|
raise exception.MarkerNotFound(marker=hints.marker)
|
||||||
|
|
||||||
|
group_refs = sqlalchemyutils.paginate_query(
|
||||||
|
query,
|
||||||
|
model.Group,
|
||||||
|
hints.get_limit_or_max(),
|
||||||
|
["id"],
|
||||||
|
marker=marker_row,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = group_refs.all()
|
||||||
|
if hints.limit:
|
||||||
|
# the `common.manager.response_truncated` decorator expects
|
||||||
|
# that when driver truncates results it should also raise
|
||||||
|
# 'truncated' flag to indicate that. Since we do not really
|
||||||
|
# know whether there are more records once we applied filters
|
||||||
|
# we can only "assume" and set the flag when count of records
|
||||||
|
# is equal to what we have limited to.
|
||||||
|
# NOTE(gtema) get rid of that once proper pagination is
|
||||||
|
# enabled for all resources
|
||||||
|
if len(data) >= hints.limit["limit"]:
|
||||||
|
hints.limit["truncated"] = True
|
||||||
|
|
||||||
|
return [ref.to_dict() for ref in data]
|
||||||
|
|
||||||
def _get_group(self, session, group_id):
|
def _get_group(self, session, group_id):
|
||||||
ref = session.get(model.Group, group_id)
|
ref = session.get(model.Group, group_id)
|
||||||
|
@@ -73,6 +73,13 @@ user_index_request_query: dict[str, Any] = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Filters the response by a unique ID.",
|
"description": "Filters the response by a unique ID.",
|
||||||
},
|
},
|
||||||
|
"marker": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ID of the last fetched entry",
|
||||||
|
},
|
||||||
|
"limit": {"type": ["integer", "string"]},
|
||||||
|
"sort_key": parameter_types.sort_key,
|
||||||
|
"sort_dir": parameter_types.sort_dir,
|
||||||
},
|
},
|
||||||
"additionalProperties": True,
|
"additionalProperties": True,
|
||||||
}
|
}
|
||||||
@@ -191,6 +198,13 @@ group_index_request_query: dict[str, Any] = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"domain_id": parameter_types.domain_id,
|
"domain_id": parameter_types.domain_id,
|
||||||
"name": parameter_types.name,
|
"name": parameter_types.name,
|
||||||
|
"marker": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ID of the last fetched entry",
|
||||||
|
},
|
||||||
|
"limit": {"type": ["integer", "string"]},
|
||||||
|
"sort_key": parameter_types.sort_key,
|
||||||
|
"sort_dir": parameter_types.sort_dir,
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
}
|
}
|
||||||
|
@@ -1666,11 +1666,14 @@ class PaginationTestCaseBase(RestfulTestCase):
|
|||||||
"""Base test for the resource pagination."""
|
"""Base test for the resource pagination."""
|
||||||
|
|
||||||
resource_name: ty.Optional[str] = None
|
resource_name: ty.Optional[str] = None
|
||||||
|
config_group: ty.Optional[str] = None
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
if not self.resource_name:
|
if not self.resource_name:
|
||||||
self.skipTest("Not testing the base")
|
self.skipTest("Not testing the base")
|
||||||
|
if not self.config_group:
|
||||||
|
self.skipTest("Not testing the base")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _create_resources(self, count: int):
|
def _create_resources(self, count: int):
|
||||||
@@ -1728,7 +1731,7 @@ class PaginationTestCaseBase(RestfulTestCase):
|
|||||||
res_list = response.json_body[f"{self.resource_name}s"]
|
res_list = response.json_body[f"{self.resource_name}s"]
|
||||||
self.assertGreaterEqual(
|
self.assertGreaterEqual(
|
||||||
len(response.json_body[f"{self.resource_name}s"]),
|
len(response.json_body[f"{self.resource_name}s"]),
|
||||||
count_resources,
|
2,
|
||||||
"Requested limit higher then default wins",
|
"Requested limit higher then default wins",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1770,7 +1773,7 @@ class PaginationTestCaseBase(RestfulTestCase):
|
|||||||
"Next page link contains corrected limit and marker",
|
"Next page link contains corrected limit and marker",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.config_fixture.config(group="resource", list_limit=3)
|
self.config_fixture.config(group=self.config_group, list_limit=3)
|
||||||
|
|
||||||
response = self.get(f'/{self.resource_name}s')
|
response = self.get(f'/{self.resource_name}s')
|
||||||
res_list = response.json_body[f"{self.resource_name}s"]
|
res_list = response.json_body[f"{self.resource_name}s"]
|
||||||
@@ -1821,7 +1824,9 @@ class PaginationTestCaseBase(RestfulTestCase):
|
|||||||
current_count = len(response.json_body[f"{self.resource_name}s"])
|
current_count = len(response.json_body[f"{self.resource_name}s"])
|
||||||
|
|
||||||
# Set pagination default at 5
|
# Set pagination default at 5
|
||||||
self.config_fixture.config(group="resource", list_limit=page_size)
|
self.config_fixture.config(
|
||||||
|
group=self.config_group, list_limit=page_size
|
||||||
|
)
|
||||||
|
|
||||||
(found_resources, pages) = self._consume_paginated_list()
|
(found_resources, pages) = self._consume_paginated_list()
|
||||||
self.assertGreaterEqual(
|
self.assertGreaterEqual(
|
||||||
|
@@ -33,6 +33,14 @@ CONF = keystone.conf.CONF
|
|||||||
PROVIDERS = provider_api.ProviderAPIs
|
PROVIDERS = provider_api.ProviderAPIs
|
||||||
|
|
||||||
|
|
||||||
|
def _get_id_list_from_ref_list(ref_list):
|
||||||
|
"""Get entity IDs from the response"""
|
||||||
|
result_list = []
|
||||||
|
for x in ref_list:
|
||||||
|
result_list.append(x['id'])
|
||||||
|
return result_list
|
||||||
|
|
||||||
|
|
||||||
class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
||||||
"""Test filter enforcement on the v3 Identity API."""
|
"""Test filter enforcement on the v3 Identity API."""
|
||||||
|
|
||||||
@@ -96,12 +104,6 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
user_id=self.user1['id'], password=self.user1['password']
|
user_id=self.user1['id'], password=self.user1['password']
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_id_list_from_ref_list(self, ref_list):
|
|
||||||
result_list = []
|
|
||||||
for x in ref_list:
|
|
||||||
result_list.append(x['id'])
|
|
||||||
return result_list
|
|
||||||
|
|
||||||
def _set_policy(self, new_policy):
|
def _set_policy(self, new_policy):
|
||||||
with open(self.tmpfilename, "w") as policyfile:
|
with open(self.tmpfilename, "w") as policyfile:
|
||||||
policyfile.write(jsonutils.dumps(new_policy))
|
policyfile.write(jsonutils.dumps(new_policy))
|
||||||
@@ -120,7 +122,7 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
url_by_name = '/users?domain_id={}'.format(self.domainB['id'])
|
url_by_name = '/users?domain_id={}'.format(self.domainB['id'])
|
||||||
r = self.get(url_by_name, auth=self.auth)
|
r = self.get(url_by_name, auth=self.auth)
|
||||||
# We should get back two users, those in DomainB
|
# We should get back two users, those in DomainB
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('users'))
|
id_list = _get_id_list_from_ref_list(r.result.get('users'))
|
||||||
self.assertIn(self.user2['id'], id_list)
|
self.assertIn(self.user2['id'], id_list)
|
||||||
self.assertIn(self.user3['id'], id_list)
|
self.assertIn(self.user3['id'], id_list)
|
||||||
|
|
||||||
@@ -140,28 +142,28 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
new_policy = {"identity:list_domains": []}
|
new_policy = {"identity:list_domains": []}
|
||||||
self._set_policy(new_policy)
|
self._set_policy(new_policy)
|
||||||
r = self.get('/domains?enabled=0', auth=self.auth)
|
r = self.get('/domains?enabled=0', auth=self.auth)
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('domains'))
|
id_list = _get_id_list_from_ref_list(r.result.get('domains'))
|
||||||
self.assertEqual(1, len(id_list))
|
self.assertEqual(1, len(id_list))
|
||||||
self.assertIn(self.domainC['id'], id_list)
|
self.assertIn(self.domainC['id'], id_list)
|
||||||
|
|
||||||
# Try a few ways of specifying 'false'
|
# Try a few ways of specifying 'false'
|
||||||
for val in ('0', 'false', 'False', 'FALSE', 'n', 'no', 'off'):
|
for val in ('0', 'false', 'False', 'FALSE', 'n', 'no', 'off'):
|
||||||
r = self.get(f'/domains?enabled={val}', auth=self.auth)
|
r = self.get(f'/domains?enabled={val}', auth=self.auth)
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('domains'))
|
id_list = _get_id_list_from_ref_list(r.result.get('domains'))
|
||||||
self.assertEqual([self.domainC['id']], id_list)
|
self.assertEqual([self.domainC['id']], id_list)
|
||||||
|
|
||||||
# Now try a few ways of specifying 'true' when we should get back
|
# Now try a few ways of specifying 'true' when we should get back
|
||||||
# the other two domains, plus the default domain
|
# the other two domains, plus the default domain
|
||||||
for val in ('1', 'true', 'True', 'TRUE', 'y', 'yes', 'on'):
|
for val in ('1', 'true', 'True', 'TRUE', 'y', 'yes', 'on'):
|
||||||
r = self.get(f'/domains?enabled={val}', auth=self.auth)
|
r = self.get(f'/domains?enabled={val}', auth=self.auth)
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('domains'))
|
id_list = _get_id_list_from_ref_list(r.result.get('domains'))
|
||||||
self.assertEqual(3, len(id_list))
|
self.assertEqual(3, len(id_list))
|
||||||
self.assertIn(self.domainA['id'], id_list)
|
self.assertIn(self.domainA['id'], id_list)
|
||||||
self.assertIn(self.domainB['id'], id_list)
|
self.assertIn(self.domainB['id'], id_list)
|
||||||
self.assertIn(CONF.identity.default_domain_id, id_list)
|
self.assertIn(CONF.identity.default_domain_id, id_list)
|
||||||
|
|
||||||
r = self.get('/domains?enabled', auth=self.auth)
|
r = self.get('/domains?enabled', auth=self.auth)
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('domains'))
|
id_list = _get_id_list_from_ref_list(r.result.get('domains'))
|
||||||
self.assertEqual(3, len(id_list))
|
self.assertEqual(3, len(id_list))
|
||||||
self.assertIn(self.domainA['id'], id_list)
|
self.assertIn(self.domainA['id'], id_list)
|
||||||
self.assertIn(self.domainB['id'], id_list)
|
self.assertIn(self.domainB['id'], id_list)
|
||||||
@@ -182,7 +184,7 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
|
|
||||||
my_url = '/domains?enabled&name={}'.format(self.domainA['name'])
|
my_url = '/domains?enabled&name={}'.format(self.domainA['name'])
|
||||||
r = self.get(my_url, auth=self.auth)
|
r = self.get(my_url, auth=self.auth)
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('domains'))
|
id_list = _get_id_list_from_ref_list(r.result.get('domains'))
|
||||||
self.assertEqual(1, len(id_list))
|
self.assertEqual(1, len(id_list))
|
||||||
self.assertIn(self.domainA['id'], id_list)
|
self.assertIn(self.domainA['id'], id_list)
|
||||||
self.assertIs(True, r.result.get('domains')[0]['enabled'])
|
self.assertIs(True, r.result.get('domains')[0]['enabled'])
|
||||||
@@ -202,7 +204,7 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
|
|
||||||
my_url = '/domains?enableds=0&name={}'.format(self.domainA['name'])
|
my_url = '/domains?enableds=0&name={}'.format(self.domainA['name'])
|
||||||
r = self.get(my_url, auth=self.auth)
|
r = self.get(my_url, auth=self.auth)
|
||||||
id_list = self._get_id_list_from_ref_list(r.result.get('domains'))
|
id_list = _get_id_list_from_ref_list(r.result.get('domains'))
|
||||||
|
|
||||||
# domainA is returned and it is enabled, since enableds=0 is not the
|
# domainA is returned and it is enabled, since enableds=0 is not the
|
||||||
# same as enabled=0
|
# same as enabled=0
|
||||||
@@ -238,7 +240,7 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
r = self.get(url_by_name, auth=self.auth)
|
r = self.get(url_by_name, auth=self.auth)
|
||||||
|
|
||||||
self.assertEqual(1, len(r.result.get('users')))
|
self.assertEqual(1, len(r.result.get('users')))
|
||||||
self.assertEqual(user['id'], r.result.get('users')[0]['id'])
|
self.assertIn(user['id'], [x["id"] for x in r.result.get('users')])
|
||||||
|
|
||||||
def test_inexact_filters(self):
|
def test_inexact_filters(self):
|
||||||
# Create 20 users
|
# Create 20 users
|
||||||
@@ -297,13 +299,19 @@ class IdentityTestFilteredCase(filtering.FilterTests, test_v3.RestfulTestCase):
|
|||||||
url_by_name = '/users?name__endswith=of'
|
url_by_name = '/users?name__endswith=of'
|
||||||
r = self.get(url_by_name, auth=self.auth)
|
r = self.get(url_by_name, auth=self.auth)
|
||||||
self.assertEqual(1, len(r.result.get('users')))
|
self.assertEqual(1, len(r.result.get('users')))
|
||||||
self.assertEqual(user_list[7]['id'], r.result.get('users')[0]['id'])
|
self.assertIn(
|
||||||
|
user_list[7]['id'], [x["id"] for x in r.result.get('users')]
|
||||||
|
)
|
||||||
|
|
||||||
url_by_name = '/users?name__iendswith=OF'
|
url_by_name = '/users?name__iendswith=OF'
|
||||||
r = self.get(url_by_name, auth=self.auth)
|
r = self.get(url_by_name, auth=self.auth)
|
||||||
self.assertEqual(2, len(r.result.get('users')))
|
self.assertEqual(2, len(r.result.get('users')))
|
||||||
self.assertEqual(user_list[7]['id'], r.result.get('users')[0]['id'])
|
self.assertIn(
|
||||||
self.assertEqual(user_list[10]['id'], r.result.get('users')[1]['id'])
|
user_list[7]['id'], [x["id"] for x in r.result.get('users')]
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
user_list[10]['id'], [x["id"] for x in r.result.get('users')]
|
||||||
|
)
|
||||||
|
|
||||||
self._delete_test_data('user', user_list)
|
self._delete_test_data('user', user_list)
|
||||||
|
|
||||||
@@ -476,7 +484,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'eq',
|
'eq',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_at_url).result.get('users')
|
resp_users = self.get(expire_at_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
self.assertIn(self.user2['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
expire_at_url = self._list_users_by_password_expires_at(
|
expire_at_url = self._list_users_by_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -484,9 +492,10 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
),
|
),
|
||||||
'neq',
|
'neq',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_at_url).result.get('users')
|
resp_users = self.get(expire_at_url).result.get("users")
|
||||||
self.assertEqual(self.user['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user3['id'], resp_users[1]['id'])
|
self.assertIn(self.user['id'], id_list)
|
||||||
|
self.assertIn(self.user3['id'], id_list)
|
||||||
|
|
||||||
def test_list_users_by_password_expires_before(self):
|
def test_list_users_by_password_expires_before(self):
|
||||||
"""Ensure users can be filtered on lt and lte.
|
"""Ensure users can be filtered on lt and lte.
|
||||||
@@ -502,8 +511,9 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'lt',
|
'lt',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_before_url).result.get('users')
|
resp_users = self.get(expire_before_url).result.get('users')
|
||||||
self.assertEqual(self.user['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user2['id'], resp_users[1]['id'])
|
self.assertIn(self.user['id'], id_list)
|
||||||
|
self.assertIn(self.user2['id'], id_list)
|
||||||
|
|
||||||
expire_before_url = self._list_users_by_password_expires_at(
|
expire_before_url = self._list_users_by_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -512,8 +522,9 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'lte',
|
'lte',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_before_url).result.get('users')
|
resp_users = self.get(expire_before_url).result.get('users')
|
||||||
self.assertEqual(self.user['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user2['id'], resp_users[1]['id'])
|
self.assertIn(self.user['id'], id_list)
|
||||||
|
self.assertIn(self.user2['id'], id_list)
|
||||||
|
|
||||||
def test_list_users_by_password_expires_after(self):
|
def test_list_users_by_password_expires_after(self):
|
||||||
"""Ensure users can be filtered on gt and gte.
|
"""Ensure users can be filtered on gt and gte.
|
||||||
@@ -529,7 +540,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'gt',
|
'gt',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_after_url).result.get('users')
|
resp_users = self.get(expire_after_url).result.get('users')
|
||||||
self.assertEqual(self.user3['id'], resp_users[0]['id'])
|
self.assertIn(self.user3['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
expire_after_url = self._list_users_by_password_expires_at(
|
expire_after_url = self._list_users_by_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -538,8 +549,9 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'gte',
|
'gte',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_after_url).result.get('users')
|
resp_users = self.get(expire_after_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user3['id'], resp_users[1]['id'])
|
self.assertIn(self.user2['id'], id_list)
|
||||||
|
self.assertIn(self.user3['id'], id_list)
|
||||||
|
|
||||||
def test_list_users_by_password_expires_interval(self):
|
def test_list_users_by_password_expires_interval(self):
|
||||||
"""Ensure users can be filtered on time intervals.
|
"""Ensure users can be filtered on time intervals.
|
||||||
@@ -562,7 +574,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'gt',
|
'gt',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_interval_url).result.get('users')
|
resp_users = self.get(expire_interval_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
self.assertIn(self.user2['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
expire_interval_url = self._list_users_by_multiple_password_expires_at(
|
expire_interval_url = self._list_users_by_multiple_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -575,7 +587,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'lte',
|
'lte',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_interval_url).result.get('users')
|
resp_users = self.get(expire_interval_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
self.assertIn(self.user2['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
def test_list_users_by_password_expires_with_bad_operator_fails(self):
|
def test_list_users_by_password_expires_with_bad_operator_fails(self):
|
||||||
"""Ensure an invalid operator returns a Bad Request.
|
"""Ensure an invalid operator returns a Bad Request.
|
||||||
@@ -675,7 +687,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'eq',
|
'eq',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_at_url).result.get('users')
|
resp_users = self.get(expire_at_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
self.assertIn(self.user2['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
expire_at_url = self._list_users_in_group_by_password_expires_at(
|
expire_at_url = self._list_users_in_group_by_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -684,7 +696,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'neq',
|
'neq',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_at_url).result.get('users')
|
resp_users = self.get(expire_at_url).result.get('users')
|
||||||
self.assertEqual(self.user3['id'], resp_users[0]['id'])
|
self.assertIn(self.user3['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
def test_list_users_in_group_by_password_expires_before(self):
|
def test_list_users_in_group_by_password_expires_before(self):
|
||||||
"""Ensure users in a group can be filtered on with lt and lte.
|
"""Ensure users in a group can be filtered on with lt and lte.
|
||||||
@@ -700,7 +712,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'lt',
|
'lt',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_before_url).result.get('users')
|
resp_users = self.get(expire_before_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
self.assertIn(self.user2['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
expire_before_url = self._list_users_in_group_by_password_expires_at(
|
expire_before_url = self._list_users_in_group_by_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -709,7 +721,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'lte',
|
'lte',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_before_url).result.get('users')
|
resp_users = self.get(expire_before_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
self.assertIn(self.user2['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
def test_list_users_in_group_by_password_expires_after(self):
|
def test_list_users_in_group_by_password_expires_after(self):
|
||||||
"""Ensure users in a group can be filtered on with gt and gte.
|
"""Ensure users in a group can be filtered on with gt and gte.
|
||||||
@@ -725,7 +737,7 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'gt',
|
'gt',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_after_url).result.get('users')
|
resp_users = self.get(expire_after_url).result.get('users')
|
||||||
self.assertEqual(self.user3['id'], resp_users[0]['id'])
|
self.assertIn(self.user3['id'], [x["id"] for x in resp_users])
|
||||||
|
|
||||||
expire_after_url = self._list_users_in_group_by_password_expires_at(
|
expire_after_url = self._list_users_in_group_by_password_expires_at(
|
||||||
self._format_timestamp(
|
self._format_timestamp(
|
||||||
@@ -734,8 +746,9 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
'gte',
|
'gte',
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_after_url).result.get('users')
|
resp_users = self.get(expire_after_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user3['id'], resp_users[1]['id'])
|
self.assertIn(self.user2['id'], id_list)
|
||||||
|
self.assertIn(self.user3['id'], id_list)
|
||||||
|
|
||||||
def test_list_users_in_group_by_password_expires_interval(self):
|
def test_list_users_in_group_by_password_expires_interval(self):
|
||||||
"""Ensure users in a group can be filtered on time intervals.
|
"""Ensure users in a group can be filtered on time intervals.
|
||||||
@@ -760,8 +773,9 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_interval_url).result.get('users')
|
resp_users = self.get(expire_interval_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user3['id'], resp_users[1]['id'])
|
self.assertIn(self.user2['id'], id_list)
|
||||||
|
self.assertIn(self.user3['id'], id_list)
|
||||||
|
|
||||||
expire_interval_url = (
|
expire_interval_url = (
|
||||||
self._list_users_in_group_by_multiple_password_expires_at(
|
self._list_users_in_group_by_multiple_password_expires_at(
|
||||||
@@ -776,8 +790,9 @@ class IdentityPasswordExpiryFilteredTestCase(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
resp_users = self.get(expire_interval_url).result.get('users')
|
resp_users = self.get(expire_interval_url).result.get('users')
|
||||||
self.assertEqual(self.user2['id'], resp_users[0]['id'])
|
id_list = _get_id_list_from_ref_list(resp_users)
|
||||||
self.assertEqual(self.user3['id'], resp_users[1]['id'])
|
self.assertIn(self.user2['id'], id_list)
|
||||||
|
self.assertIn(self.user3['id'], id_list)
|
||||||
|
|
||||||
def test_list_users_in_group_by_password_expires_bad_operator_fails(self):
|
def test_list_users_in_group_by_password_expires_bad_operator_fails(self):
|
||||||
"""Ensure an invalid operator returns a Bad Request.
|
"""Ensure an invalid operator returns a Bad Request.
|
||||||
|
@@ -2054,6 +2054,7 @@ class DomainPaginationTestCase(test_v3.PaginationTestCaseBase):
|
|||||||
"""Test domain list pagination."""
|
"""Test domain list pagination."""
|
||||||
|
|
||||||
resource_name: str = "domain"
|
resource_name: str = "domain"
|
||||||
|
config_group: str = "resource"
|
||||||
|
|
||||||
def _create_resources(self, count: int):
|
def _create_resources(self, count: int):
|
||||||
for x in range(count):
|
for x in range(count):
|
||||||
@@ -2065,8 +2066,41 @@ class ProjectPaginationTestCase(test_v3.PaginationTestCaseBase):
|
|||||||
"""Test project list pagination."""
|
"""Test project list pagination."""
|
||||||
|
|
||||||
resource_name: str = "project"
|
resource_name: str = "project"
|
||||||
|
config_group: str = "resource"
|
||||||
|
|
||||||
def _create_resources(self, count: int):
|
def _create_resources(self, count: int):
|
||||||
for x in range(count):
|
for x in range(count):
|
||||||
res = {"project": unit.new_project_ref()}
|
res = {"project": unit.new_project_ref()}
|
||||||
response = self.post("/projects", body=res)
|
response = self.post("/projects", body=res)
|
||||||
|
|
||||||
|
|
||||||
|
class UserPaginationTestCase(test_v3.PaginationTestCaseBase):
|
||||||
|
"""Test user list pagination."""
|
||||||
|
|
||||||
|
resource_name: str = "user"
|
||||||
|
config_group: str = "identity"
|
||||||
|
|
||||||
|
def _create_resources(self, count: int):
|
||||||
|
for x in range(count):
|
||||||
|
res = {
|
||||||
|
"user": unit.new_user_ref(
|
||||||
|
domain_id=CONF.identity.default_domain_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response = self.post("/users", body=res)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPaginationTestCase(test_v3.PaginationTestCaseBase):
|
||||||
|
"""Test group list pagination."""
|
||||||
|
|
||||||
|
resource_name: str = "group"
|
||||||
|
config_group: str = "identity"
|
||||||
|
|
||||||
|
def _create_resources(self, count: int):
|
||||||
|
for x in range(count):
|
||||||
|
res = {
|
||||||
|
"group": unit.new_group_ref(
|
||||||
|
domain_id=CONF.identity.default_domain_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response = self.post("/groups", body=res)
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
User and group listing supports pagination. Query parameters `limit`
|
||||||
|
and `marker` are added and work as described in `API-SIG doc
|
||||||
|
<https://specs.openstack.org/openstack/api-sig/guidelines/pagination_filter_sort.html>`_
|
Reference in New Issue
Block a user