Merge "Pecan: fix quota management"
This commit is contained in:
@@ -25,11 +25,11 @@ from neutron.api.v2 import resource
|
||||
from neutron.common import constants as const
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron import manager
|
||||
from neutron.pecan_wsgi import controllers
|
||||
from neutron import quota
|
||||
from neutron.quota import resource_registry
|
||||
from neutron import wsgi
|
||||
|
||||
|
||||
RESOURCE_NAME = 'quota'
|
||||
RESOURCE_COLLECTION = RESOURCE_NAME + "s"
|
||||
QUOTAS = quota.QUOTAS
|
||||
@@ -145,6 +145,10 @@ class Quotasv2(extensions.ExtensionDescriptor):
|
||||
controller,
|
||||
collection_actions={'tenant': 'GET'})]
|
||||
|
||||
@classmethod
|
||||
def get_pecan_controllers(cls):
|
||||
return ((RESOURCE_COLLECTION, controllers.QuotasController()), )
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return EXTENDED_ATTRIBUTES_2_0
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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 neutron.pecan_wsgi.controllers import quota
|
||||
|
||||
|
||||
QuotasController = quota.QuotasController
|
||||
|
||||
128
neutron/pecan_wsgi/controllers/quota.py
Normal file
128
neutron/pecan_wsgi/controllers/quota.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) 2015 Taturiello Consulting, Meh.
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
from pecan import request
|
||||
from pecan import response
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import constants
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.pecan_wsgi.controllers import utils
|
||||
from neutron.quota import resource_registry
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
RESOURCE_NAME = "quota"
|
||||
|
||||
|
||||
class QuotasController(utils.NeutronPecanController):
|
||||
|
||||
def __init__(self):
|
||||
self._driver = importutils.import_class(
|
||||
cfg.CONF.QUOTAS.quota_driver
|
||||
)
|
||||
super(QuotasController, self).__init__(
|
||||
"%ss" % RESOURCE_NAME, RESOURCE_NAME)
|
||||
|
||||
def _check_admin(self, context,
|
||||
reason=_("Only admin can view or configure quota")):
|
||||
if not context.is_admin:
|
||||
raise n_exc.AdminRequired(reason=reason)
|
||||
|
||||
@utils.expose()
|
||||
def _lookup(self, tenant_id, *remainder):
|
||||
return QuotaController(self._driver, tenant_id), remainder
|
||||
|
||||
@utils.expose()
|
||||
def index(self):
|
||||
neutron_context = request.context.get('neutron_context')
|
||||
# FIXME(salv-orlando): There shouldn't be any need to to this eplicit
|
||||
# check. However some behaviours from the "old" extension have
|
||||
# been temporarily carried over here
|
||||
self._check_admin(neutron_context)
|
||||
# TODO(salv-orlando): proper plurals management
|
||||
return {self.collection:
|
||||
self._driver.get_all_quotas(
|
||||
neutron_context,
|
||||
resource_registry.get_all_resources())}
|
||||
|
||||
|
||||
class QuotaController(utils.NeutronPecanController):
|
||||
|
||||
def __init__(self, _driver, tenant_id):
|
||||
self._driver = _driver
|
||||
self._tenant_id = tenant_id
|
||||
|
||||
super(QuotaController, self).__init__(
|
||||
"%ss" % RESOURCE_NAME, RESOURCE_NAME)
|
||||
|
||||
# Ensure limits for all registered resources are returned
|
||||
attr_dict = attributes.RESOURCE_ATTRIBUTE_MAP[self.collection]
|
||||
for quota_resource in resource_registry.get_all_resources().keys():
|
||||
attr_dict[quota_resource] = {
|
||||
'allow_post': False,
|
||||
'allow_put': True,
|
||||
'convert_to': attributes.convert_to_int,
|
||||
'validate': {
|
||||
'type:range': [-1, constants.DB_INTEGER_MAX_VALUE]},
|
||||
'is_visible': True}
|
||||
|
||||
@utils.expose(generic=True)
|
||||
def index(self):
|
||||
return get_tenant_quotas(self._tenant_id, self._driver)
|
||||
|
||||
@utils.when(index, method='PUT')
|
||||
def put(self, *args, **kwargs):
|
||||
neutron_context = request.context.get('neutron_context')
|
||||
# For put requests there's always going to be a single element
|
||||
quota_data = request.context['resources'][0]
|
||||
for key, value in quota_data.items():
|
||||
self._driver.update_quota_limit(
|
||||
neutron_context, self._tenant_id, key, value)
|
||||
return get_tenant_quotas(self._tenant_id, self._driver)
|
||||
|
||||
@utils.when(index, method='DELETE')
|
||||
def delete(self):
|
||||
neutron_context = request.context.get('neutron_context')
|
||||
self._driver.delete_tenant_quota(neutron_context,
|
||||
self._tenant_id)
|
||||
response.status = 204
|
||||
|
||||
|
||||
def get_tenant_quotas(tenant_id, driver=None):
|
||||
if not driver:
|
||||
driver = importutils.import_class(cfg.CONF.QUOTAS.quota_driver)
|
||||
|
||||
neutron_context = request.context.get('neutron_context')
|
||||
if tenant_id == 'tenant':
|
||||
# NOTE(salv-orlando): Read the following before the code in order
|
||||
# to avoid puking.
|
||||
# There is a weird undocumented behaviour of the Neutron quota API
|
||||
# as 'tenant' is used as an API action to return the identifier
|
||||
# of the tenant in the request context. This is used exclusively
|
||||
# for interaction with python-neutronclient and is a possibly
|
||||
# unnecessary 'whoami' API endpoint. Pending resolution of this
|
||||
# API issue, this controller will just treat the magic string
|
||||
# 'tenant' (and only that string) and return the response expected
|
||||
# by python-neutronclient
|
||||
return {'tenant': {'tenant_id': neutron_context.tenant_id}}
|
||||
tenant_quotas = driver.get_tenant_quotas(
|
||||
neutron_context,
|
||||
resource_registry.get_all_resources(),
|
||||
tenant_id)
|
||||
tenant_quotas['tenant_id'] = tenant_id
|
||||
return {RESOURCE_NAME: tenant_quotas}
|
||||
@@ -22,6 +22,7 @@ from neutron._i18n import _, _LW
|
||||
from neutron.api import extensions
|
||||
from neutron.api.views import versions as versions_view
|
||||
from neutron import manager
|
||||
from neutron.pecan_wsgi.controllers import utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
_VERSION_INFO = {}
|
||||
@@ -36,44 +37,30 @@ def _get_version_info():
|
||||
return _VERSION_INFO.values()
|
||||
|
||||
|
||||
def expose(*args, **kwargs):
|
||||
"""Helper function so we don't have to specify json for everything."""
|
||||
kwargs.setdefault('content_type', 'application/json')
|
||||
kwargs.setdefault('template', 'json')
|
||||
return pecan.expose(*args, **kwargs)
|
||||
|
||||
|
||||
def when(index, *args, **kwargs):
|
||||
"""Helper function so we don't have to specify json for everything."""
|
||||
kwargs.setdefault('content_type', 'application/json')
|
||||
kwargs.setdefault('template', 'json')
|
||||
return index.when(*args, **kwargs)
|
||||
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose(generic=True)
|
||||
@utils.expose(generic=True)
|
||||
def index(self):
|
||||
builder = versions_view.get_view_builder(pecan.request)
|
||||
versions = [builder.build(version) for version in _get_version_info()]
|
||||
return dict(versions=versions)
|
||||
|
||||
@when(index, method='HEAD')
|
||||
@when(index, method='POST')
|
||||
@when(index, method='PATCH')
|
||||
@when(index, method='PUT')
|
||||
@when(index, method='DELETE')
|
||||
@utils.when(index, method='HEAD')
|
||||
@utils.when(index, method='POST')
|
||||
@utils.when(index, method='PATCH')
|
||||
@utils.when(index, method='PUT')
|
||||
@utils.when(index, method='DELETE')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
||||
|
||||
class ExtensionsController(object):
|
||||
|
||||
@expose()
|
||||
@utils.expose()
|
||||
def _lookup(self, alias, *remainder):
|
||||
return ExtensionController(alias), remainder
|
||||
|
||||
@expose()
|
||||
@utils.expose()
|
||||
def index(self):
|
||||
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
|
||||
exts = [extensions.ExtensionController._translate(ext)
|
||||
@@ -93,20 +80,20 @@ class V2Controller(object):
|
||||
|
||||
extensions = ExtensionsController()
|
||||
|
||||
@expose(generic=True)
|
||||
@utils.expose(generic=True)
|
||||
def index(self):
|
||||
builder = versions_view.get_view_builder(pecan.request)
|
||||
return dict(version=builder.build(self.version_info))
|
||||
|
||||
@when(index, method='HEAD')
|
||||
@when(index, method='POST')
|
||||
@when(index, method='PATCH')
|
||||
@when(index, method='PUT')
|
||||
@when(index, method='DELETE')
|
||||
@utils.when(index, method='HEAD')
|
||||
@utils.when(index, method='POST')
|
||||
@utils.when(index, method='PATCH')
|
||||
@utils.when(index, method='PUT')
|
||||
@utils.when(index, method='DELETE')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
||||
@expose()
|
||||
@utils.expose()
|
||||
def _lookup(self, collection, *remainder):
|
||||
controller = manager.NeutronManager.get_controller_for_resource(
|
||||
collection)
|
||||
@@ -131,7 +118,7 @@ class ExtensionController(object):
|
||||
def __init__(self, alias):
|
||||
self.alias = alias
|
||||
|
||||
@expose()
|
||||
@utils.expose()
|
||||
def index(self):
|
||||
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
|
||||
ext = ext_mgr.extensions.get(self.alias, None)
|
||||
@@ -142,24 +129,15 @@ class ExtensionController(object):
|
||||
return {'extension': extensions.ExtensionController._translate(ext)}
|
||||
|
||||
|
||||
class NeutronPecanController(object):
|
||||
class CollectionsController(utils.NeutronPecanController):
|
||||
|
||||
def __init__(self, collection, resource):
|
||||
self.collection = collection
|
||||
self.resource = resource
|
||||
self.plugin = manager.NeutronManager.get_plugin_for_resource(
|
||||
self.resource)
|
||||
|
||||
|
||||
class CollectionsController(NeutronPecanController):
|
||||
|
||||
@expose()
|
||||
@utils.expose()
|
||||
def _lookup(self, item, *remainder):
|
||||
# Store resource identifier in request context
|
||||
request.context['resource_id'] = item
|
||||
return ItemController(self.resource, item), remainder
|
||||
|
||||
@expose(generic=True)
|
||||
@utils.expose(generic=True)
|
||||
def index(self, *args, **kwargs):
|
||||
return self.get(*args, **kwargs)
|
||||
|
||||
@@ -175,14 +153,14 @@ class CollectionsController(NeutronPecanController):
|
||||
neutron_context = request.context['neutron_context']
|
||||
return {self.collection: lister(neutron_context, filters=filters)}
|
||||
|
||||
@when(index, method='HEAD')
|
||||
@when(index, method='PATCH')
|
||||
@when(index, method='PUT')
|
||||
@when(index, method='DELETE')
|
||||
@utils.when(index, method='HEAD')
|
||||
@utils.when(index, method='PATCH')
|
||||
@utils.when(index, method='PUT')
|
||||
@utils.when(index, method='DELETE')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
||||
@when(index, method='POST')
|
||||
@utils.when(index, method='POST')
|
||||
def post(self, *args, **kwargs):
|
||||
# TODO(kevinbenton): emulated bulk!
|
||||
resources = request.context['resources']
|
||||
@@ -204,13 +182,13 @@ class CollectionsController(NeutronPecanController):
|
||||
return {key: creator(neutron_context, data)}
|
||||
|
||||
|
||||
class ItemController(NeutronPecanController):
|
||||
class ItemController(utils.NeutronPecanController):
|
||||
|
||||
def __init__(self, resource, item):
|
||||
super(ItemController, self).__init__(None, resource)
|
||||
self.item = item
|
||||
|
||||
@expose(generic=True)
|
||||
@utils.expose(generic=True)
|
||||
def index(self, *args, **kwargs):
|
||||
return self.get()
|
||||
|
||||
@@ -219,13 +197,13 @@ class ItemController(NeutronPecanController):
|
||||
neutron_context = request.context['neutron_context']
|
||||
return {self.resource: getter(neutron_context, self.item)}
|
||||
|
||||
@when(index, method='HEAD')
|
||||
@when(index, method='POST')
|
||||
@when(index, method='PATCH')
|
||||
@utils.when(index, method='HEAD')
|
||||
@utils.when(index, method='POST')
|
||||
@utils.when(index, method='PATCH')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
||||
@when(index, method='PUT')
|
||||
@utils.when(index, method='PUT')
|
||||
def put(self, *args, **kwargs):
|
||||
neutron_context = request.context['neutron_context']
|
||||
resources = request.context['resources']
|
||||
@@ -241,7 +219,7 @@ class ItemController(NeutronPecanController):
|
||||
data = {self.resource: resources[0]}
|
||||
return updater(neutron_context, self.item, data)
|
||||
|
||||
@when(index, method='DELETE')
|
||||
@utils.when(index, method='DELETE')
|
||||
def delete(self):
|
||||
# TODO(kevinbenton): setting code could be in a decorator
|
||||
pecan.response.status = 204
|
||||
|
||||
43
neutron/pecan_wsgi/controllers/utils.py
Normal file
43
neutron/pecan_wsgi/controllers/utils.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2015 Taturiello Consulting, Meh.
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
|
||||
from neutron import manager
|
||||
|
||||
# Utility functions for Pecan controllers.
|
||||
|
||||
|
||||
def expose(*args, **kwargs):
|
||||
"""Helper function so we don't have to specify json for everything."""
|
||||
kwargs.setdefault('content_type', 'application/json')
|
||||
kwargs.setdefault('template', 'json')
|
||||
return pecan.expose(*args, **kwargs)
|
||||
|
||||
|
||||
def when(index, *args, **kwargs):
|
||||
"""Helper function so we don't have to specify json for everything."""
|
||||
kwargs.setdefault('content_type', 'application/json')
|
||||
kwargs.setdefault('template', 'json')
|
||||
return index.when(*args, **kwargs)
|
||||
|
||||
|
||||
class NeutronPecanController(object):
|
||||
|
||||
def __init__(self, collection, resource):
|
||||
self.collection = collection
|
||||
self.resource = resource
|
||||
self.plugin = manager.NeutronManager.get_plugin_for_resource(
|
||||
self.resource)
|
||||
@@ -24,10 +24,18 @@ import webob
|
||||
from neutron._i18n import _
|
||||
from neutron.api.v2 import attributes as v2_attributes
|
||||
from neutron.common import constants as const
|
||||
from neutron.extensions import quotasv2
|
||||
from neutron import manager
|
||||
from neutron.pecan_wsgi.controllers import quota
|
||||
from neutron import policy
|
||||
|
||||
|
||||
def _custom_getter(resource, resource_id):
|
||||
"""Helper function to retrieve resources not served by any plugin."""
|
||||
if resource == quotasv2.RESOURCE_NAME:
|
||||
return quota.get_tenant_quotas(resource_id)[quotasv2.RESOURCE_NAME]
|
||||
|
||||
|
||||
class PolicyHook(hooks.PecanHook):
|
||||
priority = 135
|
||||
ACTION_MAP = {'POST': 'create', 'PUT': 'update', 'GET': 'get',
|
||||
@@ -39,9 +47,15 @@ class PolicyHook(hooks.PecanHook):
|
||||
if (value.get('required_by_policy') or
|
||||
value.get('primary_key') or 'default' not in value)]
|
||||
plugin = manager.NeutronManager.get_plugin_for_resource(resource)
|
||||
getter = getattr(plugin, 'get_%s' % resource)
|
||||
# TODO(kevinbenton): the parent_id logic currently in base.py
|
||||
return getter(neutron_context, resource_id, fields=field_list)
|
||||
if plugin:
|
||||
getter = getattr(plugin, 'get_%s' % resource)
|
||||
# TODO(kevinbenton): the parent_id logic currently in base.py
|
||||
return getter(neutron_context, resource_id, fields=field_list)
|
||||
else:
|
||||
# Some legit resources, like quota, do not have a plugin yet.
|
||||
# Retrieving the original object is nevertheless important
|
||||
# for policy checks.
|
||||
return _custom_getter(resource, resource_id)
|
||||
|
||||
def before(self, state):
|
||||
# This hook should be run only for PUT,POST and DELETE methods and for
|
||||
|
||||
@@ -74,8 +74,8 @@ def initialize_all():
|
||||
hasattr(ext, 'get_pecan_controllers')]
|
||||
pecan_controllers = {}
|
||||
for ext in pecanized_exts:
|
||||
LOG.debug("Extension %s is pecan-enabled. Fetching resources "
|
||||
"and controllers", ext.get_name())
|
||||
LOG.info(_LI("Extension %s is pecan-aware. Fetching resources "
|
||||
"and controllers"), ext.get_name())
|
||||
controllers = ext.get_pecan_controllers()
|
||||
# controllers is actually a list of pairs where the first element is
|
||||
# the collection name and the second the actual controller
|
||||
@@ -83,24 +83,28 @@ def initialize_all():
|
||||
pecan_controllers[collection] = coll_controller
|
||||
|
||||
for collection in attributes.RESOURCE_ATTRIBUTE_MAP:
|
||||
if collection not in pecan_controllers:
|
||||
resource = _handle_plurals(collection)
|
||||
resource = _handle_plurals(collection)
|
||||
controller = pecan_controllers.get(collection)
|
||||
if not controller:
|
||||
LOG.debug("Building controller for resource:%s", resource)
|
||||
plugin = _plugin_for_resource(collection)
|
||||
if plugin:
|
||||
manager.NeutronManager.set_plugin_for_resource(
|
||||
resource, plugin)
|
||||
else:
|
||||
LOG.warn(_LW("No plugin found for resource:%s. API calls "
|
||||
"may not be correctly dispatched"), resource)
|
||||
controller = root.CollectionsController(collection, resource)
|
||||
manager.NeutronManager.set_controller_for_resource(
|
||||
collection, controller)
|
||||
LOG.info(_LI("Added controller for resource %(resource)s "
|
||||
"via URI path segment:%(collection)s"),
|
||||
{'resource': resource,
|
||||
'collection': collection})
|
||||
else:
|
||||
LOG.debug("There are already controllers for resource:%s",
|
||||
resource)
|
||||
|
||||
manager.NeutronManager.set_controller_for_resource(
|
||||
collection, controller)
|
||||
LOG.info(_LI("Added controller for resource %(resource)s "
|
||||
"via URI path segment:%(collection)s"),
|
||||
{'resource': resource,
|
||||
'collection': collection})
|
||||
# NOTE(salv-orlando): If you are care about code quality, please read below
|
||||
# Hackiness is strong with the piece of code below. It is used for
|
||||
# populating resource plurals and registering resources with the quota
|
||||
|
||||
@@ -462,3 +462,89 @@ class TestRootController(PecanFunctionalTest):
|
||||
|
||||
def test_head(self):
|
||||
self._test_method_returns_405('head')
|
||||
|
||||
|
||||
class TestQuotasController(TestRootController):
|
||||
"""Test quota management API controller."""
|
||||
|
||||
base_url = '/v2.0/quotas'
|
||||
default_expected_limits = {
|
||||
'network': 10,
|
||||
'port': 50,
|
||||
'subnet': 10}
|
||||
|
||||
def _verify_limits(self, response, limits):
|
||||
for resource, limit in limits.items():
|
||||
self.assertEqual(limit, response['quota'][resource])
|
||||
|
||||
def _verify_default_limits(self, response):
|
||||
self._verify_limits(response, self.default_expected_limits)
|
||||
|
||||
def _verify_after_update(self, response, updated_limits):
|
||||
expected_limits = self.default_expected_limits.copy()
|
||||
expected_limits.update(updated_limits)
|
||||
self._verify_limits(response, expected_limits)
|
||||
|
||||
def test_index_admin(self):
|
||||
# NOTE(salv-orlando): The quota controller has an hardcoded check for
|
||||
# admin-ness for this operation, which is supposed to return quotas for
|
||||
# all tenants. Such check is "vestigial" from the home-grown WSGI and
|
||||
# shall be removed
|
||||
response = self.app.get('%s.json' % self.base_url,
|
||||
headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
||||
def test_index(self):
|
||||
response = self.app.get('%s.json' % self.base_url, expect_errors=True)
|
||||
self.assertEqual(403, response.status_int)
|
||||
|
||||
def test_get_admin(self):
|
||||
response = self.app.get('%s/foo.json' % self.base_url,
|
||||
headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
# As quota limits have not been updated, expect default values
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self._verify_default_limits(json_body)
|
||||
|
||||
def test_get(self):
|
||||
# It is not ok to access another tenant's limits
|
||||
url = '%s/foo.json' % self.base_url
|
||||
response = self.app.get(url, expect_errors=True)
|
||||
self.assertEqual(403, response.status_int)
|
||||
# It is however ok to retrieve your own limits
|
||||
response = self.app.get(url, headers={'X-Project-Id': 'foo'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self._verify_default_limits(json_body)
|
||||
|
||||
def test_put_get_delete(self):
|
||||
# PUT and DELETE actions are in the same test as a meaningful DELETE
|
||||
# test would require a put anyway
|
||||
url = '%s/foo.json' % self.base_url
|
||||
response = self.app.put_json(url,
|
||||
params={'quota': {'network': 99}},
|
||||
headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self._verify_after_update(json_body, {'network': 99})
|
||||
|
||||
response = self.app.get(url, headers={'X-Project-Id': 'foo'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self._verify_after_update(json_body, {'network': 99})
|
||||
|
||||
response = self.app.delete(url, headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
self.assertEqual(204, response.status_int)
|
||||
# As DELETE does not return a body we need another GET
|
||||
response = self.app.get(url, headers={'X-Project-Id': 'foo'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self._verify_default_limits(json_body)
|
||||
|
||||
def test_delete(self):
|
||||
# TODO(salv-orlando)
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user