Merge "Add support for project-specific limits"

This commit is contained in:
Zuul 2018-06-20 20:33:45 +00:00 committed by Gerrit Code Review
commit 234ea50d5d
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 endpoints
from keystoneclient.v3 import groups
from keystoneclient.v3 import limits
from keystoneclient.v3 import policies
from keystoneclient.v3 import projects
from keystoneclient.v3 import regions
@ -159,6 +160,10 @@ class Client(httpclient.HTTPClient):
:py:class:`keystoneclient.v3.groups.GroupManager`
.. py:attribute:: limits
:py:class:`keystoneclient.v3.limits.LimitManager`
.. py:attribute:: oauth1
:py:class:`keystoneclient.v3.contrib.oauth1.core.OAuthManager`
@ -235,6 +240,7 @@ class Client(httpclient.HTTPClient):
self.domains = domains.DomainManager(self._adapter)
self.federation = federation.FederationManager(self._adapter)
self.groups = groups.GroupManager(self._adapter)
self.limits = limits.LimitManager(self._adapter)
self.oauth1 = oauth1.create_oauth_manager(self._adapter)
self.policies = policies.PolicyManager(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.