Port used limits extension to v3 API Part 1
This changeset only copies the v2 implementation file into the appropriate v3 directory unchanged. The copy as-is will not be loaded by either the v2 or v3 extension loaders. The second changeset will then make the changes required for it to work as a v3 extension. This is being done in order to make reviewing of extension porting easier as gerrit will display only what is actually changed for v3 rather than entirely new files. Partially implements blueprint nova-v3-api Change-Id: I34214370f53d481346eb11fec6b8fd0ddeb42918
This commit is contained in:
99
nova/api/openstack/compute/plugins/v3/used_limits.py
Normal file
99
nova/api/openstack/compute/plugins/v3/used_limits.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# 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 nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova import quota
|
||||
|
||||
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
XMLNS = "http://docs.openstack.org/compute/ext/used_limits/api/v1.1"
|
||||
ALIAS = "os-used-limits"
|
||||
authorize = extensions.soft_extension_authorizer('compute', 'used_limits')
|
||||
authorize_for_admin = extensions.extension_authorizer('compute',
|
||||
'used_limits_for_admin')
|
||||
|
||||
|
||||
class UsedLimitsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('limits', selector='limits')
|
||||
root.set('{%s}usedLimits' % XMLNS, '%s:usedLimits' % ALIAS)
|
||||
return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS})
|
||||
|
||||
|
||||
class UsedLimitsController(wsgi.Controller):
|
||||
|
||||
def __init__(self, ext_mgr):
|
||||
self.ext_mgr = ext_mgr
|
||||
|
||||
@staticmethod
|
||||
def _reserved(req):
|
||||
try:
|
||||
return int(req.GET['reserved'])
|
||||
except (ValueError, KeyError):
|
||||
return False
|
||||
|
||||
@wsgi.extends
|
||||
def index(self, req, resp_obj):
|
||||
resp_obj.attach(xml=UsedLimitsTemplate())
|
||||
context = req.environ['nova.context']
|
||||
project_id = self._project_id(context, req)
|
||||
quotas = QUOTAS.get_project_quotas(context, project_id, usages=True)
|
||||
quota_map = {
|
||||
'totalRAMUsed': 'ram',
|
||||
'totalCoresUsed': 'cores',
|
||||
'totalInstancesUsed': 'instances',
|
||||
'totalFloatingIpsUsed': 'floating_ips',
|
||||
'totalSecurityGroupsUsed': 'security_groups',
|
||||
}
|
||||
used_limits = {}
|
||||
for display_name, quota in quota_map.iteritems():
|
||||
if quota in quotas:
|
||||
reserved = (quotas[quota]['reserved']
|
||||
if self._reserved(req) else 0)
|
||||
used_limits[display_name] = quotas[quota]['in_use'] + reserved
|
||||
|
||||
resp_obj.obj['limits']['absolute'].update(used_limits)
|
||||
|
||||
def _project_id(self, context, req):
|
||||
if self.ext_mgr.is_loaded('os-used-limits-for-admin'):
|
||||
if 'tenant_id' in req.GET:
|
||||
tenant_id = req.GET.get('tenant_id')
|
||||
target = {
|
||||
'project_id': tenant_id,
|
||||
'user_id': context.user_id
|
||||
}
|
||||
authorize_for_admin(context, target=target)
|
||||
return tenant_id
|
||||
return context.project_id
|
||||
|
||||
|
||||
class Used_limits(extensions.ExtensionDescriptor):
|
||||
"""Provide data on limited resources that are being used."""
|
||||
|
||||
name = "UsedLimits"
|
||||
alias = ALIAS
|
||||
namespace = XMLNS
|
||||
updated = "2012-07-13T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = UsedLimitsController(self.ext_mgr)
|
||||
limits_ext = extensions.ControllerExtension(self, 'limits',
|
||||
controller=controller)
|
||||
return [limits_ext]
|
||||
251
nova/tests/api/openstack/compute/plugins/v3/test_used_limits.py
Normal file
251
nova/tests/api/openstack/compute/plugins/v3/test_used_limits.py
Normal file
@@ -0,0 +1,251 @@
|
||||
# vim: tabstop=5 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
# 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 nova.api.openstack.compute.contrib import used_limits
|
||||
from nova.api.openstack.compute import limits
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
import nova.context
|
||||
from nova import exception
|
||||
from nova import quota
|
||||
from nova import test
|
||||
|
||||
|
||||
class FakeRequest(object):
|
||||
def __init__(self, context, reserved=False):
|
||||
self.environ = {'nova.context': context}
|
||||
self.reserved = reserved
|
||||
self.GET = {'reserved': 1} if reserved else {}
|
||||
|
||||
|
||||
class UsedLimitsTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
"""Run before each test."""
|
||||
super(UsedLimitsTestCase, self).setUp()
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = used_limits.UsedLimitsController(self.ext_mgr)
|
||||
|
||||
self.fake_context = nova.context.RequestContext('fake', 'fake')
|
||||
self.mox.StubOutWithMock(used_limits, 'authorize_for_admin')
|
||||
self.authorize_for_admin = used_limits.authorize_for_admin
|
||||
|
||||
def _do_test_used_limits(self, reserved):
|
||||
fake_req = FakeRequest(self.fake_context, reserved=reserved)
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
res = wsgi.ResponseObject(obj)
|
||||
quota_map = {
|
||||
'totalRAMUsed': 'ram',
|
||||
'totalCoresUsed': 'cores',
|
||||
'totalInstancesUsed': 'instances',
|
||||
'totalFloatingIpsUsed': 'floating_ips',
|
||||
'totalSecurityGroupsUsed': 'security_groups',
|
||||
}
|
||||
limits = {}
|
||||
for display_name, q in quota_map.iteritems():
|
||||
limits[q] = {'limit': len(display_name),
|
||||
'in_use': len(display_name) / 2,
|
||||
'reserved': len(display_name) / 3}
|
||||
|
||||
def stub_get_project_quotas(context, project_id, usages=True):
|
||||
return limits
|
||||
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.controller.index(fake_req, res)
|
||||
abs_limits = res.obj['limits']['absolute']
|
||||
for used_limit, value in abs_limits.iteritems():
|
||||
r = limits[quota_map[used_limit]]['reserved'] if reserved else 0
|
||||
self.assertEqual(value,
|
||||
limits[quota_map[used_limit]]['in_use'] + r)
|
||||
|
||||
def test_used_limits_basic(self):
|
||||
self._do_test_used_limits(False)
|
||||
|
||||
def test_used_limits_with_reserved(self):
|
||||
self._do_test_used_limits(True)
|
||||
|
||||
def test_admin_can_fetch_limits_for_a_given_tenant_id(self):
|
||||
project_id = "123456"
|
||||
user_id = "A1234"
|
||||
tenant_id = 'abcd'
|
||||
self.fake_context.project_id = project_id
|
||||
self.fake_context.user_id = user_id
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
target = {
|
||||
"project_id": tenant_id,
|
||||
"user_id": user_id
|
||||
}
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
fake_req.GET = {'tenant_id': tenant_id}
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True)
|
||||
self.authorize_for_admin(self.fake_context, target=target)
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas')
|
||||
quota.QUOTAS.get_project_quotas(self.fake_context, '%s' % tenant_id,
|
||||
usages=True).AndReturn({})
|
||||
self.mox.ReplayAll()
|
||||
res = wsgi.ResponseObject(obj)
|
||||
self.controller.index(fake_req, res)
|
||||
|
||||
def test_admin_can_fetch_used_limits_for_own_project(self):
|
||||
project_id = "123456"
|
||||
user_id = "A1234"
|
||||
self.fake_context.project_id = project_id
|
||||
self.fake_context.user_id = user_id
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
fake_req.GET = {}
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True)
|
||||
self.mox.StubOutWithMock(extensions, 'extension_authorizer')
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas')
|
||||
quota.QUOTAS.get_project_quotas(self.fake_context, '%s' % project_id,
|
||||
usages=True).AndReturn({})
|
||||
self.mox.ReplayAll()
|
||||
res = wsgi.ResponseObject(obj)
|
||||
self.controller.index(fake_req, res)
|
||||
|
||||
def test_non_admin_cannot_fetch_used_limits_for_any_other_project(self):
|
||||
project_id = "123456"
|
||||
user_id = "A1234"
|
||||
tenant_id = "abcd"
|
||||
self.fake_context.project_id = project_id
|
||||
self.fake_context.user_id = user_id
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
target = {
|
||||
"project_id": tenant_id,
|
||||
"user_id": user_id
|
||||
}
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
fake_req.GET = {'tenant_id': tenant_id}
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True)
|
||||
self.authorize_for_admin(self.fake_context, target=target). \
|
||||
AndRaise(exception.PolicyNotAuthorized(
|
||||
action="compute_extension:used_limits_for_admin"))
|
||||
self.mox.ReplayAll()
|
||||
res = wsgi.ResponseObject(obj)
|
||||
self.assertRaises(exception.PolicyNotAuthorized, self.controller.index,
|
||||
fake_req, res)
|
||||
|
||||
def test_used_limits_fetched_for_context_project_id(self):
|
||||
project_id = "123456"
|
||||
self.fake_context.project_id = project_id
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas')
|
||||
quota.QUOTAS.get_project_quotas(self.fake_context, project_id,
|
||||
usages=True).AndReturn({})
|
||||
self.mox.ReplayAll()
|
||||
res = wsgi.ResponseObject(obj)
|
||||
self.controller.index(fake_req, res)
|
||||
|
||||
def test_used_ram_added(self):
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {
|
||||
"maxTotalRAMSize": 512,
|
||||
},
|
||||
},
|
||||
}
|
||||
res = wsgi.ResponseObject(obj)
|
||||
|
||||
def stub_get_project_quotas(context, project_id, usages=True):
|
||||
return {'ram': {'limit': 512, 'in_use': 256}}
|
||||
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.controller.index(fake_req, res)
|
||||
abs_limits = res.obj['limits']['absolute']
|
||||
self.assertTrue('totalRAMUsed' in abs_limits)
|
||||
self.assertEqual(abs_limits['totalRAMUsed'], 256)
|
||||
|
||||
def test_no_ram_quota(self):
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
res = wsgi.ResponseObject(obj)
|
||||
|
||||
def stub_get_project_quotas(context, project_id, usages=True):
|
||||
return {}
|
||||
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.controller.index(fake_req, res)
|
||||
abs_limits = res.obj['limits']['absolute']
|
||||
self.assertFalse('totalRAMUsed' in abs_limits)
|
||||
|
||||
def test_used_limits_xmlns(self):
|
||||
fake_req = FakeRequest(self.fake_context)
|
||||
obj = {
|
||||
"limits": {
|
||||
"rate": [],
|
||||
"absolute": {},
|
||||
},
|
||||
}
|
||||
res = wsgi.ResponseObject(obj, xml=limits.LimitsTemplate)
|
||||
res.preserialize('xml')
|
||||
|
||||
def stub_get_project_quotas(context, project_id, usages=True):
|
||||
return {}
|
||||
|
||||
self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False)
|
||||
self.stubs.Set(quota.QUOTAS, "get_project_quotas",
|
||||
stub_get_project_quotas)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.controller.index(fake_req, res)
|
||||
response = res.serialize(None, 'xml')
|
||||
self.assertTrue(used_limits.XMLNS in response.body)
|
||||
Reference in New Issue
Block a user