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:
Melanie Witt
2013-07-02 22:15:46 +00:00
parent 77a66a248b
commit 0f67835da5
2 changed files with 350 additions and 0 deletions

View 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]

View 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)