Merge "Return Customer's Quota Usage through Admin API"

This commit is contained in:
Jenkins 2013-06-11 20:39:00 +00:00 committed by Gerrit Code Review
commit 00d0e2c187
15 changed files with 480 additions and 7 deletions

View File

@ -520,6 +520,14 @@
"namespace": "http://docs.openstack.org/compute/ext/used_limits/api/v1.1",
"updated": "2012-07-13T00:00:00+00:00"
},
{
"alias": "os-used-limits-for-admin",
"description": "Provide data to admin on limited resources used by other tenants.",
"links": [],
"name": "UsedLimitsForAdmin",
"namespace": "http://docs.openstack.org/compute/ext/used_limits_for_admin/api/v1.1",
"updated": "2013-05-02T00:00:00+00:00"
},
{
"alias": "os-user-data",
"description": "Add user_data to the Create Server v1.1 API.",

View File

@ -213,6 +213,9 @@
<extension alias="os-used-limits" updated="2012-07-13T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/used_limits/api/v1.1" name="UsedLimits">
<description>Provide data on limited resources that are being used.</description>
</extension>
<extension alias="os-used-limits-for-admin" updated="2013-05-02T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/used_limits_for_admin/api/v1.1" name="UsedLimitsForAdmin">
<description>Provide data to admin on limited resources used by other tenants.</description>
</extension>
<extension alias="os-user-data" updated="2012-08-07T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/userdata/api/v1.1" name="UserData">
<description>Add user_data to the Create Server v1.1 API.</description>
</extension>

View File

@ -0,0 +1,90 @@
{
"limits": {
"absolute": {
"maxImageMeta": 128,
"maxPersonality": 5,
"maxPersonalitySize": 10240,
"maxSecurityGroupRules": 20,
"maxSecurityGroups": 10,
"maxServerMeta": 128,
"maxTotalCores": 20,
"maxTotalFloatingIps": 10,
"maxTotalInstances": 10,
"maxTotalKeypairs": 100,
"maxTotalRAMSize": 51200,
"totalCoresUsed": 0,
"totalInstancesUsed": 0,
"totalRAMUsed": 0,
"totalSecurityGroupsUsed": 0,
"totalFloatingIpsUsed": 0
},
"rate": [
{
"limit": [
{
"next-available": "2012-11-27T17:24:52Z",
"remaining": 10,
"unit": "MINUTE",
"value": 10,
"verb": "POST"
},
{
"next-available": "2012-11-27T17:24:52Z",
"remaining": 10,
"unit": "MINUTE",
"value": 10,
"verb": "PUT"
},
{
"next-available": "2012-11-27T17:24:52Z",
"remaining": 100,
"unit": "MINUTE",
"value": 100,
"verb": "DELETE"
}
],
"regex": ".*",
"uri": "*"
},
{
"limit": [
{
"next-available": "2012-11-27T17:24:52Z",
"remaining": 50,
"unit": "DAY",
"value": 50,
"verb": "POST"
}
],
"regex": "^/servers",
"uri": "*/servers"
},
{
"limit": [
{
"next-available": "2012-11-27T17:24:52Z",
"remaining": 3,
"unit": "MINUTE",
"value": 3,
"verb": "GET"
}
],
"regex": ".*changes-since.*",
"uri": "*changes-since*"
},
{
"limit": [
{
"next-available": "2012-11-27T17:24:52Z",
"remaining": 12,
"unit": "HOUR",
"value": 12,
"verb": "GET"
}
],
"regex": "^/os-fping",
"uri": "*/os-fping"
}
]
}
}

View File

@ -0,0 +1,37 @@
<?xml version='1.0' encoding='UTF-8'?>
<limits xmlns:os-used-limits="http://docs.openstack.org/compute/ext/used_limits/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/common/api/v1.0">
<rates>
<rate regex=".*" uri="*">
<limit next-available="2012-11-27T17:24:53Z" unit="MINUTE" verb="POST" remaining="10" value="10"/>
<limit next-available="2012-11-27T17:24:53Z" unit="MINUTE" verb="PUT" remaining="10" value="10"/>
<limit next-available="2012-11-27T17:24:53Z" unit="MINUTE" verb="DELETE" remaining="100" value="100"/>
</rate>
<rate regex="^/servers" uri="*/servers">
<limit next-available="2012-11-27T17:24:53Z" unit="DAY" verb="POST" remaining="50" value="50"/>
</rate>
<rate regex=".*changes-since.*" uri="*changes-since*">
<limit next-available="2012-11-27T17:24:53Z" unit="MINUTE" verb="GET" remaining="3" value="3"/>
</rate>
<rate regex="^/os-fping" uri="*/os-fping">
<limit next-available="2012-11-27T17:24:53Z" unit="HOUR" verb="GET" remaining="12" value="12"/>
</rate>
</rates>
<absolute>
<limit name="maxServerMeta" value="128"/>
<limit name="maxPersonality" value="5"/>
<limit name="maxImageMeta" value="128"/>
<limit name="maxPersonalitySize" value="10240"/>
<limit name="maxSecurityGroupRules" value="20"/>
<limit name="maxTotalKeypairs" value="100"/>
<limit name="totalRAMUsed" value="0"/>
<limit name="totalInstancesUsed" value="0"/>
<limit name="maxSecurityGroups" value="10"/>
<limit name="totalFloatingIpsUsed" value="0"/>
<limit name="maxTotalCores" value="20"/>
<limit name="totalSecurityGroupsUsed" value="0"/>
<limit name="maxTotalFloatingIps" value="10"/>
<limit name="maxTotalInstances" value="10"/>
<limit name="totalCoresUsed" value="0"/>
<limit name="maxTotalRAMSize" value="51200"/>
</absolute>
</limits>

View File

@ -105,6 +105,7 @@
"compute_extension:volumetypes": "",
"compute_extension:availability_zone:list": "",
"compute_extension:availability_zone:detail": "rule:admin_api",
"compute_extension:used_limits_for_admin": "rule:admin_api",
"volume:create": "",

View File

@ -26,6 +26,8 @@ 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):
@ -37,6 +39,9 @@ class UsedLimitsTemplate(xmlutil.TemplateBuilder):
class UsedLimitsController(wsgi.Controller):
def __init__(self, ext_mgr):
self.ext_mgr = ext_mgr
@staticmethod
def _reserved(req):
try:
@ -48,8 +53,8 @@ class UsedLimitsController(wsgi.Controller):
def index(self, req, resp_obj):
resp_obj.attach(xml=UsedLimitsTemplate())
context = req.environ['nova.context']
quotas = QUOTAS.get_project_quotas(context, context.project_id,
usages=True)
project_id = self._project_id(context, req)
quotas = QUOTAS.get_project_quotas(context, project_id, usages=True)
quota_map = {
'totalRAMUsed': 'ram',
'totalCoresUsed': 'cores',
@ -66,6 +71,18 @@ class UsedLimitsController(wsgi.Controller):
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."""
@ -76,7 +93,7 @@ class Used_limits(extensions.ExtensionDescriptor):
updated = "2012-07-13T00:00:00+00:00"
def get_controller_extensions(self):
controller = UsedLimitsController()
controller = UsedLimitsController(self.ext_mgr)
limits_ext = extensions.ControllerExtension(self, 'limits',
controller=controller)
return [limits_ext]

View File

@ -0,0 +1,27 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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
class Used_limits_for_admin(extensions.ExtensionDescriptor):
"""Provide data to admin on limited resources used by other tenants."""
name = "UsedLimitsForAdmin"
alias = "os-used-limits-for-admin"
namespace = ("http://docs.openstack.org/compute/ext/used_limits_for_admin"
"/api/v1.1")
updated = "2013-05-02T00:00:00+00:00"

View File

@ -17,8 +17,10 @@
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
@ -31,13 +33,15 @@ class FakeRequest(object):
class UsedLimitsTestCase(test.TestCase):
def setUp(self):
"""Run before each test."""
super(UsedLimitsTestCase, self).setUp()
self.controller = used_limits.UsedLimitsController()
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)
@ -63,8 +67,11 @@ class UsedLimitsTestCase(test.TestCase):
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']
@ -79,6 +86,100 @@ class UsedLimitsTestCase(test.TestCase):
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 = {
@ -86,15 +187,19 @@ class UsedLimitsTestCase(test.TestCase):
"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)
@ -112,8 +217,12 @@ class UsedLimitsTestCase(test.TestCase):
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)
@ -131,8 +240,12 @@ class UsedLimitsTestCase(test.TestCase):
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)

View File

@ -26,7 +26,9 @@ from nova.api.openstack.compute import extensions as compute_extensions
from nova.api.openstack import extensions as base_extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import exception
from nova.openstack.common import jsonutils
import nova.policy
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests import matchers
@ -147,6 +149,24 @@ class ExtensionTestCase(test.TestCase):
if fox not in ext_list:
ext_list.append(fox)
self.flags(osapi_compute_extension=ext_list)
self.fake_context = nova.context.RequestContext('fake', 'fake')
def test_extension_authorizer_throws_exception_if_policy_fails(self):
target = {'project_id': '1234',
'user_id': '5678'}
self.mox.StubOutWithMock(nova.policy, 'enforce')
nova.policy.enforce(self.fake_context,
"compute_extension:used_limits_for_admin",
target).AndRaise(
exception.PolicyNotAuthorized(
action="compute_extension:used_limits_for_admin"))
('compute', 'used_limits_for_admin')
self.mox.ReplayAll()
authorize = base_extensions.extension_authorizer('compute',
'used_limits_for_admin'
)
self.assertRaises(exception.PolicyNotAuthorized, authorize,
self.fake_context, target=target)
class ExtensionControllerTest(ExtensionTestCase):

View File

@ -186,7 +186,7 @@ policy_data = """
"compute_extension:zones": "",
"compute_extension:availability_zone:list": "",
"compute_extension:availability_zone:detail": "is_admin:True",
"compute_extension:used_limits_for_admin": "is_admin:True",
"volume:create": "",
"volume:get": "",

View File

@ -512,6 +512,14 @@
"namespace": "http://docs.openstack.org/compute/ext/used_limits/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-used-limits-for-admin",
"description": "%(text)s",
"links": [],
"name": "UsedLimitsForAdmin",
"namespace": "http://docs.openstack.org/compute/ext/used_limits_for_admin/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-user-data",
"description": "%(text)s",

View File

@ -192,6 +192,9 @@
<extension alias="os-used-limits" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/used_limits/api/v1.1" name="UsedLimits">
<description>%(text)s</description>
</extension>
<extension alias="os-used-limits-for-admin" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/used_limits_for_admin/api/v1.1" name="UsedLimitsForAdmin">
<description>%(text)s</description>
</extension>
<extension alias="os-user-data" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/userdata/api/v1.1" name="UserData">
<description>%(text)s</description>
</extension>

View File

@ -0,0 +1,90 @@
{
"limits": {
"absolute": {
"maxImageMeta": 128,
"maxPersonality": 5,
"maxPersonalitySize": 10240,
"maxSecurityGroupRules": 20,
"maxSecurityGroups": 10,
"maxServerMeta": 128,
"maxTotalCores": 20,
"maxTotalFloatingIps": 10,
"maxTotalInstances": 10,
"maxTotalKeypairs": 100,
"maxTotalRAMSize": 51200,
"totalCoresUsed": 0,
"totalInstancesUsed": 0,
"totalRAMUsed": 0,
"totalSecurityGroupsUsed": 0,
"totalFloatingIpsUsed": 0
},
"rate": [
{
"limit": [
{
"next-available": "%(timestamp)s",
"remaining": 10,
"unit": "MINUTE",
"value": 10,
"verb": "POST"
},
{
"next-available": "%(timestamp)s",
"remaining": 10,
"unit": "MINUTE",
"value": 10,
"verb": "PUT"
},
{
"next-available": "%(timestamp)s",
"remaining": 100,
"unit": "MINUTE",
"value": 100,
"verb": "DELETE"
}
],
"regex": ".*",
"uri": "*"
},
{
"limit": [
{
"next-available": "%(timestamp)s",
"remaining": 50,
"unit": "DAY",
"value": 50,
"verb": "POST"
}
],
"regex": "^/servers",
"uri": "*/servers"
},
{
"limit": [
{
"next-available": "%(timestamp)s",
"remaining": 3,
"unit": "MINUTE",
"value": 3,
"verb": "GET"
}
],
"regex": ".*changes-since.*",
"uri": "*changes-since*"
},
{
"limit": [
{
"next-available": "%(timestamp)s",
"remaining": 12,
"unit": "HOUR",
"value": 12,
"verb": "GET"
}
],
"regex": "^/os-fping",
"uri": "*/os-fping"
}
]
}
}

View File

@ -0,0 +1,37 @@
<?xml version='1.0' encoding='UTF-8'?>
<limits xmlns:os-used-limits="http://docs.openstack.org/compute/ext/used_limits/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/common/api/v1.0">
<rates>
<rate regex=".*" uri="*">
<limit next-available="%(timestamp)s" unit="MINUTE" verb="POST" remaining="10" value="10"/>
<limit next-available="%(timestamp)s" unit="MINUTE" verb="PUT" remaining="10" value="10"/>
<limit next-available="%(timestamp)s" unit="MINUTE" verb="DELETE" remaining="100" value="100"/>
</rate>
<rate regex="^/servers" uri="*/servers">
<limit next-available="%(timestamp)s" unit="DAY" verb="POST" remaining="50" value="50"/>
</rate>
<rate regex=".*changes-since.*" uri="*changes-since*">
<limit next-available="%(timestamp)s" unit="MINUTE" verb="GET" remaining="3" value="3"/>
</rate>
<rate regex="^/os-fping" uri="*/os-fping">
<limit next-available="%(timestamp)s" unit="HOUR" verb="GET" remaining="12" value="12"/>
</rate>
</rates>
<absolute>
<limit name="maxServerMeta" value="128"/>
<limit name="maxTotalInstances" value="10"/>
<limit name="maxPersonality" value="5"/>
<limit name="maxImageMeta" value="128"/>
<limit name="maxPersonalitySize" value="10240"/>
<limit name="maxSecurityGroupRules" value="20"/>
<limit name="maxTotalKeypairs" value="100"/>
<limit name="totalCoresUsed" value="0"/>
<limit name="totalRAMUsed" value="0"/>
<limit name="totalInstancesUsed" value="0"/>
<limit name="maxSecurityGroups" value="10"/>
<limit name="maxTotalCores" value="20"/>
<limit name="totalSecurityGroupsUsed" value="0"/>
<limit name="maxTotalFloatingIps" value="10"/>
<limit name="totalFloatingIpsUsed" value="0"/>
<limit name="maxTotalRAMSize" value="51200"/>
</absolute>
</limits>

View File

@ -1896,6 +1896,25 @@ class UsedLimitsSamplesXmlTest(UsedLimitsSamplesJsonTest):
ctype = "xml"
class UsedLimitsForAdminSamplesJsonTest(ApiSampleTestBase):
extends_name = ("nova.api.openstack.compute.contrib.used_limits."
"Used_limits")
extension_name = (
"nova.api.openstack.compute.contrib.used_limits_for_admin."
"Used_limits_for_admin")
def test_get_used_limits_for_admin(self):
tenant_id = 'openstack'
response = self._do_get('limits?tenant_id=%s' % tenant_id)
subs = self._get_regexes()
return self._verify_response('usedlimitsforadmin-get-resp', subs,
response, 200)
class UsedLimitsForAdminSamplesXmlTest(UsedLimitsForAdminSamplesJsonTest):
ctype = "xml"
class MultipleCreateJsonTest(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib.multiple_create."
"Multiple_create")