Add consumptions API support.

Change-Id: I56a60b98801fce9de6a792371d3ca60ed5712c56
This commit is contained in:
lvdongbing 2016-08-03 21:07:06 -04:00
parent ff26ed98e7
commit 711c54e6d2
7 changed files with 214 additions and 8 deletions

View File

@ -13,6 +13,7 @@
import routes import routes
from bilean.api.openstack.v1 import consumptions
from bilean.api.openstack.v1 import events from bilean.api.openstack.v1 import events
from bilean.api.openstack.v1 import policies from bilean.api.openstack.v1 import policies
from bilean.api.openstack.v1 import resources from bilean.api.openstack.v1 import resources
@ -172,4 +173,21 @@ class API(wsgi.Router):
action="index", action="index",
conditions={'method': 'GET'}) conditions={'method': 'GET'})
# Consumptions
cons_resource = consumptions.create_resource(conf)
cons_path = "/consumptions"
with mapper.submapper(controller=cons_resource,
path_prefix=cons_path) as cons_mapper:
# Consumption collection
cons_mapper.connect("consumption_index",
"",
action="index",
conditions={'method': 'GET'})
cons_mapper.connect("consumption_statistics",
"/statistics",
action="statistics",
conditions={'method': 'GET'})
super(API, self).__init__(mapper) super(API, self).__init__(mapper)

View File

@ -0,0 +1,88 @@
#
# 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 bilean.api.openstack.v1 import util
from bilean.common import consts
from bilean.common import serializers
from bilean.common import utils
from bilean.common import wsgi
from bilean.rpc import client as rpc_client
class ConsumptionController(object):
"""WSGI controller for Consumptions in Bilean v1 API."""
# Define request scope (must match what is in policy.json)
REQUEST_SCOPE = 'consumptions'
def __init__(self, options):
self.options = options
self.rpc_client = rpc_client.EngineClient()
@util.policy_enforce
def index(self, req):
"""Lists all consumptions."""
filter_whitelist = {
'resource_type': 'mixed',
}
param_whitelist = {
'user_id': 'single',
'start_time': 'single',
'end_time': 'single',
'limit': 'single',
'marker': 'single',
'sort_dir': 'single',
'sort_keys': 'multi',
}
params = util.get_allowed_params(req.params, param_whitelist)
filters = util.get_allowed_params(req.params, filter_whitelist)
key = consts.PARAM_LIMIT
if key in params:
params[key] = utils.parse_int_param(key, params[key])
if not filters:
filters = None
consumptions = self.rpc_client.consumption_list(req.context,
filters=filters,
**params)
return {'consumptions': consumptions}
@util.policy_enforce
def statistics(self, req):
'''Consumptions statistics.'''
filter_whitelist = {
'resource_type': 'mixed',
}
param_whitelist = {
'user_id': 'single',
'start_time': 'single',
'end_time': 'single',
}
params = util.get_allowed_params(req.params, param_whitelist)
filters = util.get_allowed_params(req.params, filter_whitelist)
if not filters:
filters = None
statistics = self.rpc_client.consumption_statistics(req.context,
filters=filters,
**params)
return {'statistics': statistics}
def create_resource(ops):
"""Consumption resource factory method."""
deserializer = wsgi.JSONRequestDeserializer()
serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(ConsumptionController(ops), deserializer, serializer)

View File

