Merge "Add Extensions to the v2 API"

This commit is contained in:
Jenkins 2014-06-27 03:07:33 +00:00 committed by Gerrit Code Review
commit 3494e43a2b
13 changed files with 335 additions and 1 deletions

View File

@ -23,7 +23,7 @@
"*.class",
"*.psd",
"*.db",
".vagrant",
".vagrant"
],
"folder_exclude_patterns":
[

View File

@ -22,6 +22,12 @@ from designate.openstack.common import log as logging
LOG = logging.getLogger(__name__)
OPTS = [
cfg.ListOpt('enabled-extensions-v2', default=[],
help='Enabled API Extensions'),
]
cfg.CONF.register_opts(OPTS, group='service:api')
def factory(global_config, **local_conf):
if not cfg.CONF['service:api'].enable_api_v2:

View File

@ -0,0 +1,77 @@
# COPYRIGHT 2014 Rackspace
#
# Author: Tim Simmons <tim.simmons@rackspace.com>
#
# 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 designate.openstack.common import log as logging
from designate import schema
from designate.api.v2.controllers import rest
from designate.api.v2.views.extensions import quotas as quotas_view
LOG = logging.getLogger(__name__)
class QuotasController(rest.RestController):
_view = quotas_view.QuotasView()
_resource_schema = schema.Schema('v2', 'quota')
@staticmethod
def get_path():
return '.quotas'
@pecan.expose(template='json:', content_type='application/json')
def get_one(self, tenant_id):
request = pecan.request
context = pecan.request.environ['context']
quotas = self.central_api.get_quotas(context, tenant_id)
return self._view.show(context, request, quotas)
@pecan.expose(template='json:', content_type='application/json')
def patch_one(self, tenant_id):
""" Modify a Quota """
request = pecan.request
response = pecan.response
context = request.environ['context']
body = request.body_dict
# Validate the request conforms to the schema
self._resource_schema.validate(body)
values = self._view.load(context, request, body)
for resource, hard_limit in values.iteritems():
self.central_api.set_quota(context, tenant_id, resource,
hard_limit)
response.status_int = 200
quotas = self.central_api.get_quotas(context, tenant_id)
return self._view.show(context, request, quotas)
@pecan.expose(template=None, content_type='application/json')
def delete_one(self, tenant_id):
""" Reset to the Default Quotas """
request = pecan.request
response = pecan.response
context = request.environ['context']
self.central_api.reset_quotas(context, tenant_id)
response.status_int = 204
return ''

View File

@ -13,6 +13,9 @@
# 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 stevedore import named
from designate import exceptions
from designate.openstack.common import log as logging
from designate.api.v2.controllers import limits
@ -32,6 +35,22 @@ class RootController(object):
This is /v2/ Controller. Pecan will find all controllers via the object
properties attached to this.
"""
def __init__(self):
enabled_ext = cfg.CONF['service:api'].enabled_extensions_v2
if len(enabled_ext) > 0:
self._mgr = named.NamedExtensionManager(
namespace='designate.api.v2.extensions',
names=enabled_ext,
invoke_on_load=True)
for ext in self._mgr:
controller = self
path = ext.obj.get_path()
for p in path.split('.')[:-1]:
if p != '':
controller = getattr(controller, p)
setattr(controller, path.split('.')[-1], ext.obj)
limits = limits.LimitsController()
schemas = schemas.SchemasController()
reverse = reverse.ReverseController()

View File

@ -0,0 +1,55 @@
# COPYRIGHT 2014 Rackspace
#
# Author: Tim Simmons <tim.simmons@rackspace.com>
#
# 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 designate.api.v2.views import base as base_view
from designate.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class QuotasView(base_view.BaseView):
""" Model a Quota API response as a python dictionary """
_resource_name = 'quota'
_collection_name = 'quotas'
def show_basic(self, context, request, quota):
""" Basic view of a quota """
return {
"zones": quota['domains'],
"zone_records": quota['domain_records'],
"zone_recordsets": quota['domain_recordsets'],
"recordset_records": quota['recordset_records']
}
def load(self, context, request, body):
""" Extract a "central" compatible dict from an API call """
valid_keys = ('domain_records', 'domain_recordsets', 'domains',
'recordset_records')
quota = body["quota"]
old_keys = {
'zones': 'domains',
'zone_records': 'domain_records',
'zone_recordsets': 'domain_recordsets',
'recordset_records': 'recordset_records'
}
for key in quota:
quota[old_keys[key]] = quota.pop(key)
return self._load(context, request, body, valid_keys)

View File

@ -313,6 +313,9 @@ class Service(service.Service):
target = {'tenant_id': tenant_id}
policy.check('get_quotas', context, target)
# This allows admins to get quota information correctly for all tenants
context.all_tenants = True
return self.quota.get_quotas(context, tenant_id)
def get_quota(self, context, tenant_id, resource):

View File

@ -0,0 +1,47 @@
{
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"id": "quota",
"title": "quota",
"description": "quota",
"additionalProperties": false,
"required": [
"quota"
],
"properties": {
"quota": {
"type": "object",
"additionalProperties": false,
"required": [],
"properties": {
"zones": {
"type": "integer",
"description": "Number of zones allowed",
"min": 0,
"max": 2147483647,
"default": 10
},
"zone_recordsets": {
"type": "integer",
"description": "Number of zone recordsets allowed",
"min": 0,
"max": 2147483647,
"default": 500
},
"zone_records": {
"type": "integer",
"description": "Number of zone records allowed",
"min": 0,
"max": 2147483647,
"default": 500
},
"recordset_records": {
"type": "integer",
"description": "Number of recordset records allowed",
"min": 0,
"max": 2147483647,
"default": 20
}
}
}
}
}

View File

@ -0,0 +1,121 @@
# coding=utf-8
# COPYRIGHT 2014 Rackspace
#
# Author: Tim Simmons <tim.simmons@rackspace.com>
#
# 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 designate.tests.test_api.test_v2 import ApiV2TestCase
cfg.CONF.import_opt('enabled_extensions_v2', 'designate.api.v2',
group='service:api')
class ApiV2QuotasTest(ApiV2TestCase):
def setUp(self):
self.config(enabled_extensions_v2=['quotas'], group='service:api')
super(ApiV2QuotasTest, self).setUp()
def test_get_quotas(self):
self.policy({'get_quotas': '@'})
context = self.get_admin_context()
response = self.client.get('/quotas/%s' % context.tenant,
headers={'X-Test-Tenant-Id':
context.tenant})
self.assertEqual(200, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertIn('quota', response.json)
self.assertIn('zones', response.json['quota'])
self.assertIn('zone_records', response.json['quota'])
self.assertIn('zone_recordsets', response.json['quota'])
self.assertIn('recordset_records', response.json['quota'])
max_zones = response.json['quota']['zones']
max_zone_records = response.json['quota']['zone_records']
self.assertEqual(cfg.CONF.quota_domains, max_zones)
self.assertEqual(cfg.CONF.quota_domain_records, max_zone_records)
def test_patch_quotas(self):
self.policy({'set_quotas': '@'})
context = self.get_context(tenant='a', is_admin=True)
response = self.client.get('/quotas/%s' % 'a',
headers={'X-Test-Tenant-Id':
context.tenant})
self.assertEqual(200, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertIn('quota', response.json)
self.assertIn('zones', response.json['quota'])
current_count = response.json['quota']['zones']
body = {'quota': {"zones": 1337}}
response = self.client.patch_json('/quotas/%s' % 'a', body,
status=200,
headers={'X-Test-Tenant-Id':
context.tenant})
self.assertEqual(200, response.status_int)
response = self.client.get('/quotas/%s' % 'a',
headers={'X-Test-Tenant-Id':
context.tenant})
new_count = response.json['quota']['zones']
self.assertNotEqual(current_count, new_count)
def test_reset_quotas(self):
self.policy({'reset_quotas': '@'})
context = self.get_context(tenant='a', is_admin=True)
response = self.client.get('/quotas/%s' % 'a',
headers={'X-Test-Tenant-Id':
context.tenant})
self.assertEqual(200, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertIn('quota', response.json)
self.assertIn('zones', response.json['quota'])
current_count = response.json['quota']['zones']
body = {'quota': {"zones": 1337}}
response = self.client.patch_json('/quotas/%s' % 'a', body,
status=200,
headers={'X-Test-Tenant-Id':
context.tenant})
self.assertEqual(200, response.status_int)
response = self.client.get('/quotas/%s' % 'a',
headers={'X-Test-Tenant-Id':
context.tenant})
new_count = response.json['quota']['zones']
self.assertNotEqual(current_count, new_count)
response = self.client.delete('/quotas/%s' % 'a',
headers={'X-Test-Tenant-Id':
context.tenant}, status=204)
response = self.client.get('/quotas/%s' % 'a',
headers={'X-Test-Tenant-Id':
context.tenant})
newest_count = response.json['quota']['zones']
self.assertNotEqual(new_count, newest_count)
self.assertEqual(current_count, newest_count)

View File

@ -90,6 +90,9 @@ debug = False
# Enabled API Version 1 extensions
#enabled_extensions_v1 = diagnostics, quotas, reports, sync, touch
# Enabled API Version 2 extensions
#enabled_extensions_v2 = quotas
#-----------------------
# Keystone Middleware
#-----------------------

View File

@ -52,6 +52,9 @@ designate.api.v1.extensions =
reports = designate.api.v1.extensions.reports:blueprint
touch = designate.api.v1.extensions.touch:blueprint
designate.api.v2.extensions =
quotas = designate.api.v2.controllers.extensions.quotas:QuotasController
designate.storage =
sqlalchemy = designate.storage.impl_sqlalchemy:SQLAlchemyStorage