Merge "Port quotas to core API"
This commit is contained in:
commit
8d1cc739aa
@ -7,6 +7,13 @@
|
||||
|
||||
"availability_zone:index": "rule:default",
|
||||
|
||||
"quota_set:update": "rule:admin_api",
|
||||
"quota_set:show": "rule:default",
|
||||
"quota_set:delete": "rule:admin_api",
|
||||
|
||||
"quota_class_set:show": "rule:default",
|
||||
"quota_class_set:update": "rule:admin_api",
|
||||
|
||||
"service:index": "rule:admin_api",
|
||||
"service:update": "rule:admin_api",
|
||||
|
||||
@ -41,11 +48,6 @@
|
||||
"share_instance:index": "rule:admin_api",
|
||||
"share_instance:show": "rule:admin_api",
|
||||
|
||||
"share_extension:quotas:show": "",
|
||||
"share_extension:quotas:update": "rule:admin_api",
|
||||
"share_extension:quotas:delete": "rule:admin_api",
|
||||
"share_extension:quota_classes": "",
|
||||
|
||||
"share_extension:share_admin_actions:force_delete": "rule:admin_api",
|
||||
"share_extension:share_admin_actions:reset_status": "rule:admin_api",
|
||||
"share_extension:snapshot_admin_actions:force_delete": "rule:admin_api",
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# Copyright (c) 2015 Mirantis inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -15,67 +16,47 @@
|
||||
|
||||
import webob
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import quota_class_sets as quota_class_sets_views
|
||||
from manila import db
|
||||
from manila import exception
|
||||
from manila import quota
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
authorize = extensions.extension_authorizer('share', 'quota_classes')
|
||||
|
||||
|
||||
class QuotaClassSetsController(object):
|
||||
class QuotaClassSetsController(wsgi.Controller):
|
||||
|
||||
def _format_quota_set(self, quota_class, quota_set):
|
||||
"""Convert the quota object to a result dict."""
|
||||
|
||||
result = dict(id=str(quota_class))
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
result[resource] = quota_set[resource]
|
||||
|
||||
return dict(quota_class_set=result)
|
||||
resource_name = "quota_class_set"
|
||||
_view_builder_class = quota_class_sets_views.ViewBuilder
|
||||
|
||||
def show(self, req, id):
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
self.authorize(context, 'show')
|
||||
try:
|
||||
db.sqlalchemy.api.authorize_quota_class_context(context, id)
|
||||
db.authorize_quota_class_context(context, id)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
return self._format_quota_set(id,
|
||||
QUOTAS.get_class_quotas(context, id))
|
||||
return self._view_builder.detail_list(
|
||||
QUOTAS.get_class_quotas(context, id), id)
|
||||
|
||||
def update(self, req, id, body):
|
||||
context = req.environ['manila.context']
|
||||
authorize(context)
|
||||
self.authorize(context, 'update')
|
||||
quota_class = id
|
||||
for key in body['quota_class_set'].keys():
|
||||
for key in body.get(self.resource_name, {}).keys():
|
||||
if key in QUOTAS:
|
||||
value = int(body['quota_class_set'][key])
|
||||
value = int(body[self.resource_name][key])
|
||||
try:
|
||||
db.quota_class_update(context, quota_class, key, value)
|
||||
except exception.QuotaClassNotFound:
|
||||
db.quota_class_create(context, quota_class, key, value)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
return {'quota_class_set': QUOTAS.get_class_quotas(context,
|
||||
quota_class)}
|
||||
return self._view_builder.detail_list(
|
||||
QUOTAS.get_class_quotas(context, quota_class))
|
||||
|
||||
|
||||
class Quota_classes(extensions.ExtensionDescriptor):
|
||||
"""Quota classes management support."""
|
||||
|
||||
name = "QuotaClasses"
|
||||
alias = "os-quota-class-sets"
|
||||
updated = "2012-03-12T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
|
||||
res = extensions.ResourceExtension('os-quota-class-sets',
|
||||
QuotaClassSetsController())
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
||||
def create_resource():
|
||||
return wsgi.Resource(QuotaClassSetsController())
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright (c) 2015 Mirantis inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -18,38 +19,23 @@ from oslo_utils import strutils
|
||||
from six.moves.urllib import parse
|
||||
import webob
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api.openstack import wsgi
|
||||
from manila.api.views import quota_sets as quota_sets_views
|
||||
from manila import db
|
||||
from manila.db.sqlalchemy import api as sqlalchemy_api
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila import quota
|
||||
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
LOG = log.getLogger(__name__)
|
||||
NON_QUOTA_KEYS = ['tenant_id', 'id', 'force']
|
||||
NON_QUOTA_KEYS = ('tenant_id', 'id', 'force')
|
||||
|
||||
|
||||
authorize_update = extensions.extension_authorizer('compute', 'quotas:update')
|
||||
authorize_show = extensions.extension_authorizer('compute', 'quotas:show')
|
||||
authorize_delete = extensions.extension_authorizer('compute', 'quotas:delete')
|
||||
class QuotaSetsController(wsgi.Controller):
|
||||
"""The Quota Sets API controller for the OpenStack API."""
|
||||
|
||||
|
||||
class QuotaSetsController(object):
|
||||
|
||||
def __init__(self, ext_mgr):
|
||||
self.ext_mgr = ext_mgr
|
||||
|
||||
def _format_quota_set(self, project_id, quota_set):
|
||||
"""Convert the quota object to a result dict."""
|
||||
|
||||
result = dict(id=str(project_id))
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
result[resource] = quota_set[resource]
|
||||
|
||||
return dict(quota_set=result)
|
||||
resource_name = "quota_set"
|
||||
_view_builder_class = quota_sets_views.ViewBuilder
|
||||
|
||||
def _validate_quota_limit(self, limit, minimum, maximum, force_update):
|
||||
# NOTE: -1 is a flag value for unlimited
|
||||
@ -73,45 +59,33 @@ class QuotaSetsController(object):
|
||||
|
||||
if usages:
|
||||
return values
|
||||
else:
|
||||
return dict((k, v['limit']) for k, v in values.items())
|
||||
return dict((k, v['limit']) for k, v in values.items())
|
||||
|
||||
def show(self, req, id):
|
||||
context = req.environ['manila.context']
|
||||
authorize_show(context)
|
||||
self.authorize(context, 'show')
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = None
|
||||
if self.ext_mgr.is_loaded('os-user-quotas'):
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
try:
|
||||
sqlalchemy_api.authorize_project_context(context, id)
|
||||
return self._format_quota_set(
|
||||
id, self._get_quotas(context, id, user_id=user_id))
|
||||
db.authorize_project_context(context, id)
|
||||
return self._view_builder.detail_list(
|
||||
self._get_quotas(context, id, user_id=user_id), id)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
def defaults(self, req, id):
|
||||
context = req.environ['manila.context']
|
||||
self.authorize(context, 'show')
|
||||
return self._view_builder.detail_list(QUOTAS.get_defaults(context), id)
|
||||
|
||||
def update(self, req, id, body):
|
||||
context = req.environ['manila.context']
|
||||
authorize_update(context)
|
||||
self.authorize(context, 'update')
|
||||
project_id = id
|
||||
|
||||
bad_keys = []
|
||||
|
||||
# By default, we can force update the quota if the extended
|
||||
# is not loaded
|
||||
force_update = True
|
||||
extended_loaded = False
|
||||
if self.ext_mgr.is_loaded('os-extended-quotas'):
|
||||
# force optional has been enabled, the default value of
|
||||
# force_update need to be changed to False
|
||||
extended_loaded = True
|
||||
force_update = False
|
||||
|
||||
user_id = None
|
||||
if self.ext_mgr.is_loaded('os-user-quotas'):
|
||||
# Update user quotas only if the extended is loaded
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
force_update = False
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
|
||||
try:
|
||||
settable_quotas = QUOTAS.get_settable_quotas(context, project_id,
|
||||
@ -119,14 +93,12 @@ class QuotaSetsController(object):
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
for key, value in body['quota_set'].items():
|
||||
for key, value in body.get('quota_set', {}).items():
|
||||
if (key not in QUOTAS and
|
||||
key not in NON_QUOTA_KEYS):
|
||||
bad_keys.append(key)
|
||||
continue
|
||||
if key == 'force' and extended_loaded:
|
||||
# only check the force optional when the extended has
|
||||
# been loaded
|
||||
if key == 'force':
|
||||
force_update = strutils.bool_from_string(value)
|
||||
elif key not in NON_QUOTA_KEYS and value:
|
||||
try:
|
||||
@ -137,10 +109,10 @@ class QuotaSetsController(object):
|
||||
LOG.warn(msg)
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
LOG.debug("force update quotas: %s", force_update)
|
||||
LOG.debug("Force update quotas: %s.", force_update)
|
||||
|
||||
if len(bad_keys) > 0:
|
||||
msg = _("Bad key(s) %s in quota_set") % ",".join(bad_keys)
|
||||
msg = _("Bad key(s) %s in quota_set.") % ",".join(bad_keys)
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
try:
|
||||
@ -149,7 +121,7 @@ class QuotaSetsController(object):
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
for key, value in body['quota_set'].items():
|
||||
for key, value in body.get('quota_set', {}).items():
|
||||
if key in NON_QUOTA_KEYS or (not value and value != 0):
|
||||
continue
|
||||
# validate whether already used and reserved exceeds the new
|
||||
@ -163,7 +135,7 @@ class QuotaSetsController(object):
|
||||
LOG.warn(msg)
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if force_update is not True and value >= 0:
|
||||
if force_update is False and value >= 0:
|
||||
quota_value = quotas.get(key)
|
||||
if quota_value and quota_value['limit'] >= 0:
|
||||
quota_used = (quota_value['in_use'] +
|
||||
@ -175,7 +147,7 @@ class QuotaSetsController(object):
|
||||
if quota_used > value:
|
||||
msg = (_("Quota value %(value)s for %(key)s are "
|
||||
"greater than already used and reserved "
|
||||
"%(quota_used)s") %
|
||||
"%(quota_used)s.") %
|
||||
{'value': value, 'key': key,
|
||||
'quota_used': quota_used})
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
@ -191,46 +163,24 @@ class QuotaSetsController(object):
|
||||
user_id=user_id)
|
||||
except exception.AdminRequired:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
return {'quota_set': self._get_quotas(context, id, user_id=user_id)}
|
||||
|
||||
def defaults(self, req, id):
|
||||
context = req.environ['manila.context']
|
||||
authorize_show(context)
|
||||
return self._format_quota_set(id, QUOTAS.get_defaults(context))
|
||||
return self._view_builder.detail_list(
|
||||
self._get_quotas(context, id, user_id=user_id))
|
||||
|
||||
def delete(self, req, id):
|
||||
if self.ext_mgr.is_loaded('os-extended-quotas'):
|
||||
context = req.environ['manila.context']
|
||||
authorize_delete(context)
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
if user_id and not self.ext_mgr.is_loaded('os-user-quotas'):
|
||||
raise webob.exc.HTTPNotFound()
|
||||
try:
|
||||
sqlalchemy_api.authorize_project_context(context, id)
|
||||
if user_id:
|
||||
QUOTAS.destroy_all_by_project_and_user(context,
|
||||
id, user_id)
|
||||
else:
|
||||
QUOTAS.destroy_all_by_project(context, id)
|
||||
return webob.Response(status_int=202)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
raise webob.exc.HTTPNotFound()
|
||||
context = req.environ['manila.context']
|
||||
self.authorize(context, 'update')
|
||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
try:
|
||||
db.authorize_project_context(context, id)
|
||||
if user_id:
|
||||
QUOTAS.destroy_all_by_project_and_user(context, id, user_id)
|
||||
else:
|
||||
QUOTAS.destroy_all_by_project(context, id)
|
||||
return webob.Response(status_int=202)
|
||||
except exception.NotAuthorized:
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
|
||||
class Quotas(extensions.ExtensionDescriptor):
|
||||
"""Quotas management support."""
|
||||
|
||||
name = "Quotas"
|
||||
alias = "os-quota-sets"
|
||||
updated = "2011-08-08T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
res = extensions.ResourceExtension('os-quota-sets',
|
||||
QuotaSetsController(self.ext_mgr),
|
||||
member_actions={'defaults': 'GET'})
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
||||
def create_resource():
|
||||
return wsgi.Resource(QuotaSetsController())
|
@ -27,6 +27,8 @@ from manila.api.v1 import availability_zones
|
||||
from manila.api.v1 import cgsnapshots
|
||||
from manila.api.v1 import consistency_groups
|
||||
from manila.api.v1 import limits
|
||||
from manila.api.v1 import quota_class_sets
|
||||
from manila.api.v1 import quota_sets
|
||||
from manila.api.v1 import scheduler_stats
|
||||
from manila.api.v1 import security_service
|
||||
from manila.api.v1 import services
|
||||
@ -75,6 +77,21 @@ class APIRouter(manila.api.openstack.APIRouter):
|
||||
"os-services",
|
||||
controller=self.resources["services"])
|
||||
|
||||
self.resources["quota_sets"] = quota_sets.create_resource()
|
||||
mapper.resource("quota-set",
|
||||
# TODO(vponomaryov): rename 'os-quota-sets' to
|
||||
# 'quota-sets' when API urls rename happens.
|
||||
"os-quota-sets",
|
||||
controller=self.resources["quota_sets"],
|
||||
member={'defaults': 'GET'})
|
||||
|
||||
self.resources["quota_class_sets"] = quota_class_sets.create_resource()
|
||||
mapper.resource("quota-class-set",
|
||||
# TODO(vponomaryov): rename 'os-quota-class-sets' to
|
||||
# 'quota-class-sets' when API urls rename happens.
|
||||
"os-quota-class-sets",
|
||||
controller=self.resources["quota_class_sets"])
|
||||
|
||||
self.resources["share_manage"] = share_manage.create_resource()
|
||||
mapper.resource("share_manage",
|
||||
# TODO(vponomaryov): remove it when it is ported
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Author: Andrei Ostapenko <aostapenko@mirantis.com>
|
||||
# Copyright (c) 2015 Mirantis inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -14,12 +13,23 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api import common
|
||||
|
||||
|
||||
class User_quotas(extensions.ExtensionDescriptor):
|
||||
"""Project user quota support."""
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
|
||||
name = "UserQuotas"
|
||||
alias = "os-user-quotas"
|
||||
updated = "2013-07-18T00:00:00+00:00"
|
||||
_collection_name = "quota_class_set"
|
||||
|
||||
def detail_list(self, quota_set, quota_class=None):
|
||||
"""Detailed view of quota class set."""
|
||||
keys = (
|
||||
'shares',
|
||||
'gigabytes',
|
||||
'snapshots',
|
||||
'snapshot_gigabytes',
|
||||
'share_networks',
|
||||
)
|
||||
view = {key: quota_set.get(key) for key in keys}
|
||||
if quota_class:
|
||||
view['id'] = quota_class
|
||||
return {self._collection_name: view}
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2013 Rackspace Hosting
|
||||
# Copyright (c) 2015 Mirantis inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -13,16 +13,23 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manila.api import extensions
|
||||
from manila.api import common
|
||||
|
||||
|
||||
class Extended_quotas(extensions.ExtensionDescriptor):
|
||||
"""Extend quotas.
|
||||
class ViewBuilder(common.ViewBuilder):
|
||||
|
||||
Adds ability for admins to delete quota and optionally force the
|
||||
update Quota command.
|
||||
"""
|
||||
_collection_name = "quota_set"
|
||||
|
||||
name = "ExtendedQuotas"
|
||||
alias = "os-extended-quotas"
|
||||
updated = "2013-06-09T00:00:00+00:00"
|
||||
def detail_list(self, quota_set, project_id=None):
|
||||
"""Detailed view of quota set."""
|
||||
keys = (
|
||||
'shares',
|
||||
'gigabytes',
|
||||
'snapshots',
|
||||
'snapshot_gigabytes',
|
||||
'share_networks',
|
||||
)
|
||||
view = {key: quota_set.get(key) for key in keys}
|
||||
if project_id:
|
||||
view['id'] = project_id
|
||||
return {self._collection_name: view}
|
@ -68,6 +68,16 @@ IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=_BACKEND_MAPPING,
|
||||
lazy=True)
|
||||
|
||||
|
||||
def authorize_project_context(context, project_id):
|
||||
"""Ensures a request has permission to access the given project."""
|
||||
return IMPL.authorize_project_context(context, project_id)
|
||||
|
||||
|
||||
def authorize_quota_class_context(context, class_name):
|
||||
"""Ensures a request has permission to access the given quota class."""
|
||||
return IMPL.authorize_quota_class_context(context, class_name)
|
||||
|
||||
|
||||
###################
|
||||
def service_destroy(context, service_id):
|
||||
"""Destroy the service or raise if it does not exist."""
|
||||
|
129
manila/tests/api/v1/test_quota_class_sets.py
Normal file
129
manila/tests/api/v1/test_quota_class_sets.py
Normal file
@ -0,0 +1,129 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright (c) 2015 Mirantis inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Tests for manila.api.v1.quota_class_sets.py
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import webob.exc
|
||||
import webob.response
|
||||
|
||||
from manila.api.v1 import quota_class_sets
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila import test
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
REQ = mock.MagicMock()
|
||||
REQ.environ = {'manila.context': context.get_admin_context()}
|
||||
REQ.environ['manila.context'].is_admin = True
|
||||
REQ.environ['manila.context'].auth_token = 'foo_auth_token'
|
||||
REQ.environ['manila.context'].project_id = 'foo_project_id'
|
||||
|
||||
REQ_MEMBER = copy.deepcopy(REQ)
|
||||
REQ_MEMBER.environ['manila.context'].is_admin = False
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class QuotaSetsControllerTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
self.controller = quota_class_sets.QuotaClassSetsController()
|
||||
self.class_name = 'foo_class_name'
|
||||
|
||||
def test_show_quota(self):
|
||||
quotas = {
|
||||
"shares": 23,
|
||||
"snapshots": 34,
|
||||
"gigabytes": 45,
|
||||
"snapshot_gigabytes": 56,
|
||||
"share_networks": 67,
|
||||
}
|
||||
expected = {
|
||||
'quota_class_set': {
|
||||
'id': self.class_name,
|
||||
'shares': quotas.get('shares', 50),
|
||||
'gigabytes': quotas.get('gigabytes', 1000),
|
||||
'snapshots': quotas.get('snapshots', 50),
|
||||
'snapshot_gigabytes': quotas.get('snapshot_gigabytes', 1000),
|
||||
'share_networks': quotas.get('share_networks', 10),
|
||||
}
|
||||
}
|
||||
for k, v in quotas.items():
|
||||
CONF.set_default('quota_' + k, v)
|
||||
|
||||
result = self.controller.show(REQ, self.class_name)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_show_quota_not_authorized(self):
|
||||
self.mock_object(
|
||||
quota_class_sets.db,
|
||||
'authorize_quota_class_context',
|
||||
mock.Mock(side_effect=exception.NotAuthorized))
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPForbidden,
|
||||
self.controller.show,
|
||||
REQ, self.class_name)
|
||||
|
||||
def test_update_quota(self):
|
||||
CONF.set_default('quota_shares', 789)
|
||||
body = {
|
||||
'quota_class_set': {
|
||||
'class_name': self.class_name,
|
||||
'shares': 788,
|
||||
}
|
||||
}
|
||||
expected = {
|
||||
'quota_class_set': {
|
||||
'shares': body['quota_class_set']['shares'],
|
||||
'gigabytes': 1000,
|
||||
'snapshots': 50,
|
||||
'snapshot_gigabytes': 1000,
|
||||
'share_networks': 10,
|
||||
}
|
||||
}
|
||||
|
||||
update_result = self.controller.update(
|
||||
REQ, self.class_name, body=body)
|
||||
|
||||
self.assertEqual(expected, update_result)
|
||||
|
||||
show_result = self.controller.show(REQ, self.class_name)
|
||||
|
||||
expected['quota_class_set']['id'] = self.class_name
|
||||
self.assertEqual(expected, show_result)
|
||||
|
||||
def test_update_quota_not_authorized(self):
|
||||
body = {
|
||||
'quota_class_set': {
|
||||
'class_name': self.class_name,
|
||||
'shares': 13,
|
||||
}
|
||||
}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPForbidden,
|
||||
self.controller.update,
|
||||
REQ_MEMBER, self.class_name, body=body)
|
267
manila/tests/api/v1/test_quota_sets.py
Normal file
267
manila/tests/api/v1/test_quota_sets.py
Normal file
@ -0,0 +1,267 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright (c) 2015 Mirantis inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Tests for manila.api.v1.quota_sets.py
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import webob.exc
|
||||
import webob.response
|
||||
|
||||
from manila.api.v1 import quota_sets
|
||||
from manila import context
|
||||
from manila import exception
|
||||
from manila import test
|
||||
from manila import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
REQ = mock.MagicMock()
|
||||
REQ.environ = {'manila.context': context.get_admin_context()}
|
||||
REQ.environ['manila.context'].is_admin = True
|
||||
REQ.environ['manila.context'].auth_token = 'foo_auth_token'
|
||||
REQ.environ['manila.context'].project_id = 'foo_project_id'
|
||||
|
||||
REQ_WITH_USER = copy.deepcopy(REQ)
|
||||
REQ_WITH_USER.environ['manila.context'].user_id = 'foo_user_id'
|
||||
REQ_WITH_USER.environ['QUERY_STRING'] = 'user_id=foo_user_id'
|
||||
|
||||
REQ_MEMBER = copy.deepcopy(REQ)
|
||||
REQ_MEMBER.environ['manila.context'].is_admin = False
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class QuotaSetsControllerTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
self.controller = quota_sets.QuotaSetsController()
|
||||
self.project_id = 'foo_project_id'
|
||||
|
||||
@ddt.data(
|
||||
{"shares": 3, "snapshots": 4, "gigabytes": 5,
|
||||
"snapshot_gigabytes": 6, "share_networks": 7},
|
||||
{"shares": -1, "snapshots": -1, "gigabytes": -1,
|
||||
"snapshot_gigabytes": -1, "share_networks": -1},
|
||||
{"shares": 13},
|
||||
{"snapshots": 24},
|
||||
{"gigabytes": 7},
|
||||
{"snapshot_gigabytes": 10001},
|
||||
{"share_networks": 12345},
|
||||
)
|
||||
def test_defaults(self, quotas):
|
||||
for k, v in quotas.items():
|
||||
CONF.set_default('quota_' + k, v)
|
||||
expected = {
|
||||
'quota_set': {
|
||||
'id': self.project_id,
|
||||
'shares': quotas.get('shares', 50),
|
||||
'gigabytes': quotas.get('gigabytes', 1000),
|
||||
'snapshots': quotas.get('snapshots', 50),
|
||||
'snapshot_gigabytes': quotas.get('snapshot_gigabytes', 1000),
|
||||
'share_networks': quotas.get('share_networks', 10),
|
||||
}
|
||||
}
|
||||
|
||||
result = self.controller.defaults(REQ, self.project_id)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@ddt.data(REQ, REQ_WITH_USER)
|
||||
def test_show_quota(self, request):
|
||||
quotas = {
|
||||
"shares": 23,
|
||||
"snapshots": 34,
|
||||
"gigabytes": 45,
|
||||
"snapshot_gigabytes": 56,
|
||||
"share_networks": 67,
|
||||
}
|
||||
expected = {
|
||||
'quota_set': {
|
||||
'id': self.project_id,
|
||||
'shares': quotas.get('shares', 50),
|
||||
'gigabytes': quotas.get('gigabytes', 1000),
|
||||
'snapshots': quotas.get('snapshots', 50),
|
||||
'snapshot_gigabytes': quotas.get('snapshot_gigabytes', 1000),
|
||||
'share_networks': quotas.get('share_networks', 10),
|
||||
}
|
||||
}
|
||||
for k, v in quotas.items():
|
||||
CONF.set_default('quota_' + k, v)
|
||||
|
||||
result = self.controller.show(request, self.project_id)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_show_quota_not_authorized(self):
|
||||
self.mock_object(
|
||||
quota_sets.db,
|
||||
'authorize_project_context',
|
||||
mock.Mock(side_effect=exception.NotAuthorized))
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPForbidden,
|
||||
self.controller.show,
|
||||
REQ, self.project_id)
|
||||
|
||||
@ddt.data(REQ, REQ_WITH_USER)
|
||||
def test_update_quota(self, request):
|
||||
CONF.set_default('quota_shares', 789)
|
||||
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 788}}
|
||||
expected = {
|
||||
'quota_set': {
|
||||
'shares': body['quota_set']['shares'],
|
||||
'gigabytes': 1000,
|
||||
'snapshots': 50,
|
||||
'snapshot_gigabytes': 1000,
|
||||
'share_networks': 10,
|
||||
}
|
||||
}
|
||||
|
||||
update_result = self.controller.update(
|
||||
request, self.project_id, body=body)
|
||||
|
||||
self.assertEqual(expected, update_result)
|
||||
|
||||
show_result = self.controller.show(request, self.project_id)
|
||||
|
||||
expected['quota_set']['id'] = self.project_id
|
||||
self.assertEqual(expected, show_result)
|
||||
|
||||
@ddt.data(-2, 'foo', {1: 2}, [1])
|
||||
def test_update_quota_with_invalid_value(self, value):
|
||||
body = {'quota_set': {'tenant_id': self.project_id, 'shares': value}}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller.update,
|
||||
REQ, self.project_id, body=body)
|
||||
|
||||
def test_user_quota_can_not_be_bigger_than_tenant_quota(self):
|
||||
value = 777
|
||||
CONF.set_default('quota_shares', value)
|
||||
body = {
|
||||
'quota_set': {
|
||||
'tenant_id': self.project_id,
|
||||
'shares': value + 1,
|
||||
}
|
||||
}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller.update,
|
||||
REQ_WITH_USER, self.project_id, body=body)
|
||||
|
||||
def test_update_inexistent_quota(self):
|
||||
body = {
|
||||
'quota_set': {
|
||||
'tenant_id': self.project_id,
|
||||
'fake_quota': 13,
|
||||
}
|
||||
}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest,
|
||||
self.controller.update,
|
||||
REQ, self.project_id, body=body)
|
||||
|
||||
def test_update_quota_not_authorized(self):
|
||||
body = {'quota_set': {'tenant_id': self.project_id, 'shares': 13}}
|
||||
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPForbidden,
|
||||
self.controller.update,
|
||||
REQ_MEMBER, self.project_id, body=body)
|
||||
|
||||
def test_update_all_quotas_with_force(self):
|
||||
quotas = (
|
||||
('quota_shares', 13),
|
||||
('quota_gigabytes', 14),
|
||||
('quota_snapshots', 15),
|
||||
('quota_snapshot_gigabytes', 16),
|
||||
('quota_share_networks', 17),
|
||||
)
|
||||
for quota, value in quotas:
|
||||
CONF.set_default(quota, value)
|
||||
expected = {
|
||||
'quota_set': {
|
||||
'tenant_id': self.project_id,
|
||||
'shares': quotas[0][1],
|
||||
'gigabytes': quotas[1][1],
|
||||
'snapshots': quotas[2][1],
|
||||
'snapshot_gigabytes': quotas[3][1],
|
||||
'share_networks': quotas[4][1],
|
||||
'force': True,
|
||||
}
|
||||
}
|
||||
|
||||
update_result = self.controller.update(
|
||||
REQ, self.project_id, body=expected)
|
||||
|
||||
expected['quota_set'].pop('force')
|
||||
expected['quota_set'].pop('tenant_id')
|
||||
self.assertEqual(expected, update_result)
|
||||
|
||||
show_result = self.controller.show(REQ, self.project_id)
|
||||
|
||||
expected['quota_set']['id'] = self.project_id
|
||||
self.assertEqual(expected, show_result)
|
||||
|
||||
def test_delete_tenant_quota(self):
|
||||
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project_and_user')
|
||||
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project')
|
||||
|
||||
result = self.controller.delete(REQ, self.project_id)
|
||||
|
||||
self.assertTrue(
|
||||
utils.IsAMatcher(webob.response.Response) == result
|
||||
)
|
||||
self.assertTrue(hasattr(result, 'status_code'))
|
||||
self.assertEqual(202, result.status_code)
|
||||
self.assertFalse(
|
||||
quota_sets.QUOTAS.destroy_all_by_project_and_user.called)
|
||||
quota_sets.QUOTAS.destroy_all_by_project.assert_called_once_with(
|
||||
REQ.environ['manila.context'], self.project_id)
|
||||
|
||||
def test_delete_user_quota(self):
|
||||
project_id = 'foo_project_id'
|
||||
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project_and_user')
|
||||
self.mock_object(quota_sets.QUOTAS, 'destroy_all_by_project')
|
||||
|
||||
result = self.controller.delete(REQ_WITH_USER, project_id)
|
||||
|
||||
self.assertTrue(
|
||||
utils.IsAMatcher(webob.response.Response) == result
|
||||
)
|
||||
self.assertTrue(hasattr(result, 'status_code'))
|
||||
self.assertEqual(202, result.status_code)
|
||||
quota_sets.QUOTAS.destroy_all_by_project_and_user. \
|
||||
assert_called_once_with(
|
||||
REQ_WITH_USER.environ['manila.context'],
|
||||
project_id,
|
||||
REQ_WITH_USER.environ['manila.context'].user_id)
|
||||
self.assertFalse(quota_sets.QUOTAS.destroy_all_by_project.called)
|
||||
|
||||
def test_delete_not_authorized(self):
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPForbidden,
|
||||
self.controller.delete,
|
||||
REQ_MEMBER, self.project_id)
|
@ -6,6 +6,13 @@
|
||||
|
||||
"availability_zone:index": "rule:default",
|
||||
|
||||
"quota_set:update": "rule:admin_api",
|
||||
"quota_set:show": "rule:default",
|
||||
"quota_set:delete": "rule:admin_api",
|
||||
|
||||
"quota_class_set:show": "rule:default",
|
||||
"quota_class_set:update": "rule:admin_api",
|
||||
|
||||
"service:index": "rule:admin_api",
|
||||
"service:update": "rule:admin_api",
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user