@ -312,9 +312,12 @@ def consumption_get(context, consumption_id, project_safe=True):
project_safe=project_safe) project_safe=project_safe)
def consumption_get_all(context, limit=None, marker=None, sort_keys=None, def consumption_get_all(context, user_id=None, limit=None, marker=None,
sort_dir=None, filters=None, project_safe=True): sort_keys=None, sort_dir=None, filters=None,
return IMPL.consumption_get_all(context, limit=limit, project_safe=True):
return IMPL.consumption_get_all(context,
user_id=user_id,
limit=limit,
marker=marker, marker=marker,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dir=sort_dir, sort_dir=sort_dir,

View File

@ -881,14 +881,17 @@ def consumption_get(context, consumption_id, project_safe=True):
return consumption return consumption
def consumption_get_all(context, limit=None, marker=None, sort_keys=None, def consumption_get_all(context, user_id=None, limit=None, marker=None,
sort_dir=None, filters=None, project_safe=True): sort_keys=None, sort_dir=None, filters=None,
project_safe=True):
query = model_query(context, models.Consumption) query = model_query(context, models.Consumption)
if context.is_admin: if context.is_admin:
project_safe = False project_safe = False
if project_safe: if project_safe:
query = query.filter_by(user_id=context.project) query = query.filter_by(user_id=context.project)
elif user_id:
query = query.filter_by(user_id=user_id)
if filters is None: if filters is None:
filters = {} filters = {}

View File

@ -67,11 +67,14 @@ class Consumption(object):
return cls.from_db_record(record) return cls.from_db_record(record)
@classmethod @classmethod
def load_all(cls, context, limit=None, marker=None, sort_keys=None, def load_all(cls, context, user_id=None, limit=None, marker=None,
sort_dir=None, filters=None, project_safe=True): sort_keys=None, sort_dir=None, filters=None,
project_safe=True):
'''Retrieve all consumptions from database.''' '''Retrieve all consumptions from database.'''
records = db_api.consumption_get_all(context, limit=limit, records = db_api.consumption_get_all(context,
user_id=user_id,
limit=limit,
marker=marker, marker=marker,
filters=filters, filters=filters,
sort_keys=sort_keys, sort_keys=sort_keys,

View File

@ -34,6 +34,7 @@ from bilean.common import schema
from bilean.common import utils from bilean.common import utils
from bilean.db import api as db_api from bilean.db import api as db_api
from bilean.engine.actions import base as action_mod from bilean.engine.actions import base as action_mod
from bilean.engine import consumption as cons_mod
from bilean.engine import dispatcher from bilean.engine import dispatcher
from bilean.engine import environment from bilean.engine import environment
from bilean.engine import event as event_mod from bilean.engine import event as event_mod
@ -679,3 +680,71 @@ class EngineService(service.Service):
self.TG.start_action(self.engine_id, action_id=action_id) self.TG.start_action(self.engine_id, action_id=action_id)
LOG.info(_LI('User settle_account action queued: %s'), action_id) LOG.info(_LI('User settle_account action queued: %s'), action_id)
@request_context
def consumption_list(self, cnxt, user_id=None, limit=None,
marker=None, sort_keys=None, sort_dir=None,
filters=None, project_safe=True):
if limit is not None:
limit = utils.parse_int_param('limit', limit)
consumptions = cons_mod.Consumption.load_all(cnxt,
user_id=user_id,
limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
project_safe=project_safe)
return [c.to_dict() for c in consumptions]
@request_context
def consumption_statistics(self, cnxt, user_id=None, filters=None,
start_time=None, end_time=None,
project_safe=True):
result = {}
if start_time is None:
start_time = 0
else:
start_time = utils.format_time_to_seconds(start_time)
start_time = utils.make_decimal(start_time)
now_time = utils.format_time_to_seconds(timeutils.utcnow())
now_time = utils.make_decimal(now_time)
if end_time is None:
end_time = now_time
else:
end_time = utils.format_time_to_seconds(end_time)
end_time = utils.make_decimal(end_time)
consumptions = cons_mod.Consumption.load_all(cnxt, user_id=user_id,
project_safe=project_safe)
for cons in consumptions:
if cons.start_time > end_time or cons.end_time < start_time:
continue
et = min(cons.end_time, end_time)
st = max(cons.start_time, start_time)
seconds = et - st
cost = cons.rate * seconds
if cons.resource_type not in result:
result[cons.resource_type] = cost
else:
result[cons.resource_type] += cost
resources = plugin_base.Resource.load_all(cnxt, user_id=user_id,
project_safe=project_safe)
for res in resources:
if res.last_bill > end_time or now_time < start_time:
continue
et = min(now_time, end_time)
st = max(res.last_bill, start_time)
seconds = et - st
cost = res.rate * seconds
if res.resource_type not in result:
result[res.resource_type] = cost
else:
result[res.resource_type] += cost
for key in six.iterkeys(result):
result[key] = utils.dec2str(result[key])
return result

View File

@ -210,3 +210,25 @@ class EngineClient(object):
return self.call(ctxt, self.make_msg('settle_account', return self.call(ctxt, self.make_msg('settle_account',
user_id=user_id, user_id=user_id,
task=task)) task=task))
# consumptions
def consumption_list(self, ctxt, user_id=None, limit=None, marker=None,
sort_keys=None, sort_dir=None, filters=None,
project_safe=True):
return self.call(ctxt, self.make_msg('consumption_list',
user_id=user_id,
limit=limit, marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
project_safe=project_safe))
def consumption_statistics(self, ctxt, user_id=None, filters=None,
start_time=None, end_time=None,
project_safe=True):
return self.call(ctxt, self.make_msg('consumption_statistics',
user_id=user_id,
filters=filters,
start_time=start_time,
end_time=end_time,
project_safe=project_safe))