Add API controller for quotas and quota classes
Change-Id: Ie451ec88f65819ca91d6042ea2229eae17cebf2e Partial-Implements: blueprint quota-support
This commit is contained in:
parent
0d3336661e
commit
8af77465c3
@ -29,6 +29,8 @@ from zun.api.controllers.v1 import containers as container_controller
|
||||
from zun.api.controllers.v1 import hosts as host_controller
|
||||
from zun.api.controllers.v1 import images as image_controller
|
||||
from zun.api.controllers.v1 import networks as network_controller
|
||||
from zun.api.controllers.v1 import quota_classes as quota_classes_controller
|
||||
from zun.api.controllers.v1 import quotas as quotas_controller
|
||||
from zun.api.controllers.v1 import zun_services
|
||||
from zun.api.controllers import versions as ver
|
||||
from zun.api import http_error
|
||||
@ -71,7 +73,9 @@ class V1(controllers_base.APIBase):
|
||||
'networks',
|
||||
'hosts',
|
||||
'capsules',
|
||||
'availability_zones'
|
||||
'availability_zones',
|
||||
'quotas',
|
||||
'quota_classes'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ -85,8 +89,9 @@ class V1(controllers_base.APIBase):
|
||||
'developer/zun/dev',
|
||||
'api-spec-v1.html',
|
||||
bookmark=True, type='text/html')]
|
||||
v1.media_types = [MediaType(base='application/json',
|
||||
type='application/vnd.openstack.zun.v1+json')]
|
||||
v1.media_types = [MediaType(
|
||||
base='application/json',
|
||||
type='application/vnd.openstack.zun.v1+json')]
|
||||
v1.services = [link.make_link('self', pecan.request.host_url,
|
||||
'services', ''),
|
||||
link.make_link('bookmark',
|
||||
@ -129,6 +134,18 @@ class V1(controllers_base.APIBase):
|
||||
pecan.request.host_url,
|
||||
'capsules', '',
|
||||
bookmark=True)]
|
||||
v1.quotas = [link.make_link('self', pecan.request.host_url,
|
||||
'quotas', ''),
|
||||
link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'quotas', '',
|
||||
bookmark=True)]
|
||||
v1.quota_classes = [link.make_link('self', pecan.request.host_url,
|
||||
'quota_classes', ''),
|
||||
link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'quota_classes', '',
|
||||
bookmark=True)]
|
||||
return v1
|
||||
|
||||
|
||||
@ -142,6 +159,8 @@ class Controller(controllers_base.Controller):
|
||||
hosts = host_controller.HostController()
|
||||
availability_zones = a_zone.AvailabilityZoneController()
|
||||
capsules = capsule_controller.CapsuleController()
|
||||
quotas = quotas_controller.QuotaController()
|
||||
quota_classes = quota_classes_controller.QuotaClassController()
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self):
|
||||
|
@ -36,6 +36,7 @@ from zun.common.i18n import _
|
||||
from zun.common import name_generator
|
||||
from zun.common.policies import container as policies
|
||||
from zun.common import policy
|
||||
from zun.common import quota
|
||||
from zun.common import utils
|
||||
import zun.conf
|
||||
from zun.network import model as network_model
|
||||
@ -46,6 +47,7 @@ from zun.volume import cinder_api as cinder
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
def check_policy_on_container(container, action):
|
||||
@ -329,6 +331,9 @@ class ContainersController(base.Controller):
|
||||
raise exception.InvalidValue(_('Valid run or interactive '
|
||||
'values are: %s') % bools)
|
||||
|
||||
# Check container quotas
|
||||
self._check_container_quotas(context, container_dict)
|
||||
|
||||
auto_remove = container_dict.pop('auto_remove', None)
|
||||
if auto_remove is not None:
|
||||
api_utils.version_check('auto_remove', '1.3')
|
||||
@ -428,6 +433,52 @@ class ContainersController(base.Controller):
|
||||
return view.format_container(context, pecan.request.host_url,
|
||||
new_container.as_dict())
|
||||
|
||||
def _check_container_quotas(self, context, container_delta_dict,
|
||||
update_container=False):
|
||||
deltas = {
|
||||
'containers': 0 if update_container else 1,
|
||||
'cpu': container_delta_dict.get('cpu', 0),
|
||||
'memory': container_delta_dict.get('memory', 0),
|
||||
'disk': container_delta_dict.get('disk', 0)
|
||||
}
|
||||
|
||||
def _check_deltas(context, deltas):
|
||||
"""Check usage deltas against quota limits.
|
||||
|
||||
This does QUOTAS.count() followed by a QUOTAS.limit_check()
|
||||
using the provided deltas.
|
||||
|
||||
:param context: The request context, for access check.
|
||||
:param deltas: A dict of {resource_name: delta, ...} to check
|
||||
against the quota limits.
|
||||
"""
|
||||
check_kwargs = {}
|
||||
count_as_dict = {}
|
||||
project_id = context.project_id
|
||||
for res_name, res_delta in deltas.items():
|
||||
# TODO(kiennt): Apply count_as_dict method, query count usage
|
||||
# once rather than count each resource.
|
||||
count_as_dict[res_name] = QUOTAS.count(context, res_name,
|
||||
project_id)
|
||||
total = None
|
||||
try:
|
||||
if isinstance(count_as_dict[res_name], six.integer_types):
|
||||
total = count_as_dict[res_name] + int(res_delta)
|
||||
else:
|
||||
total = float(count_as_dict[res_name]) + \
|
||||
float(res_delta)
|
||||
except TypeError as e:
|
||||
raise e
|
||||
check_kwargs[res_name] = total
|
||||
try:
|
||||
QUOTAS.limit_check(context, project_id, **check_kwargs)
|
||||
except exception.OverQuota as exc:
|
||||
# Set HTTP response status code
|
||||
pecan.response.status = 403
|
||||
raise exc
|
||||
|
||||
_check_deltas(context, deltas)
|
||||
|
||||
def _set_default_resource_limit(self, container_dict):
|
||||
# NOTE(kiennt): Default disk size will be set later.
|
||||
container_dict['disk'] = container_dict.get('disk')
|
||||
@ -630,21 +681,28 @@ class ContainersController(base.Controller):
|
||||
:param patch: a json PATCH document to apply to this container.
|
||||
"""
|
||||
container = utils.get_container(container_ident)
|
||||
context = pecan.request.context
|
||||
container_deltas = {}
|
||||
check_policy_on_container(container.as_dict(), "container:update")
|
||||
utils.validate_container_state(container, 'update')
|
||||
if 'memory' in patch:
|
||||
container_deltas['memory'] = patch['memory'] - container.memory
|
||||
patch['memory'] = str(patch['memory'])
|
||||
if 'cpu' in patch:
|
||||
patch['cpu'] = float(patch['cpu'])
|
||||
container_deltas['cpu'] = patch['cpu'] - container.cpu
|
||||
if 'name' in patch:
|
||||
patch['name'] = str(patch['name'])
|
||||
context = pecan.request.context
|
||||
|
||||
if 'memory' not in patch and 'cpu' not in patch:
|
||||
for field, patch_val in patch.items():
|
||||
if getattr(container, field) != patch_val:
|
||||
setattr(container, field, patch_val)
|
||||
container.save(context)
|
||||
else:
|
||||
# Check container quotas
|
||||
self._check_container_quotas(context, container_deltas,
|
||||
update_container=True)
|
||||
compute_api = pecan.request.compute_api
|
||||
container = compute_api.container_update(context, container, patch)
|
||||
return view.format_container(context, pecan.request.host_url,
|
||||
|
57
zun/api/controllers/v1/quota_classes.py
Normal file
57
zun/api/controllers/v1/quota_classes.py
Normal file
@ -0,0 +1,57 @@
|
||||
# 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 pecan
|
||||
|
||||
from zun.api.controllers import base
|
||||
from zun.api.controllers.v1.schemas import quota_classes as schema
|
||||
from zun.api import validation
|
||||
from zun.common import exception
|
||||
from zun.common import policy
|
||||
from zun.common import quota
|
||||
from zun import objects
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
class QuotaClassController(base.Controller):
|
||||
"""Controller for QuotaClass"""
|
||||
|
||||
def _get_quotas(self, context, quota_class):
|
||||
return QUOTAS.get_class_quotas(context, quota_class)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
@validation.validate_query_param(pecan.request, schema.query_param_update)
|
||||
@validation.validated(schema.query_param_update)
|
||||
def put(self, quota_class_name, **quota_classes_dict):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, 'quota_class:update',
|
||||
action='quota_class:update')
|
||||
for key, value in quota_classes_dict.items():
|
||||
value = int(value)
|
||||
quota_class = objects.QuotaClass(
|
||||
context, class_name=quota_class_name,
|
||||
resource=key, hard_limit=value)
|
||||
try:
|
||||
quota_class.update(context)
|
||||
except exception.QuotaClassNotFound:
|
||||
quota_class.create(context)
|
||||
return self._get_quotas(context, quota_class_name)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
def get(self, quota_class_name):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, 'quota_class:get',
|
||||
action='quota_class:get')
|
||||
return self._get_quotas(context, quota_class_name)
|
85
zun/api/controllers/v1/quotas.py
Normal file
85
zun/api/controllers/v1/quotas.py
Normal file
@ -0,0 +1,85 @@
|
||||
# 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 pecan
|
||||
|
||||
from zun.api.controllers import base
|
||||
from zun.api.controllers.v1.schemas import quotas as schema
|
||||
from zun.api import validation
|
||||
from zun.common import exception
|
||||
from zun.common import policy
|
||||
from zun.common import quota
|
||||
from zun import objects
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
class QuotaController(base.Controller):
|
||||
"""Controller for Quotas"""
|
||||
|
||||
_custom_actions = {
|
||||
'defaults': ['GET'],
|
||||
}
|
||||
|
||||
def _get_quotas(self, context, usages=False):
|
||||
values = QUOTAS.get_project_quotas(context, context.project_id,
|
||||
usages=usages)
|
||||
|
||||
if usages:
|
||||
return values
|
||||
else:
|
||||
return {k: v['limit'] for k, v in values.items()}
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
@validation.validate_query_param(pecan.request, schema.query_param_update)
|
||||
@validation.validated(schema.query_param_update)
|
||||
def put(self, **quotas_dict):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, 'quota:update',
|
||||
action='quota:update')
|
||||
project_id = context.project_id
|
||||
for key, value in quotas_dict.items():
|
||||
value = int(value)
|
||||
quota = objects.Quota(context, project_id=project_id, resource=key,
|
||||
hard_limit=value)
|
||||
try:
|
||||
quota.create(context)
|
||||
except exception.QuotaExists:
|
||||
quota.update(context)
|
||||
return self._get_quotas(context)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
def get(self, **kwargs):
|
||||
context = pecan.request.context
|
||||
usages = kwargs.get('usages', False)
|
||||
policy.enforce(context, 'quota:get',
|
||||
action='quota:get')
|
||||
return self._get_quotas(context, usages=usages)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
def defaults(self):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, 'quota:get_default',
|
||||
action='quota:get_default')
|
||||
values = QUOTAS.get_defaults(context)
|
||||
return values
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
def delete(self):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, 'quota:delete',
|
||||
action='quota:delete')
|
||||
QUOTAS.destroy_all_by_project(context, context.project_id)
|
20
zun/api/controllers/v1/schemas/quota_classes.py
Normal file
20
zun/api/controllers/v1/schemas/quota_classes.py
Normal file
@ -0,0 +1,20 @@
|
||||
# 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 zun.api.controllers.v1.schemas import quotas
|
||||
|
||||
|
||||
query_param_update = {
|
||||
'type': 'object',
|
||||
'properties': quotas.quota_resources,
|
||||
'additionalProperties': False
|
||||
}
|
31
zun/api/controllers/v1/schemas/quotas.py
Normal file
31
zun/api/controllers/v1/schemas/quotas.py
Normal file
@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
|
||||
common_quota = {
|
||||
'type': ['integer', 'string'],
|
||||
'pattern': '^-?[0-9]+$',
|
||||
# -1 is a flag value for unlimited
|
||||
'minimum': -1
|
||||
}
|
||||
|
||||
quota_resources = {
|
||||
'containers': common_quota,
|
||||
'memory': common_quota,
|
||||
'cpu': common_quota,
|
||||
'disk': common_quota
|
||||
}
|
||||
|
||||
query_param_update = {
|
||||
'type': 'object',
|
||||
'properties': quota_resources,
|
||||
'additionalProperties': False
|
||||
}
|
@ -58,10 +58,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
* 1.23 - Add attribute 'type' to parameter 'mounts'
|
||||
* 1.24 - Add exposed_ports to container
|
||||
* 1.25 - Encode/Decode archive file
|
||||
* 1.26 - Introduce Quota support
|
||||
"""
|
||||
|
||||
BASE_VER = '1.1'
|
||||
CURRENT_MAX_VER = '1.25'
|
||||
CURRENT_MAX_VER = '1.26'
|
||||
|
||||
|
||||
class Version(object):
|
||||
|
@ -209,3 +209,8 @@ user documentation.
|
||||
The get_archive endpoint returns a encoded archived file data by using
|
||||
Base64 algorithm.
|
||||
The put_archive endpoint take a Base64-encoded archived file data as input.
|
||||
|
||||
1.26
|
||||
----
|
||||
|
||||
Introduce Quota support API
|
||||
|
@ -30,6 +30,7 @@ from zun.conf import neutron_client
|
||||
from zun.conf import path
|
||||
from zun.conf import pci
|
||||
from zun.conf import profiler
|
||||
from zun.conf import quota
|
||||
from zun.conf import scheduler
|
||||
from zun.conf import services
|
||||
from zun.conf import ssl
|
||||
@ -58,6 +59,7 @@ neutron_client.register_opts(CONF)
|
||||
network.register_opts(CONF)
|
||||
websocket_proxy.register_opts(CONF)
|
||||
pci.register_opts(CONF)
|
||||
quota.register_opts(CONF)
|
||||
volume.register_opts(CONF)
|
||||
cinder_client.register_opts(CONF)
|
||||
netconf.register_opts(CONF)
|
||||
|
@ -26,7 +26,7 @@ from zun.tests.unit.db import base
|
||||
|
||||
|
||||
PATH_PREFIX = '/v1'
|
||||
CURRENT_VERSION = "container 1.25"
|
||||
CURRENT_VERSION = "container 1.26"
|
||||
|
||||
|
||||
class FunctionalTest(base.DbTestCase):
|
||||
|
@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest):
|
||||
'default_version':
|
||||
{'id': 'v1',
|
||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||
'max_version': '1.25',
|
||||
'max_version': '1.26',
|
||||
'min_version': '1.1',
|
||||
'status': 'CURRENT'},
|
||||
'description': 'Zun is an OpenStack project which '
|
||||
@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest):
|
||||
'versions': [{'id': 'v1',
|
||||
'links': [{'href': 'http://localhost/v1/',
|
||||
'rel': 'self'}],
|
||||
'max_version': '1.25',
|
||||
'max_version': '1.26',
|
||||
'min_version': '1.1',
|
||||
'status': 'CURRENT'}]}
|
||||
|
||||
@ -80,7 +80,16 @@ class TestRootController(api_base.FunctionalTest):
|
||||
'capsules': [{'href': 'http://localhost/v1/capsules/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://localhost/capsules/',
|
||||
'rel': 'bookmark'}]}
|
||||
'rel': 'bookmark'}],
|
||||
'quotas': [{'href': 'http://localhost/v1/quotas/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://localhost/quotas/',
|
||||
'rel': 'bookmark'}],
|
||||
'quota_classes': [{'href': 'http://localhost/v1/quota_classes/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://localhost/quota_classes/',
|
||||
'rel': 'bookmark'}]
|
||||
}
|
||||
|
||||
def make_app(self, paste_file):
|
||||
file_name = self.get_path(paste_file)
|
||||
|
48
zun/tests/unit/api/controllers/v1/test_quota_classes.py
Normal file
48
zun/tests/unit/api/controllers/v1/test_quota_classes.py
Normal file
@ -0,0 +1,48 @@
|
||||
# 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 mock
|
||||
|
||||
from zun.tests.unit.api import base as api_base
|
||||
|
||||
|
||||
class TestQuotaClassController(api_base.FunctionalTest):
|
||||
def setUp(self):
|
||||
super(TestQuotaClassController, self).setUp()
|
||||
self.default_quotas = {
|
||||
'containers': 40,
|
||||
'cpu': 20,
|
||||
'memory': 51200,
|
||||
'disk': 100
|
||||
}
|
||||
|
||||
@mock.patch('zun.common.policy.enforce', return_value=True)
|
||||
def test_put_quota(self, mock_policy):
|
||||
update_quota_dicts = {
|
||||
'containers': '50',
|
||||
'memory': '61440'
|
||||
}
|
||||
|
||||
admin_project_id = 'fakeadminprojectid'
|
||||
path = '/quota_classes/' + admin_project_id
|
||||
response = self.put_json(path, update_quota_dicts)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(50, response.json['containers'])
|
||||
self.assertEqual(61440, response.json['memory'])
|
||||
|
||||
@mock.patch('zun.common.policy.enforce', return_value=True)
|
||||
def test_get_quota(self, mock_policy):
|
||||
admin_project_id = 'fakeadminprojectid'
|
||||
path = '/quota_classes/' + admin_project_id
|
||||
response = self.get(path)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(self.default_quotas, response.json)
|
57
zun/tests/unit/api/controllers/v1/test_quotas.py
Normal file
57
zun/tests/unit/api/controllers/v1/test_quotas.py
Normal file
@ -0,0 +1,57 @@
|
||||
# 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 mock
|
||||
|
||||
from zun.tests.unit.api import base as api_base
|
||||
|
||||
|
||||
class TestQuotaController(api_base.FunctionalTest):
|
||||
def setUp(self):
|
||||
super(TestQuotaController, self).setUp()
|
||||
self.default_quotas = {
|
||||
'containers': 40,
|
||||
'cpu': 20,
|
||||
'memory': 51200,
|
||||
'disk': 100
|
||||
}
|
||||
|
||||
@mock.patch('zun.common.policy.enforce', return_value=True)
|
||||
def test_get_defaults_quota(self, mock_policy):
|
||||
response = self.get('/quotas/defaults')
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(self.default_quotas, response.json)
|
||||
|
||||
@mock.patch('zun.common.policy.enforce', return_value=True)
|
||||
def test_put_quota(self, mock_policy):
|
||||
update_quota_dicts = {
|
||||
'containers': '50',
|
||||
'memory': '61440'
|
||||
}
|
||||
|
||||
response = self.put_json('/quotas', update_quota_dicts)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(50, response.json['containers'])
|
||||
self.assertEqual(61440, response.json['memory'])
|
||||
|
||||
@mock.patch('zun.common.policy.enforce', return_value=True)
|
||||
def test_get_quota(self, mock_policy):
|
||||
response = self.get('/quotas')
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(self.default_quotas, response.json)
|
||||
|
||||
@mock.patch('zun.common.policy.enforce', return_value=True)
|
||||
def test_delete_quota(self, mock_policy):
|
||||
response = self.delete('/quotas')
|
||||
self.assertEqual(200, response.status_int)
|
||||
response = self.get('/quotas')
|
||||
self.assertEqual(self.default_quotas, response.json)
|
Loading…
Reference in New Issue
Block a user