Add support for project-specific limits

Thsi commit adds client support for managing limits in keystone.

bp unified-limits

Change-Id: I33251dbd4d3bfaf178ca86a2f5d564ac94879dd2
This commit is contained in:
Lance Bragstad
2018-06-11 19:19:03 +00:00
parent 0b9a7b05c0
commit 650716d0dd
4 changed files with 237 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
# 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 limits
class LimitTests(utils.ClientTestCase, utils.CrudTests):
def setUp(self):
super(LimitTests, self).setUp()
self.key = 'limit'
self.collection_key = 'limits'
self.model = limits.Limit
self.manager = self.client.limits
def new_ref(self, **kwargs):
ref = {
'id': uuid.uuid4().hex,
'project_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'resource_name': uuid.uuid4().hex,
'resource_limit': 15,
'description': uuid.uuid4().hex
}
ref.update(kwargs)
return ref
def test_create(self):
# This test overrides the generic test case provided by the CrudTests
# class because the limits API supports creating multiple limits in a
# single POST request. As a result, it returns the limits as a list of
# all the created limits from the request. This is different from what
# the base test_create() method assumes about keystone's API. The
# changes here override the base test to closely model how the actual
# limit API behaves.
ref = self.new_ref()
manager_ref = ref.copy()
manager_ref.pop('id')
req_ref = [manager_ref.copy()]
self.stub_entity('POST', entity=req_ref, status_code=201)
returned = self.manager.create(**utils.parameterize(manager_ref))
self.assertIsInstance(returned, self.model)
expected_limit = req_ref.pop()
for attr in expected_limit:
self.assertEqual(
getattr(returned, attr),
expected_limit[attr],
'Expected different %s' % attr)
self.assertEntityRequestBodyIs([expected_limit])
def test_list_filter_by_service(self):
service_id = uuid.uuid4().hex
expected_query = {'service_id': service_id}
self.test_list(expected_query=expected_query, service=service_id)
def test_list_filtered_by_resource_name(self):
resource_name = uuid.uuid4().hex
self.test_list(resource_name=resource_name)
def test_list_filtered_by_region(self):
region_id = uuid.uuid4().hex
expected_query = {'region_id': region_id}
self.test_list(expected_query=expected_query, region=region_id)

View File

@@ -37,6 +37,7 @@ from keystoneclient.v3 import ec2
from keystoneclient.v3 import endpoint_groups from keystoneclient.v3 import endpoint_groups
from keystoneclient.v3 import endpoints from keystoneclient.v3 import endpoints
from keystoneclient.v3 import groups from keystoneclient.v3 import groups
from keystoneclient.v3 import limits
from keystoneclient.v3 import policies from keystoneclient.v3 import policies
from keystoneclient.v3 import projects from keystoneclient.v3 import projects
from keystoneclient.v3 import regions from keystoneclient.v3 import regions
@@ -159,6 +160,10 @@ class Client(httpclient.HTTPClient):
:py:class:`keystoneclient.v3.groups.GroupManager` :py:class:`keystoneclient.v3.groups.GroupManager`
.. py:attribute:: limits
:py:class:`keystoneclient.v3.limits.LimitManager`
.. py:attribute:: oauth1 .. py:attribute:: oauth1
:py:class:`keystoneclient.v3.contrib.oauth1.core.OAuthManager` :py:class:`keystoneclient.v3.contrib.oauth1.core.OAuthManager`
@@ -235,6 +240,7 @@ class Client(httpclient.HTTPClient):
self.domains = domains.DomainManager(self._adapter) self.domains = domains.DomainManager(self._adapter)
self.federation = federation.FederationManager(self._adapter) self.federation = federation.FederationManager(self._adapter)
self.groups = groups.GroupManager(self._adapter) self.groups = groups.GroupManager(self._adapter)
self.limits = limits.LimitManager(self._adapter)
self.oauth1 = oauth1.create_oauth_manager(self._adapter) self.oauth1 = oauth1.create_oauth_manager(self._adapter)
self.policies = policies.PolicyManager(self._adapter) self.policies = policies.PolicyManager(self._adapter)
self.projects = projects.ProjectManager(self._adapter) self.projects = projects.ProjectManager(self._adapter)

