diff --git a/bin/dough-api b/bin/dough-api index ffbe959..bbec55f 100755 --- a/bin/dough-api +++ b/bin/dough-api @@ -6,9 +6,9 @@ import sys import zmq +from nova import utils from nova import flags from nova import log as logging -from nova import utils from dough import api from dough import context as dough_context @@ -25,6 +25,7 @@ if __name__ == '__main__': # Socket to receive messages on handler = zmq_context.socket(zmq.REP) handler.bind("tcp://%s:%s" % (FLAGS.api_listen, FLAGS.api_listen_port)) + print "listen:", FLAGS.api_listen, FLAGS.api_listen_port poller = zmq.Poller() poller.register(handler, zmq.POLLIN) @@ -40,8 +41,13 @@ if __name__ == '__main__': method = msg_body['method'] args = msg_body['args'] context = dough_context.get_context(**args) + print "-"*60 + print "\033[0;31m" + method + "\033[0m:" + print args method_func = getattr(api, method) response = method_func(context, **args) + print "response:" + print response except Exception, e: print traceback.format_exc() cli_msg['code'] = 500 diff --git a/bin/dough-client b/bin/dough-client new file mode 100755 index 0000000..2fefd94 --- /dev/null +++ b/bin/dough-client @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Sina Corporation +# All Rights Reserved. +# Author: YuWei Peng +# +# 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 sys +import time +import ConfigParser +import json +import zmq +from collections import OrderedDict +from dough.client.dough_client import * + +def show_usage(): + print "usage:" + print "\tdough_client" + print "\tdough_client [time_to] [period_hours]" + print "param:" + print "\t -m : query_monthly_report" + print "\t -d : query_report" + print "./dough-client -d 1adfb274fee24dbd96ea89b57d110fc5 2012-06-01T00:00:00 2012-07-01T00:00:00 days network resource_name" + +def show_result(data): + print data + +def show_report(data): +# print "ID=", data['data']['id'] + data = data['data']['default'] + line_total_sum = 0 + quantity_sum = 0 + count = 0 + + rs = OrderedDict(sorted(data.items(), key=lambda t: t[0])) + for k, i in rs.iteritems(): + count += 1 + print '-' * 60, count + print k + for kk, ii in i.iteritems(): + print "\t", kk, ii + if kk == 'line_total': + line_total_sum += ii + elif kk == 'quantity': + quantity_sum += ii + print "total[", count, "] = ", line_total_sum + +def main(): + if len(sys.argv) == 2: + show_usage() + return + if len(sys.argv) == 3: + if sys.argv[2] in ['--help', "-h", "?"]: + show_usage() + return + + data = None + client = DoughClient() + + if len(sys.argv) > 8: + tenant_id = sys.argv[3] + time_from = sys.argv[4] + time_to = sys.argv[5] + period = sys.argv[6] + item_name = sys.argv[7] + resource_name = sys.argv[8] + if sys.argv[2] == '-d': + data = client.query_report(tenant_id, time_from, time_to, period, + item_name, resource_name) + else: + pass + + show_report(data) + elif len(sys.argv) > 6: + return + elif len(sys.argv) > 5: + tenant_id = sys.argv[3] + time_from = sys.argv[4] + time_to = sys.argv[5] + if sys.argv[2] == '-m': + data = client.query_monthly_report(tenant_id, time_from, time_to) + else: + pass + show_result(data) + else: + pass + +if __name__ == '__main__': + main() diff --git a/bin/dough-farmer b/bin/dough-farmer index 58ac455..e891f03 100755 --- a/bin/dough-farmer +++ b/bin/dough-farmer @@ -29,6 +29,7 @@ if __name__ == '__main__': context = dough_context.get_admin_context() while True: current_time = utils.utcnow() + print "-" * 50, str(current_time) subscriptions = list() _subscriptions = db.subscription_get_all(context) for sub in _subscriptions: diff --git a/bin/dough-manager b/bin/dough-manager new file mode 100755 index 0000000..dd43f5e --- /dev/null +++ b/bin/dough-manager @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Sina Corporation +# All Rights Reserved. +# Author: YuWei Peng +# +# 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 sys +import time +import traceback + +#from dateutil.relativedelta import relativedelta +#import zmq + +from nova import flags +from nova import log as logging +from nova import utils + +#from dough import billing +# from dough.billing import api +from dough import context as dough_context +from dough import db +from dough import api +from nova.openstack.common import cfg +#from dough import exception + +utils.default_flagfile(filename='/etc/dough/dough.conf') +FLAGS = flags.FLAGS +#logging.setup() + +manager_opts = [ + cfg.StrOpt('resource_name', + short='r', + default='name1', + help='resource_name.'), + cfg.StrOpt('purchase', + short='p', + default='name1', + help='purchase.'), + cfg.StrOpt('help', + short='?', + default='', + help='help'), + ] + +FLAGS.register_cli_opts(manager_opts) +flags.FLAGS(sys.argv) + +def show_usage(): + print "dough-mansger -n " + +def get_subs(context, param): + print "filter: resource_name=", param + try: + sub = db.subscription_get_byname(context, resource_name=param) + subscription_id = sub['id'] + status = sub['status'] + tenant_id = sub['project_id'] + resource_uuid = sub['resource_uuid'] + created_at = sub['created_at'] + updated_at = sub['updated_at'] + expires_at = sub['expires_at'] + order_unit = sub['product']['order_unit'] + order_size = sub['product']['order_size'] + price = sub['product']['price'] + currency = sub['product']['currency'] + + region_name = sub['product']['region']['name'] + item_name = sub['product']['item']['name'] + pay_type = sub['product']['payment_type'] + interval_unit = pay_type['interval_unit'] + interval_size = pay_type['interval_size'] + is_prepaid = pay_type['is_prepaid'] + print '-'*60 + print "%24s : %s" % ("subscription_id", str(subscription_id)) + print "%24s : %s" % ("tenant_id", str(tenant_id)) + print "%24s : %s" % ("expires_at", str(expires_at)) + print "%24s : %s" % ("price", str(price)) + print "%24s : %s" % ("region_name", str(region_name)) + print "%24s : %s" % ("item_name", str(item_name)) +# print "%24s : %s" % ("pay_type", str(pay_type)) + print "%24s : %s" % ("created_at", str(created_at)) + except Exception, e: + print Exception, e + +def query_report(tenant_id, timestamp_from, timestamp_to): + datetime_from = iso8601.parse_date(timestamp_from) + datetime_to = iso8601.parse_date(timestamp_to) + context = dough_context.get_context(tenant_id=self.tenant_id) + data = api.query_report(self.context, + timestamp_from, + timestamp_to) + return data + +""" +select id, subscription_id, created_at, quantity from purchases where subscription_id =151 and created_at>"2012-06-03 00:00:00"; +""" + +def main(): + baselen = 2 + context = dough_context.get_admin_context() + if len(sys.argv) == baselen: + get_subs(context, sys.argv) + return + if len(sys.argv) == baselen: + if sys.argv[baselen-1] in ['--help', "-h", "?"]: + show_usage() + return + + if len(sys.argv) == baselen + 2: + param = sys.argv[baselen + 1] + if sys.argv[baselen][:2] == '-r': + get_subs(context, param) + elif len(sys.argv) == baselen + 4: + tenant_id = sys.argv[baselen + 1] + timestamp_from = sys.argv[baselen + 2] + timestamp_to = sys.argv[baselen + 3] + print query_report(tenant_id, timestamp_from, timestamp_to) + + +if __name__ == '__main__': + main() diff --git a/dough/api.py b/dough/api.py index 39e0f7a..20481b0 100644 --- a/dough/api.py +++ b/dough/api.py @@ -182,12 +182,14 @@ def find_timeframe(start_time, end_time, target): current_frame = start_time month_cnt = 1 while current_frame < end_time: + # 2012-05-10 00:00:00+00:00-->2012-06-10 00:00:00+00:00 next_frame = start_time + relativedelta(months=month_cnt) if current_frame <= target_utc < next_frame: break month_cnt += 1 current_frame = next_frame assert(current_frame < end_time) + return current_frame.isoformat() monthly_report = dict() @@ -218,3 +220,97 @@ def find_timeframe(start_time, end_time, target): monthly_usage.setdefault(item_name, 0) monthly_usage[item_name] += line_total return {'data': monthly_report} + +def query_report(context, timestamp_from=None, timestamp_to=None, + period=None, item_name=None, resource_name=None, **kwargs): + """period='days' or 'hours'""" + print "query_report", timestamp_from, timestamp_to, item_name, resource_name +# period = int(period) + + if not period in ['days', 'hours', 'months']: + return {'data': None} + + def find_timeframe(start_time, end_time, target): + target_utc = target.replace(tzinfo=UTC_TIMEZONE) + current_frame = start_time + cnt = 1 + while current_frame < end_time: + foo = {period: cnt} + next_frame = start_time + relativedelta(**foo) + if current_frame <= target_utc < next_frame: + break + cnt += 1 + current_frame = next_frame + assert(current_frame < end_time) + return current_frame.isoformat() + + monthly_report = dict() + usage_report = dict() + datetime_from = iso8601.parse_date(timestamp_from) + datetime_to = iso8601.parse_date(timestamp_to) + subscriptions = list() + _subscriptions = list() + + __subscriptions = db.subscription_get_all_by_project(context, + context.project_id) + if not __subscriptions: + return {'data': None} +# print "context.project_id", context.project_id + for subscription in __subscriptions: +# print subscription['id'], subscription['resource_name'], subscription['product']['item']['name'] + if subscription['resource_name'] != resource_name: + continue + elif subscription['product']['item']['name'] != item_name: + continue + _subscriptions.append(subscription) + + for subscription in _subscriptions: + subscription_id = subscription['id'] + resource_uuid = subscription['resource_uuid'] + resource_name = subscription['resource_name'] + created_at = subscription['created_at'] + expires_at = subscription['expires_at'] + region_name = subscription['product']['region']['name'] + item_name = subscription['product']['item']['name'] + item_type_name = subscription['product']['item_type']['name'] + order_unit = subscription['product']['order_unit'] + order_size = subscription['product']['order_size'] + price = subscription['product']['price'] + currency = subscription['product']['currency'] + subscriptions.append([subscription_id, resource_uuid, resource_name, + created_at, expires_at, + region_name, item_name, item_type_name, + order_unit, order_size, price, currency]) + for (subscription_id, resource_uuid, resource_name, created_at, expires_at, + region_name, item_name, item_type_name, + order_unit, order_size, price, currency) in subscriptions: + purchases = db.purchase_get_all_by_subscription_and_timeframe(context, + subscription_id, + datetime_from, + datetime_to) + if not purchases: + continue + i = 0 + for purchase in purchases: + line_total = purchase['line_total'] + quantity = purchase['quantity'] + timeframe = find_timeframe(datetime_from, + datetime_to, + purchase['created_at']) +# print timeframe + i += 1 + usage_datum = (resource_uuid, resource_name, item_type_name, + order_unit, order_size, price, + currency, quantity, line_total, + created_at.isoformat(), expires_at.isoformat()) + region_usage = monthly_report.setdefault(region_name, dict()) + monthly_usage = region_usage.setdefault(timeframe, dict()) + item = monthly_usage.setdefault(item_name, 0) + monthly_usage[item_name] = usage_datum + item = monthly_usage.setdefault("quantity", 0) + monthly_usage.setdefault("line_total", 0) + monthly_usage["quantity"] += quantity + monthly_usage["line_total"] += line_total + print "total:", i + return {'data': monthly_report} + diff --git a/dough/billing/api.py b/dough/billing/api.py index 5bc6875..a853178 100644 --- a/dough/billing/api.py +++ b/dough/billing/api.py @@ -42,6 +42,8 @@ def creating(context, subscription_id, tenant_id, resource_uuid, quantity = conn.get_usage(resource_uuid, expires_at - relativedelta(**interval_info), expires_at, order_size) + print "creating", tenant_id, subscription_id, \ + quantity, order_size, "\033[1;33m", price, "\033[0m" charge(context, tenant_id, subscription_id, quantity, order_size, price) db.subscription_extend(context, subscription_id, @@ -67,6 +69,8 @@ def deleting(context, subscription_id, tenant_id, resource_uuid, quantity = conn.get_usage(resource_uuid, expires_at - relativedelta(**interval_info), expires_at, order_size) + print "deleting", tenant_id, subscription_id, \ + quantity, order_size, "\033[1;33m", price, "\033[0m" charge(context, tenant_id, subscription_id, quantity, order_size, price) @@ -85,6 +89,8 @@ def verified(context, subscription_id, tenant_id, resource_uuid, quantity = conn.get_usage(resource_uuid, expires_at - relativedelta(**interval_info), expires_at, order_size) + print "verified", tenant_id, subscription_id, \ + quantity, order_size, "\033[1;33m", price, "\033[0m" charge(context, tenant_id, subscription_id, quantity, order_size, price) db.subscription_extend(context, subscription_id, expires_at + relativedelta(**interval_info)) diff --git a/dough/client/__init__.py b/dough/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dough/client/dough_client.py b/dough/client/dough_client.py new file mode 100644 index 0000000..cb709c6 --- /dev/null +++ b/dough/client/dough_client.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Sina Corporation +# All Rights Reserved. +# Author: YuWei Peng +# +# 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 sys +import time +from md5 import md5 +import json +import zmq +import urllib2 +import datetime +import base64 +from collections import OrderedDict + +from nova import utils +from nova import flags +from nova.openstack.common import cfg +from nova import log as logging + +utils.default_flagfile(filename='/etc/dough/dough.conf') +logging.setup() + +api_opts = [ + cfg.StrOpt('api_host', + default='127.0.0.1', + help='IP address of dough API.'), + cfg.IntOpt('api_port', + default=8783, + help='Port of dough api.'), +# cfg.StrOpt('monthly_report', +# short='m', +# default='name1', +# help='monthly_report.'), + ] + +cli_opts = [ + cfg.StrOpt('monthly_report', + short='m', + default='name1', + help='monthly_report.'), + ] + +FLAGS = flags.FLAGS +FLAGS.register_cli_opts(cli_opts) +FLAGS.register_opts(api_opts) +flags.FLAGS(sys.argv) + +STANDARD_PROTOCOL = { + 'method': 'query_report', + 'args': { + 'user_id': '864bbc5d23ea47799ae2a702927920e9', + 'tenant_id': '864bbc5d23ea47799ae2a702927920e9', + 'timestamp_from': '2012-03-01T00:00:00', + 'timestamp_to': '2012-03-02T00:00:00', + } + } + +class DoughClient(): + + def __init__(self): + context = zmq.Context() + self.socket = context.socket(zmq.REQ) + connstr = "tcp://%(api_host)s:%(api_port)s" % FLAGS + # print connstr + self.socket.connect(connstr) + + def invoke(self, param): + self.socket.send_multipart (["client", "1", json.dumps(param)]) + msg_type, uuid, message = self.socket.recv_multipart() + return json.loads(message) + + def query_monthly_report(self, tenant_id, time_from, time_to): + request = STANDARD_PROTOCOL + request["method"] = "query_monthly_report" + request["args"]["tenant_id"] = tenant_id + request["args"]["timestamp_from"] = time_from + request["args"]["timestamp_to"] = time_to + + data = self.invoke(request) + return data + + def query_report(self, tenant_id, time_from, time_to, period, + item_name, resource_name): + request = STANDARD_PROTOCOL + request["method"] = "query_report" + request["args"]["tenant_id"] = tenant_id + request["args"]["timestamp_from"] = time_from + request["args"]["timestamp_to"] = time_to + request["args"]["period"] = period + request["args"]["item_name"] = item_name + request["args"]["resource_name"] = resource_name + + data = self.invoke(request) + return data + diff --git a/dough/db/sqlalchemy/api.py b/dough/db/sqlalchemy/api.py index 82cdd90..2355aae 100644 --- a/dough/db/sqlalchemy/api.py +++ b/dough/db/sqlalchemy/api.py @@ -241,6 +241,15 @@ def payment_type_get_by_name(context, payment_type_name): # products +def subscription_get_byname(context, resource_name): + result = model_query(context, models.Subscription).\ + filter_by(resource_name=resource_name).\ + first() + if not result: + return None + return result + + def product_get(context, product_id): result = model_query(context, models.Product).\ filter_by(id=product_id).\ @@ -352,6 +361,7 @@ def subscription_get_all_by_resource_uuid(context, resource_uuid): def subscription_get_all(context, filters=None): + """filters={project_id:1}""" filters = filters or dict() filters = dict(filter(lambda (x, y): x in ['project_id', 'product_id',