Port simple_tenant_usage into v2.1
The simple_tenant_usage extension is removed from v3 API. For now, we need porting it back. And also try to share unittests between v2 and v2.1. Partially implements blueprint v2-on-v3-api Change-Id: Ie0c36fcd37fb188c1f1b57f04733bfa267b65201
This commit is contained in:
parent
c3ad01b65f
commit
65ef524d55
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server" : {
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef" : "http://openstack.example.com/openstack/flavors/1",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"personality" : [
|
||||
{
|
||||
"path" : "/etc/banner.txt",
|
||||
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "tpKL9n5BHKFv",
|
||||
"id": "3edb83e6-2c90-41c1-bf80-0b61472b4c19",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v3/servers/3edb83e6-2c90-41c1-bf80-0b61472b4c19",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/servers/3edb83e6-2c90-41c1-bf80-0b61472b4c19",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"tenant_usage": {
|
||||
"server_usages": [
|
||||
{
|
||||
"ended_at": null,
|
||||
"flavor": "m1.tiny",
|
||||
"hours": 1.0,
|
||||
"instance_id": "1f1deceb-17b5-4c04-84c7-e0d4499c8fe0",
|
||||
"local_gb": 1,
|
||||
"memory_mb": 512,
|
||||
"name": "new-server-test",
|
||||
"started_at": "2012-10-08T20:10:44.541277",
|
||||
"state": "active",
|
||||
"tenant_id": "openstack",
|
||||
"uptime": 3600,
|
||||
"vcpus": 1
|
||||
}
|
||||
],
|
||||
"start": "2012-10-08T20:10:44.587336",
|
||||
"stop": "2012-10-08T21:10:44.587336",
|
||||
"tenant_id": "openstack",
|
||||
"total_hours": 1.0,
|
||||
"total_local_gb_usage": 1.0,
|
||||
"total_memory_mb_usage": 512.0,
|
||||
"total_vcpus_usage": 1.0
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"tenant_usages": [
|
||||
{
|
||||
"start": "2012-10-08T21:10:44.587336",
|
||||
"stop": "2012-10-08T22:10:44.587336",
|
||||
"tenant_id": "openstack",
|
||||
"total_hours": 1.0,
|
||||
"total_local_gb_usage": 1.0,
|
||||
"total_memory_mb_usage": 512.0,
|
||||
"total_vcpus_usage": 1.0
|
||||
}
|
||||
]
|
||||
}
|
@ -239,6 +239,9 @@
|
||||
"compute_extension:v3:os-shelve:shelve:discoverable": "",
|
||||
"compute_extension:v3:os-shelve:shelve_offload": "rule:admin_api",
|
||||
"compute_extension:simple_tenant_usage:show": "rule:admin_or_owner",
|
||||
"compute_extension::v3:os-simple-tenant-usage:discoverable": "",
|
||||
"compute_extension::v3:os-simple-tenant-usage:show": "rule:admin_or_owner",
|
||||
"compute_extension::v3:os-simple-tenant-usage:list": "rule:admin_api",
|
||||
"compute_extension:v3:os-suspend-server:discoverable": "",
|
||||
"compute_extension:v3:os-suspend-server:suspend": "rule:admin_or_owner",
|
||||
"compute_extension:v3:os-suspend-server:resume": "rule:admin_or_owner",
|
||||
|
291
nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py
Normal file
291
nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py
Normal file
@ -0,0 +1,291 @@
|
||||
# Copyright 2011 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.
|
||||
|
||||
import datetime
|
||||
|
||||
import iso8601
|
||||
import six.moves.urllib.parse as urlparse
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import extensions
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova import objects
|
||||
from nova.objects import instance as instance_obj
|
||||
from nova.openstack.common import timeutils
|
||||
|
||||
ALIAS = "os-simple-tenant-usage"
|
||||
authorize_show = extensions.extension_authorizer('compute',
|
||||
'v3:%s:show' % ALIAS)
|
||||
authorize_list = extensions.extension_authorizer('compute',
|
||||
'v3:%s:list' % ALIAS)
|
||||
|
||||
|
||||
def parse_strtime(dstr, fmt):
|
||||
try:
|
||||
return timeutils.parse_strtime(dstr, fmt)
|
||||
except (TypeError, ValueError) as e:
|
||||
raise exception.InvalidStrTime(reason=unicode(e))
|
||||
|
||||
|
||||
class SimpleTenantUsageController(object):
|
||||
def _hours_for(self, instance, period_start, period_stop):
|
||||
launched_at = instance.launched_at
|
||||
terminated_at = instance.terminated_at
|
||||
if terminated_at is not None:
|
||||
if not isinstance(terminated_at, datetime.datetime):
|
||||
# NOTE(mriedem): Instance object DateTime fields are
|
||||
# timezone-aware so convert using isotime.
|
||||
terminated_at = timeutils.parse_isotime(terminated_at)
|
||||
|
||||
if launched_at is not None:
|
||||
if not isinstance(launched_at, datetime.datetime):
|
||||
launched_at = timeutils.parse_isotime(launched_at)
|
||||
|
||||
if terminated_at and terminated_at < period_start:
|
||||
return 0
|
||||
# nothing if it started after the usage report ended
|
||||
if launched_at and launched_at > period_stop:
|
||||
return 0
|
||||
if launched_at:
|
||||
# if instance launched after period_started, don't charge for first
|
||||
start = max(launched_at, period_start)
|
||||
if terminated_at:
|
||||
# if instance stopped before period_stop, don't charge after
|
||||
stop = min(period_stop, terminated_at)
|
||||
else:
|
||||
# instance is still running, so charge them up to current time
|
||||
stop = period_stop
|
||||
dt = stop - start
|
||||
seconds = (dt.days * 3600 * 24 + dt.seconds +
|
||||
dt.microseconds / 100000.0)
|
||||
|
||||
return seconds / 3600.0
|
||||
else:
|
||||
# instance hasn't launched, so no charge
|
||||
return 0
|
||||
|
||||
def _get_flavor(self, context, instance, flavors_cache):
|
||||
"""Get flavor information from the instance's system_metadata,
|
||||
allowing a fallback to lookup by-id for deleted instances only.
|
||||
"""
|
||||
try:
|
||||
return instance.get_flavor()
|
||||
except KeyError:
|
||||
if not instance.deleted:
|
||||
# Only support the fallback mechanism for deleted instances
|
||||
# that would have been skipped by migration #153
|
||||
raise
|
||||
|
||||
flavor_type = instance.instance_type_id
|
||||
if flavor_type in flavors_cache:
|
||||
return flavors_cache[flavor_type]
|
||||
|
||||
try:
|
||||
flavor_ref = objects.Flavor.get_by_id(context, flavor_type)
|
||||
flavors_cache[flavor_type] = flavor_ref
|
||||
except exception.FlavorNotFound:
|
||||
# can't bill if there is no flavor
|
||||
flavor_ref = None
|
||||
|
||||
return flavor_ref
|
||||
|
||||
def _tenant_usages_for_period(self, context, period_start,
|
||||
period_stop, tenant_id=None, detailed=True):
|
||||
|
||||
instances = objects.InstanceList.get_active_by_window_joined(
|
||||
context, period_start, period_stop, tenant_id,
|
||||
expected_attrs=instance_obj.INSTANCE_DEFAULT_FIELDS)
|
||||
rval = {}
|
||||
flavors = {}
|
||||
|
||||
for instance in instances:
|
||||
info = {}
|
||||
info['hours'] = self._hours_for(instance,
|
||||
period_start,
|
||||
period_stop)
|
||||
flavor = self._get_flavor(context, instance, flavors)
|
||||
if not flavor:
|
||||
info['flavor'] = ''
|
||||
else:
|
||||
info['flavor'] = flavor.name
|
||||
|
||||
info['instance_id'] = instance.uuid
|
||||
info['name'] = instance.display_name
|
||||
|
||||
info['memory_mb'] = instance.memory_mb
|
||||
info['local_gb'] = instance.root_gb + instance.ephemeral_gb
|
||||
info['vcpus'] = instance.vcpus
|
||||
|
||||
info['tenant_id'] = instance.project_id
|
||||
|
||||
# NOTE(mriedem): We need to normalize the start/end times back
|
||||
# to timezone-naive so the response doesn't change after the
|
||||
# conversion to objects.
|
||||
info['started_at'] = timeutils.normalize_time(instance.launched_at)
|
||||
|
||||
info['ended_at'] = (
|
||||
timeutils.normalize_time(instance.terminated_at) if
|
||||
instance.terminated_at else None)
|
||||
|
||||
if info['ended_at']:
|
||||
info['state'] = 'terminated'
|
||||
else:
|
||||
info['state'] = instance.vm_state
|
||||
|
||||
now = timeutils.utcnow()
|
||||
|
||||
if info['state'] == 'terminated':
|
||||
delta = info['ended_at'] - info['started_at']
|
||||
else:
|
||||
delta = now - info['started_at']
|
||||
|
||||
info['uptime'] = delta.days * 24 * 3600 + delta.seconds
|
||||
|
||||
if info['tenant_id'] not in rval:
|
||||
summary = {}
|
||||
summary['tenant_id'] = info['tenant_id']
|
||||
if detailed:
|
||||
summary['server_usages'] = []
|
||||
summary['total_local_gb_usage'] = 0
|
||||
summary['total_vcpus_usage'] = 0
|
||||
summary['total_memory_mb_usage'] = 0
|
||||
summary['total_hours'] = 0
|
||||
summary['start'] = timeutils.normalize_time(period_start)
|
||||
summary['stop'] = timeutils.normalize_time(period_stop)
|
||||
rval[info['tenant_id']] = summary
|
||||
|
||||
summary = rval[info['tenant_id']]
|
||||
summary['total_local_gb_usage'] += info['local_gb'] * info['hours']
|
||||
summary['total_vcpus_usage'] += info['vcpus'] * info['hours']
|
||||
summary['total_memory_mb_usage'] += (info['memory_mb'] *
|
||||
info['hours'])
|
||||
|
||||
summary['total_hours'] += info['hours']
|
||||
if detailed:
|
||||
summary['server_usages'].append(info)
|
||||
|
||||
return rval.values()
|
||||
|
||||
def _parse_datetime(self, dtstr):
|
||||
if not dtstr:
|
||||
value = timeutils.utcnow()
|
||||
elif isinstance(dtstr, datetime.datetime):
|
||||
value = dtstr
|
||||
else:
|
||||
for fmt in ["%Y-%m-%dT%H:%M:%S",
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S.%f"]:
|
||||
try:
|
||||
value = parse_strtime(dtstr, fmt)
|
||||
break
|
||||
except exception.InvalidStrTime:
|
||||
pass
|
||||
else:
|
||||
msg = _("Datetime is in invalid format")
|
||||
raise exception.InvalidStrTime(reason=msg)
|
||||
|
||||
# NOTE(mriedem): Instance object DateTime fields are timezone-aware
|
||||
# so we have to force UTC timezone for comparing this datetime against
|
||||
# instance object fields and still maintain backwards compatibility
|
||||
# in the API.
|
||||
if value.utcoffset() is None:
|
||||
value = value.replace(tzinfo=iso8601.iso8601.Utc())
|
||||
return value
|
||||
|
||||
def _get_datetime_range(self, req):
|
||||
qs = req.environ.get('QUERY_STRING', '')
|
||||
env = urlparse.parse_qs(qs)
|
||||
# NOTE(lzyeval): env.get() always returns a list
|
||||
period_start = self._parse_datetime(env.get('start', [None])[0])
|
||||
period_stop = self._parse_datetime(env.get('end', [None])[0])
|
||||
|
||||
if not period_start < period_stop:
|
||||
msg = _("Invalid start time. The start time cannot occur after "
|
||||
"the end time.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
detailed = env.get('detailed', ['0'])[0] == '1'
|
||||
return (period_start, period_stop, detailed)
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
def index(self, req):
|
||||
"""Retrieve tenant_usage for all tenants."""
|
||||
context = req.environ['nova.context']
|
||||
|
||||
authorize_list(context)
|
||||
|
||||
try:
|
||||
(period_start, period_stop, detailed) = self._get_datetime_range(
|
||||
req)
|
||||
except exception.InvalidStrTime as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
now = timeutils.parse_isotime(timeutils.strtime())
|
||||
if period_stop > now:
|
||||
period_stop = now
|
||||
usages = self._tenant_usages_for_period(context,
|
||||
period_start,
|
||||
period_stop,
|
||||
detailed=detailed)
|
||||
return {'tenant_usages': usages}
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
def show(self, req, id):
|
||||
"""Retrieve tenant_usage for a specified tenant."""
|
||||
tenant_id = id
|
||||
context = req.environ['nova.context']
|
||||
|
||||
authorize_show(context, {'project_id': tenant_id})
|
||||
|
||||
try:
|
||||
(period_start, period_stop, ignore) = self._get_datetime_range(
|
||||
req)
|
||||
except exception.InvalidStrTime as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
|
||||
now = timeutils.parse_isotime(timeutils.strtime())
|
||||
if period_stop > now:
|
||||
period_stop = now
|
||||
usage = self._tenant_usages_for_period(context,
|
||||
period_start,
|
||||
period_stop,
|
||||
tenant_id=tenant_id,
|
||||
detailed=True)
|
||||
if len(usage):
|
||||
usage = usage[0]
|
||||
else:
|
||||
usage = {}
|
||||
return {'tenant_usage': usage}
|
||||
|
||||
|
||||
class SimpleTenantUsage(extensions.V3APIExtensionBase):
|
||||
"""Simple tenant usage extension."""
|
||||
|
||||
name = "SimpleTenantUsage"
|
||||
alias = ALIAS
|
||||
version = 1
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
|
||||
res = extensions.ResourceExtension(ALIAS,
|
||||
SimpleTenantUsageController())
|
||||
resources.append(res)
|
||||
|
||||
return resources
|
||||
|
||||
def get_controller_extensions(self):
|
||||
return []
|
@ -19,7 +19,10 @@ from lxml import etree
|
||||
import mock
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.compute.contrib import simple_tenant_usage
|
||||
from nova.api.openstack.compute.contrib import simple_tenant_usage as \
|
||||
simple_tenant_usage_v2
|
||||
from nova.api.openstack.compute.plugins.v3 import simple_tenant_usage as \
|
||||
simple_tenant_usage_v21
|
||||
from nova.compute import flavors
|
||||
from nova.compute import vm_states
|
||||
from nova import context
|
||||
@ -102,9 +105,13 @@ def fake_instance_get_active_by_window_joined(context, begin, end,
|
||||
|
||||
@mock.patch.object(db, 'instance_get_active_by_window_joined',
|
||||
fake_instance_get_active_by_window_joined)
|
||||
class SimpleTenantUsageTest(test.TestCase):
|
||||
class SimpleTenantUsageTestV21(test.TestCase):
|
||||
url = '/v3/os-simple-tenant-usage'
|
||||
alt_url = '/v3/os-simple-tenant-usage'
|
||||
policy_rule_prefix = "compute_extension:v3:os-simple-tenant-usage"
|
||||
|
||||
def setUp(self):
|
||||
super(SimpleTenantUsageTest, self).setUp()
|
||||
super(SimpleTenantUsageTestV21, self).setUp()
|
||||
self.admin_context = context.RequestContext('fakeadmin_0',
|
||||
'faketenant_0',
|
||||
is_admin=True)
|
||||
@ -114,21 +121,20 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
self.alt_user_context = context.RequestContext('fakeadmin_0',
|
||||
'faketenant_1',
|
||||
is_admin=False)
|
||||
self.flags(
|
||||
osapi_compute_extension=[
|
||||
'nova.api.openstack.compute.contrib.select_extensions'],
|
||||
osapi_compute_ext_list=['Simple_tenant_usage'])
|
||||
|
||||
def _get_wsgi_app(self, context):
|
||||
return fakes.wsgi_app_v3(fake_auth_context=context,
|
||||
init_only=('servers',
|
||||
'os-simple-tenant-usage'))
|
||||
|
||||
def _test_verify_index(self, start, stop):
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_0/os-simple-tenant-usage?start=%s&end=%s' %
|
||||
self.url + '?start=%s&end=%s' %
|
||||
(start.isoformat(), stop.isoformat()))
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.admin_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.admin_context))
|
||||
|
||||
self.assertEqual(res.status_int, 200)
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
@ -160,15 +166,12 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
|
||||
def _get_tenant_usages(self, detailed=''):
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_0/os-simple-tenant-usage?'
|
||||
'detailed=%s&start=%s&end=%s' %
|
||||
self.url + '?detailed=%s&start=%s&end=%s' %
|
||||
(detailed, START.isoformat(), STOP.isoformat()))
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.admin_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.admin_context))
|
||||
self.assertEqual(res.status_int, 200)
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
return res_dict['tenant_usages']
|
||||
@ -194,15 +197,12 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
def _test_verify_show(self, start, stop):
|
||||
tenant_id = 0
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_0/os-simple-tenant-usage/'
|
||||
'faketenant_%s?start=%s&end=%s' %
|
||||
self.url + '/faketenant_%s?start=%s&end=%s' %
|
||||
(tenant_id, start.isoformat(), stop.isoformat()))
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.user_context))
|
||||
self.assertEqual(res.status_int, 200)
|
||||
res_dict = jsonutils.loads(res.body)
|
||||
|
||||
@ -220,14 +220,13 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
|
||||
def test_verify_show_cant_view_other_tenant(self):
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_1/os-simple-tenant-usage/'
|
||||
'faketenant_0?start=%s&end=%s' %
|
||||
self.alt_url + '/faketenant_0?start=%s&end=%s' %
|
||||
(START.isoformat(), STOP.isoformat()))
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
rules = {
|
||||
"compute_extension:simple_tenant_usage:show":
|
||||
self.policy_rule_prefix + ":show":
|
||||
common_policy.parse_rule([
|
||||
["role:admin"], ["project_id:%(project_id)s"]
|
||||
])
|
||||
@ -235,9 +234,7 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
policy.set_rules(rules)
|
||||
|
||||
try:
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.alt_user_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.alt_user_context))
|
||||
self.assertEqual(res.status_int, 403)
|
||||
finally:
|
||||
policy.reset()
|
||||
@ -246,40 +243,34 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
future = NOW + datetime.timedelta(hours=HOURS)
|
||||
tenant_id = 0
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_0/os-simple-tenant-usage/'
|
||||
self.url + '/'
|
||||
'faketenant_%s?start=%s&end=%s' %
|
||||
(tenant_id, future.isoformat(), NOW.isoformat()))
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.user_context))
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_get_tenants_usage_with_invalid_start_date(self):
|
||||
tenant_id = 0
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_0/os-simple-tenant-usage/'
|
||||
self.url + '/'
|
||||
'faketenant_%s?start=%s&end=%s' %
|
||||
(tenant_id, "xxxx", NOW.isoformat()))
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.user_context))
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def _test_get_tenants_usage_with_one_date(self, date_url_param):
|
||||
req = webob.Request.blank(
|
||||
'/v2/faketenant_0/os-simple-tenant-usage/'
|
||||
self.url + '/'
|
||||
'faketenant_0?%s' % date_url_param)
|
||||
req.method = "GET"
|
||||
req.headers["content-type"] = "application/json"
|
||||
res = req.get_response(fakes.wsgi_app(
|
||||
fake_auth_context=self.user_context,
|
||||
init_only=('os-simple-tenant-usage',)))
|
||||
res = req.get_response(self._get_wsgi_app(self.user_context))
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
def test_get_tenants_usage_with_no_start_date(self):
|
||||
@ -291,6 +282,20 @@ class SimpleTenantUsageTest(test.TestCase):
|
||||
'start=%s' % (NOW - datetime.timedelta(5)).isoformat())
|
||||
|
||||
|
||||
class SimpleTenantUsageTestV2(SimpleTenantUsageTestV21):
|
||||
url = '/v2/faketenant_0/os-simple-tenant-usage'
|
||||
alt_url = '/v2/faketenant_1/os-simple-tenant-usage'
|
||||
policy_rule_prefix = "compute_extension:simple_tenant_usage"
|
||||
|
||||
def _get_wsgi_app(self, context):
|
||||
self.flags(
|
||||
osapi_compute_extension=[
|
||||
'nova.api.openstack.compute.contrib.select_extensions'],
|
||||
osapi_compute_ext_list=['Simple_tenant_usage'])
|
||||
return fakes.wsgi_app(fake_auth_context=context,
|
||||
init_only=('os-simple-tenant-usage', ))
|
||||
|
||||
|
||||
class SimpleTenantUsageSerializerTest(test.TestCase):
|
||||
def _verify_server_usage(self, raw_usage, tree):
|
||||
self.assertEqual('server_usage', tree.tag)
|
||||
@ -324,7 +329,7 @@ class SimpleTenantUsageSerializerTest(test.TestCase):
|
||||
self.assertEqual(len(not_seen), 0)
|
||||
|
||||
def test_serializer_show(self):
|
||||
serializer = simple_tenant_usage.SimpleTenantUsageTemplate()
|
||||
serializer = simple_tenant_usage_v2.SimpleTenantUsageTemplate()
|
||||
today = timeutils.utcnow()
|
||||
yesterday = today - datetime.timedelta(days=1)
|
||||
raw_usage = dict(
|
||||
@ -371,7 +376,7 @@ class SimpleTenantUsageSerializerTest(test.TestCase):
|
||||
self._verify_tenant_usage(raw_usage, tree)
|
||||
|
||||
def test_serializer_index(self):
|
||||
serializer = simple_tenant_usage.SimpleTenantUsagesTemplate()
|
||||
serializer = simple_tenant_usage_v2.SimpleTenantUsagesTemplate()
|
||||
today = timeutils.utcnow()
|
||||
yesterday = today - datetime.timedelta(days=1)
|
||||
raw_usages = [dict(
|
||||
@ -458,10 +463,11 @@ class SimpleTenantUsageSerializerTest(test.TestCase):
|
||||
self._verify_tenant_usage(raw_usages[idx], child)
|
||||
|
||||
|
||||
class SimpleTenantUsageControllerTest(test.TestCase):
|
||||
class SimpleTenantUsageControllerTestV21(test.TestCase):
|
||||
controller = simple_tenant_usage_v21.SimpleTenantUsageController()
|
||||
|
||||
def setUp(self):
|
||||
super(SimpleTenantUsageControllerTest, self).setUp()
|
||||
self.controller = simple_tenant_usage.SimpleTenantUsageController()
|
||||
super(SimpleTenantUsageControllerTestV21, self).setUp()
|
||||
|
||||
self.context = context.RequestContext('fakeuser', 'fake-project')
|
||||
|
||||
@ -510,16 +516,26 @@ class SimpleTenantUsageControllerTest(test.TestCase):
|
||||
self.assertIsNone(flavor)
|
||||
|
||||
|
||||
class SimpleTenantUsageUtils(test.NoDBTestCase):
|
||||
class SimpleTenantUsageControllerTestV2(SimpleTenantUsageControllerTestV21):
|
||||
controller = simple_tenant_usage_v2.SimpleTenantUsageController()
|
||||
|
||||
|
||||
class SimpleTenantUsageUtilsV21(test.NoDBTestCase):
|
||||
simple_tenant_usage = simple_tenant_usage_v21
|
||||
|
||||
def test_valid_string(self):
|
||||
dt = simple_tenant_usage.parse_strtime("2014-02-21T13:47:20.824060",
|
||||
"%Y-%m-%dT%H:%M:%S.%f")
|
||||
dt = self.simple_tenant_usage.parse_strtime(
|
||||
"2014-02-21T13:47:20.824060", "%Y-%m-%dT%H:%M:%S.%f")
|
||||
self.assertEqual(datetime.datetime(
|
||||
microsecond=824060, second=20, minute=47, hour=13,
|
||||
day=21, month=2, year=2014), dt)
|
||||
|
||||
def test_invalid_string(self):
|
||||
self.assertRaises(exception.InvalidStrTime,
|
||||
simple_tenant_usage.parse_strtime,
|
||||
self.simple_tenant_usage.parse_strtime,
|
||||
"2014-02-21 13:47:20.824060",
|
||||
"%Y-%m-%dT%H:%M:%S.%f")
|
||||
|
||||
|
||||
class SimpleTenantUsageUtilsV2(SimpleTenantUsageUtilsV21):
|
||||
simple_tenant_usage = simple_tenant_usage_v2
|
||||
|
@ -281,6 +281,8 @@ policy_data = """
|
||||
"compute_extension:v3:os-shelve:shelve_offload": "",
|
||||
"compute_extension:simple_tenant_usage:show": "",
|
||||
"compute_extension:simple_tenant_usage:list": "",
|
||||
"compute_extension:v3:os-simple-tenant-usage:show": "",
|
||||
"compute_extension:v3:os-simple-tenant-usage:list": "",
|
||||
"compute_extension:unshelve": "",
|
||||
"compute_extension:v3:os-shelve:unshelve": "",
|
||||
"compute_extension:v3:os-suspend-server:suspend": "",
|
||||
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server" : {
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "%(host)s/openstack/images/%(image_id)s",
|
||||
"flavorRef" : "%(host)s/openstack/flavors/1",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"personality" : [
|
||||
{
|
||||
"path" : "/etc/banner.txt",
|
||||
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "%(password)s",
|
||||
"id": "%(id)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(host)s/v3/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(host)s/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"tenant_usage": {
|
||||
"server_usages": [
|
||||
{
|
||||
"ended_at": null,
|
||||
"flavor": "m1.tiny",
|
||||
"hours": 1.0,
|
||||
"instance_id": "%(uuid)s",
|
||||
"local_gb": 1,
|
||||
"memory_mb": 512,
|
||||
"name": "new-server-test",
|
||||
"started_at": "%(strtime)s",
|
||||
"state": "active",
|
||||
"tenant_id": "openstack",
|
||||
"uptime": 3600,
|
||||
"vcpus": 1
|
||||
}
|
||||
],
|
||||
"start": "%(strtime)s",
|
||||
"stop": "%(strtime)s",
|
||||
"tenant_id": "openstack",
|
||||
"total_hours": 1.0,
|
||||
"total_local_gb_usage": 1.0,
|
||||
"total_memory_mb_usage": 512.0,
|
||||
"total_vcpus_usage": 1.0
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"tenant_usages": [
|
||||
{
|
||||
"start": "%(strtime)s",
|
||||
"stop": "%(strtime)s",
|
||||
"tenant_id": "openstack",
|
||||
"total_hours": 1.0,
|
||||
"total_local_gb_usage": 1.0,
|
||||
"total_memory_mb_usage": 512.0,
|
||||
"total_vcpus_usage": 1.0
|
||||
}
|
||||
]
|
||||
}
|
60
nova/tests/integrated/v3/test_simple_tenant_usage.py
Normal file
60
nova/tests/integrated/v3/test_simple_tenant_usage.py
Normal file
@ -0,0 +1,60 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 datetime
|
||||
import urllib
|
||||
|
||||
from nova.openstack.common import timeutils
|
||||
from nova.tests.integrated.v3 import test_servers
|
||||
|
||||
|
||||
class SimpleTenantUsageSampleJsonTest(test_servers.ServersSampleBase):
|
||||
extension_name = "os-simple-tenant-usage"
|
||||
|
||||
def setUp(self):
|
||||
"""setUp method for simple tenant usage."""
|
||||
super(SimpleTenantUsageSampleJsonTest, self).setUp()
|
||||
|
||||
started = timeutils.utcnow()
|
||||
now = started + datetime.timedelta(hours=1)
|
||||
|
||||
timeutils.set_time_override(started)
|
||||
self._post_server()
|
||||
timeutils.set_time_override(now)
|
||||
|
||||
self.query = {
|
||||
'start': str(started),
|
||||
'end': str(now)
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown method for simple tenant usage."""
|
||||
super(SimpleTenantUsageSampleJsonTest, self).tearDown()
|
||||
timeutils.clear_time_override()
|
||||
|
||||
def test_get_tenants_usage(self):
|
||||
# Get api sample to get all tenants usage request.
|
||||
response = self._do_get('os-simple-tenant-usage?%s' % (
|
||||
urllib.urlencode(self.query)))
|
||||
subs = self._get_regexes()
|
||||
self._verify_response('simple-tenant-usage-get', subs, response, 200)
|
||||
|
||||
def test_get_tenant_usage_details(self):
|
||||
# Get api sample to get specific tenant usage request.
|
||||
tenant_id = 'openstack'
|
||||
response = self._do_get('os-simple-tenant-usage/%s?%s' % (tenant_id,
|
||||
urllib.urlencode(self.query)))
|
||||
subs = self._get_regexes()
|
||||
self._verify_response('simple-tenant-usage-get-specific', subs,
|
||||
response, 200)
|
@ -109,6 +109,7 @@ nova.api.v3.extensions =
|
||||
servers = nova.api.openstack.compute.plugins.v3.servers:Servers
|
||||
services = nova.api.openstack.compute.plugins.v3.services:Services
|
||||
shelve = nova.api.openstack.compute.plugins.v3.shelve:Shelve
|
||||
simple_tenant_usage = nova.api.openstack.compute.plugins.v3.simple_tenant_usage:SimpleTenantUsage
|
||||
suspend_server = nova.api.openstack.compute.plugins.v3.suspend_server:SuspendServer
|
||||
versions = nova.api.openstack.compute.plugins.v3.versions:Versions
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user