148
keystoneclient/v3/limits.py Normal file
View File

@@ -0,0 +1,148 @@
# 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 Limit(base.Resource):
"""Represents a project limit.
Attributes:
* id: a UUID that identifies the project limit
* service_id: a UUID that identifies the service for the limit
* region_id: a UUID that identifies the region for the limit
* project_id: a UUID that identifies the project for the limit
* resource_name: the name of the resource to limit
* resource_limit: the limit to apply to the project
* description: a description for the project limit
"""
pass
class LimitManager(base.CrudManager):
"""Manager class for project limits."""
resource_class = Limit
collection_key = 'limits'
key = 'limit'
def create(self, project, service, resource_name, resource_limit,
description=None, region=None, **kwargs):
"""Create a project-specific limit.
:param project: the project to create a limit for.
:type project: str or :class:`keystoneclient.v3.projects.Project`
:param service: the service that owns the resource to limit.
:type service: str or :class:`keystoneclient.v3.services.Service`
:param resource_name: the name of the resource to limit
:type resource_name: str
:param resource_limit: the quantity of the limit
:type resource_limit: int
:param description: a description of the limit
:type description: str
:param region: region the limit applies to
:type region: str or :class:`keystoneclient.v3.regions.Region`
:returns: a reference of the created limit
:rtype: :class:`keystoneclient.v3.limits.Limit`
"""
limit_data = base.filter_none(
project_id=base.getid(project),
service_id=base.getid(service),
resource_name=resource_name,
resource_limit=resource_limit,
description=description,
region_id=base.getid(region),
**kwargs
)
body = {self.collection_key: [limit_data]}
resp, body = self.client.post('/limits', body=body)
limit = body[self.collection_key].pop()
return self.resource_class(self, limit)
def update(self, limit, project=None, service=None, resource_name=None,
resource_limit=None, description=None, **kwargs):
"""Update a project-specific limit.
:param limit: a limit to update
:param project: the project ID of the limit to update
:type project: str or :class:`keystoneclient.v3.projects.Project`
:param resource_limit: the limit of the limit's resource to update
:type: resource_limit: int
:param description: a description of the limit
:type description: str
:returns: a reference of the updated limit.
:rtype: :class:`keystoneclient.v3.limits.Limit`
"""
return super(LimitManager, self).update(
limit_id=base.getid(limit),
project_id=base.getid(project),
service_id=base.getid(service),
resource_name=resource_name,
resource_limit=resource_limit,
description=description,
**kwargs
)
def get(self, limit):
"""Retrieve a project limit.
:param limit:
the project-specific limit to be retrieved.
:type limit:
str or :class:`keystoneclient.v3.limit.Limit`
:returns: a project-specific limit
:rtype: :class:`keystoneclient.v3.limit.Limit`
"""
return super(LimitManager, self).get(limit_id=base.getid(limit))
def list(self, service=None, region=None, resource_name=None, **kwargs):
"""List project-specific limits.
Any parameter provided will be passed to the server as a filter
:param service: service to filter limits by
:type service: UUID or :class:`keystoneclient.v3.services.Service`
:param region: region to filter limits by
:type region: UUID or :class:`keystoneclient.v3.regions.Region`
:param resource_name: the name of the resource to filter limits by
:type resource_name: str
:returns: a list of project-specific limits.
:rtype: list of :class:`keystoneclient.v3.limits.Limit`
"""
return super(LimitManager, self).list(
service_id=base.getid(service),
region_id=base.getid(region),
resource_name=resource_name,
**kwargs
)
def delete(self, limit):
"""Delete a project-specific limit.
:param limit: the project-specific limit to be deleted.
:type limit: str or :class:`keystoneclient.v3.limit.Limit`
:returns: Response object with 204 status
:rtype: :class:`requests.models.Response`
"""
return super(LimitManager, self).delete(limit_id=base.getid(limit))

View File

@@ -0,0 +1,6 @@
---
features:
- |
Added support for managing project-specific limits. The ``POST`` API for
limits in keystone supports batch creation, but the client implementation
does not. Creation for limits using the client must be done one at a time.