diff --git a/bin/dough-api b/bin/dough-api index ffbe959..e64e6ef 100755 --- a/bin/dough-api +++ b/bin/dough-api @@ -6,12 +6,13 @@ 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 +from kanyun.common.app import App utils.default_flagfile(filename='/etc/dough/dough.conf') flags.FLAGS(sys.argv) @@ -21,10 +22,12 @@ FLAGS = flags.FLAGS if __name__ == '__main__': zmq_context = zmq.Context() + app = App(conf="dough/dough.conf", name="dough-api") # 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,10 +43,17 @@ if __name__ == '__main__': method = msg_body['method'] args = msg_body['args'] context = dough_context.get_context(**args) + context.app = app + 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() + app.error(method + ": " + str(e)) cli_msg['code'] = 500 cli_msg['message'] = str(e) response.update(cli_msg) diff --git a/bin/dough-client b/bin/dough-client new file mode 100755 index 0000000..e6115cd --- /dev/null +++ b/bin/dough-client @@ -0,0 +1,158 @@ +#!/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 +from collections import OrderedDict +from dough.client.dough_client import DoughClient + + +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 "\t -u : unsubscribe_item" + print "\t -s : subscribe_item" + print "\t -l : get_load_balancers" + print "example:" + print "\t./dough-client -s user_id tenant_id resource_uuid resource_name region item item_type payment_type timestamp" + print "\t./dough-client -u user_id, tenant_id, region, resource_uuid, item, timestamp" + print "\t./dough-client -d 1adfb274fee24dbd96ea89b57d110fc5 2012-06-01T00:00:00 2012-07-01T00:00:00 days network resource_name" + print "\tget_all: ./dough-client -l user_id tenant_id" + print "\tget_by_uuid: ./dough-client -l user_id tenant_id load_balancer_uuid" + print "\tisrunning?: ./dough-client -l load_balancer_uuid" + + +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) == 12: + if sys.argv[2] == '-s': + user_id = sys.argv[3] + tenant_id = sys.argv[4] + resource_uuid = sys.argv[5] + resource_name = sys.argv[6] + region = sys.argv[7] + item = sys.argv[8] + item_type = sys.argv[9] + payment_type = sys.argv[10] + timestamp = sys.argv[11] + data = client.subscribe_item(user_id, tenant_id, + resource_uuid, resource_name, + region, item, + item_type, payment_type, + timestamp) + print data + + if len(sys.argv) > 8: + if sys.argv[2] == '-d': + 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] + data = client.query_report(tenant_id, time_from, time_to, period, + item_name, resource_name) + elif sys.argv[2] == '-u': + user_id = sys.argv[3] + tenant_id = sys.argv[4] + region = sys.argv[5] + resource_uuid = sys.argv[6] + item = sys.argv[7] + timestamp = sys.argv[8] + data = client.unsubscribe_item(user_id, tenant_id, region, + resource_uuid, item, timestamp) + print data + else: + pass + + show_report(data) + elif len(sys.argv) > 6: + return + elif len(sys.argv) >= 3: + if sys.argv[2] == '-l': + if len(sys.argv) == 5: + user_id = sys.argv[3] + tenant_id = sys.argv[4] + data = client.load_balancer_get_all(user_id, tenant_id) + print data + elif len(sys.argv) == 6: + user_id = sys.argv[3] + tenant_id = sys.argv[4] + lb_id = sys.argv[5] + data = client.load_balancer_get(user_id, tenant_id, lb_id) + print data + elif len(sys.argv) == 4: + lb_id = sys.argv[3] + data = client.load_balancer_is_running(lb_id) + print data + else: + pass + elif sys.argv[2] == '-m': + if len(sys.argv) == 6: + tenant_id = sys.argv[3] + time_from = sys.argv[4] + time_to = sys.argv[5] + data = client.query_monthly_report(tenant_id, time_from, time_to) + show_result(data) + else: + pass + else: + pass + + +if __name__ == '__main__': + main() diff --git a/bin/dough-farmer b/bin/dough-farmer index 58ac455..796fac3 100755 --- a/bin/dough-farmer +++ b/bin/dough-farmer @@ -1,22 +1,23 @@ #!/usr/bin/env python # -*- coding: utf8 -*- -import datetime +#import datetime import sys import time -import traceback +#import traceback -from dateutil.relativedelta import relativedelta -import zmq +#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 import billing from dough import context as dough_context from dough import db -from dough import exception +#from dough import exception +from kanyun.common.app import App utils.default_flagfile(filename='/etc/dough/dough.conf') flags.FLAGS(sys.argv) @@ -26,9 +27,13 @@ FLAGS = flags.FLAGS if __name__ == '__main__': from dough.billing import api + app = App(conf="dough/dough.conf", name="farmer") context = dough_context.get_admin_context() + context.app = app while True: current_time = utils.utcnow() + print "-" * 30, str(current_time) + app.info("------------ farming ----------") subscriptions = list() _subscriptions = db.subscription_get_all(context) for sub in _subscriptions: @@ -63,9 +68,17 @@ if __name__ == '__main__': created_at, updated_at, expires_at, order_unit, order_size, price, currency, region_name, item_name, interval_unit, interval_size, is_prepaid) in subscriptions: + print "-" * 20 + print "farming:subid=", subscription_id, "resid=", resource_uuid, "tid=", tenant_id + print "item_name=", item_name + action = getattr(api, status) - action(context, subscription_id, tenant_id, resource_uuid, + try: + action(context, subscription_id, tenant_id, item_name, resource_uuid, created_at, updated_at, expires_at, order_unit, order_size, price, currency, region_name, - item_name, interval_unit, interval_size, is_prepaid) - time.sleep(600) + interval_unit, interval_size, is_prepaid) + except Exception, e: + print e, subscription_id, item_name + print "" + time.sleep(60 * 2) diff --git a/bin/dough-manager b/bin/dough-manager new file mode 100755 index 0000000..6ca9449 --- /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 sys +import iso8601 +#from dateutil.relativedelta import relativedelta +#import zmq + +from nova import flags +from nova import utils + +from dough import context as dough_context +from dough import db +from dough import api +from nova.openstack.common import cfg +from dough.billing import driver + +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" % ("status", str(status)) + 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) + print datetime_from, datetime_to + context = dough_context.get_context(tenant_id=tenant_id) + data = api.query_report(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..26b2983 100644 --- a/dough/api.py +++ b/dough/api.py @@ -6,7 +6,7 @@ import iso8601 from nova import flags -from nova import utils +#from nova import utils from nova.openstack.common import cfg from dough import db @@ -45,6 +45,7 @@ def _product_get_all(context, region=None, item=None, item_type=None, products = db.product_get_all(context, filters=filters) except Exception, e: # TODO(lzyeval): report + print e raise return products @@ -67,12 +68,22 @@ def subscribe_item(context, region=None, item=None, item_type=None, payment_type=payment_type) # TODO(lzyeval): check if products size is not 1 values['product_id'] = products[0]['id'] + values['status'] = "verified" + print "subscription_create", item, payment_type, values + app = context.app + app.info("subscribe_item:proj_id=" + str(context.project_id) + \ + " name:" + str(resource_name) + \ + "/" + str(item) + \ + "/" + str(payment_type) + \ + "/" + str(resource_uuid)) subscription_ref = db.subscription_create(context, values) db.subscription_extend(context, subscription_ref['id'], subscription_ref['created_at']) + print "item subscribed." except Exception, e: # TODO(lzyeval): report + print "subscribe failed:", Exception, e raise return dict() @@ -82,24 +93,36 @@ def unsubscribe_item(context, region=None, item=None, """ """ try: + app = context.app + app.info("unsubscribe_item:" + str(region) + "/" + str(item) + "/" + str(resource_uuid)) subscription_id = 0 subscriptions = db.subscription_get_all_by_resource_uuid(context, resource_uuid) if not subscriptions: + print "unsubscribe_item.SubscriptionNotFoundByResourceUUID", resource_uuid raise exception.SubscriptionNotFoundByResourceUUID( resource_uuid=resource_uuid) for subscription in subscriptions: - if subscription['product']['region']['name'] != region: + product = subscription['product'] + if product['region']['name'] != region: continue - elif subscription['product']['item']['name'] != item: + elif product['item']['name'] != item: + continue + # TODO: status==verified + print subscription['status'] + if "floating_ip" == product['item']['name'] and "verified" != subscription['status']: continue subscription_id = subscription['id'] + break if not subscription_id: + print "subscription_get_by_resource_uuid", resource_uuid, "item=", item, "region=", region raise exception.SubscriptionNotFoundByRegionOrItem(region=region, item=item) + app.info("\tsubs_id=" + str(subscription_id)) db.subscription_destroy(context, subscription_id) except Exception, e: # TODO(lzyeval): report + print e raise return dict() @@ -136,6 +159,11 @@ def query_usage_report(context, timestamp_from=None, _subscriptions = db.subscription_get_all_by_project(context, context.project_id) for subscription in _subscriptions: + if subscription is None \ + or subscription['product'] is None \ + or subscription['product']['region'] is None \ + or subscription['product']['region']['name'] is None: + continue subscription_id = subscription['id'] resource_uuid = subscription['resource_uuid'] resource_name = subscription['resource_name'] @@ -174,6 +202,7 @@ def query_usage_report(context, timestamp_from=None, item_usage.append(usage_datum) return {'data': usage_report} + def query_monthly_report(context, timestamp_from=None, timestamp_to=None, **kwargs): @@ -182,12 +211,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() @@ -198,6 +229,11 @@ def find_timeframe(start_time, end_time, target): context.project_id) for subscription in _subscriptions: subscription_id = subscription['id'] + if subscription is None \ + or subscription['product'] is None \ + or subscription['product']['region'] is None \ + or subscription['product']['region']['name'] is None: + continue region_name = subscription['product']['region']['name'] item_name = subscription['product']['item']['name'] subscriptions.append([subscription_id, region_name, item_name]) @@ -218,3 +254,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()) + monthly_usage.setdefault(item_name, 0) + monthly_usage[item_name] = usage_datum + 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..0ffd3c9 100644 --- a/dough/billing/api.py +++ b/dough/billing/api.py @@ -17,48 +17,60 @@ # under the License. from dateutil.relativedelta import relativedelta - from nova import utils from dough import db from dough.billing import driver -def creating(context, subscription_id, tenant_id, resource_uuid, +def creating(context, subscription_id, tenant_id, item_name, resource_uuid, created_at, updated_at, expires_at, - order_unit, order_size, price, currency, region_name, - item_name, interval_unit, interval_size, is_prepaid): + order_unit, order_size, price, currency, region_name, + interval_unit, interval_size, is_prepaid): + app = context.app conn = driver.get_connection(item_name) if not conn.is_running(resource_uuid): + app.info("wait:%s creating, but %s not running." % (str(subscription_id), item_name)) if created_at + relativedelta(minutes=10) < utils.utcnow(): + app.info("%s(%s) status creating-->error" % (str(subscription_id), item_name)) db.subscription_error(context, subscription_id) # TODO(lzyeval): report else: interval_info = { interval_unit: interval_size, } + app.info("%s(%s) status creating-->verify" % (str(subscription_id), item_name)) db.subscription_verify(context, subscription_id) if is_prepaid: quantity = conn.get_usage(resource_uuid, expires_at - relativedelta(**interval_info), expires_at, order_size) + print "creating and is running", tenant_id, subscription_id, \ + quantity, order_size, "\033[1;33m", price, "\033[0m" + app.info("creating %s:subid=%s,tid=%s,price=%s" % (item_name, subscription_id, tenant_id, str(price))) charge(context, tenant_id, subscription_id, quantity, order_size, price) + else: + app.info("%s/%s/%s is_prepaid" % (tenant_id, str(subscription_id), item_name)) db.subscription_extend(context, subscription_id, expires_at + relativedelta(**interval_info)) -def deleting(context, subscription_id, tenant_id, resource_uuid, +def deleting(context, subscription_id, tenant_id, item_name, resource_uuid, created_at, updated_at, expires_at, - order_unit, order_size, price, currency, region_name, - item_name, interval_unit, interval_size, is_prepaid): + order_unit, order_size, price, currency, region_name, + interval_unit, interval_size, is_prepaid): + app = context.app conn = driver.get_connection(item_name) if not conn.is_terminated(resource_uuid): + app.info("wait:%s deleting, but %s not terminated." % (str(subscription_id), item_name)) if updated_at + relativedelta(minutes=10) < utils.utcnow(): + app.info("%s(%s) status deleting-->error" % (str(subscription_id), item_name)) db.subscription_error(context, subscription_id) # TODO(lzyeval): report else: # TODO(lzyeval): implement + app.info("%s(%s) status deleting-->terminated" % (str(subscription_id), item_name)) db.subscription_terminate(context, subscription_id) if not is_prepaid: interval_info = { @@ -67,17 +79,24 @@ 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" + app.info("deleting %s(%s),tid=%s,price=%s" % (subscription_id, item_name, tenant_id, str(price))) charge(context, tenant_id, subscription_id, quantity, order_size, price) + else: + app.info("%s/%s/%s is_prepaid" % (tenant_id, str(subscription_id), item_name)) -def verified(context, subscription_id, tenant_id, resource_uuid, +def verified(context, subscription_id, tenant_id, item_name, resource_uuid, created_at, updated_at, expires_at, - order_unit, order_size, price, currency, region_name, - item_name, interval_unit, interval_size, is_prepaid): + order_unit, order_size, price, currency, region_name, + interval_unit, interval_size, is_prepaid): + app = context.app conn = driver.get_connection(item_name) - if not conn.is_running(resource_uuid): + if not conn.is_running(resource_uuid, tenant_id=tenant_id): # FIXME(lzyeval): raise Exception() + app.info("%s verified, but %s not running." % (str(subscription_id), item_name)) return interval_info = { interval_unit: interval_size, @@ -85,23 +104,31 @@ 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" + app.info("verified %s:subid=%s,tid=%s,price=%s" % (item_name, subscription_id, tenant_id, str(price))) charge(context, tenant_id, subscription_id, quantity, order_size, price) db.subscription_extend(context, subscription_id, expires_at + relativedelta(**interval_info)) -def error(*args, **kwargs): +def error(context, subscription_id, tenant_id, item_name, *args, **kwargs): # TODO(lzyeval): report +# print "[BillingAPI]error", args, kwargs +# context.app.info("error:%s(%s)" % (subscription_id, item_name)) return def charge(context, tenant_id, subscription_id, quantity, order_size, price): if not quantity: return - line_total = price * quantity / order_size + line_total = price * quantity / order_size values = { 'subscription_id': subscription_id, 'quantity': quantity, 'line_total': line_total, } + print "purchase_create, tenant_id=%s, subid=%s" % (tenant_id, subscription_id) + print values + context.app.info("purchase_create:tenant_id=%s, subid=%s, line_total=%s" % (tenant_id, subscription_id, str(line_total))) db.purchase_create(context, values) diff --git a/dough/billing/driver/floating_ip.py b/dough/billing/driver/floating_ip.py index 5394fa2..3f6a8ad 100644 --- a/dough/billing/driver/floating_ip.py +++ b/dough/billing/driver/floating_ip.py @@ -29,7 +29,7 @@ service_type="compute") -def is_running(floating_ip_uuid): +def is_running(floating_ip_uuid, **kwargs): return not is_terminated(floating_ip_uuid) diff --git a/dough/billing/driver/instance.py b/dough/billing/driver/instance.py index f39c289..f805cec 100644 --- a/dough/billing/driver/instance.py +++ b/dough/billing/driver/instance.py @@ -29,7 +29,7 @@ service_type="compute") -def is_running(instance_uuid): +def is_running(instance_uuid, **kwargs): try: instance = NOVA_CLIENT.servers.get(instance_uuid) except Exception: diff --git a/dough/billing/driver/load_balancer.py b/dough/billing/driver/load_balancer.py index 31f3bd3..93d38d5 100644 --- a/dough/billing/driver/load_balancer.py +++ b/dough/billing/driver/load_balancer.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -import zmq +import zmq from nova import flags from nova import utils @@ -32,6 +32,7 @@ def __init__(self, protocol="tcp", host="localhost", port="80"): context = zmq.Context() self.handler = context.socket(zmq.REQ) self.handler.connect(url) + print "connect", url def __del__(self): self.handler.close() @@ -44,22 +45,42 @@ def send(self, msg_body): r_msg_type, r_msg_uuid, r_msg_body = self.handler.recv_multipart() assert (all([x == y for x, y in zip([msg_type, msg_uuid], [r_msg_type, r_msg_uuid])])) - result = utils.loads(r_msg_body)['msg'] + #result = utils.loads(r_msg_body)['msg'] + #if result['code'] == 500: + result = utils.loads(r_msg_body) + if "msg" in result: # FIXME: old version support + result = result["msg"] if result['code'] == 500: - raise Exception() + return None else: - return result['load_balancer_ids'] - + #return result['load_balancer_ids'] + return result['data'] +print "connect load_balancer:", FLAGS.demux_host, FLAGS.demux_port DEMUX_CLIENT = Client(host=FLAGS.demux_host, port=FLAGS.demux_port) -def is_running(load_balancer_uuid): +def is_running(load_balancer_uuid, **kwargs): # TODO(lzyeval): handle error - load_balancers = DEMUX_CLIENT.send({'cmd': 'read_load_balancer_id_all', - 'msg': {'user_name': 'foo', - 'tenant': 'bar',}}) - return load_balancer_uuid in load_balancers + #load_balancers = DEMUX_CLIENT.send({'cmd': 'read_load_balancer_id_all', + # 'msg': {'user_name': 'foo', + # 'tenant': 'bar'}}) + load_balancer = None + tenant_id = None + user_id = None + if "tenant_id" in kwargs: + tenant_id = kwargs['tenant_id'] + if "user_id" in kwargs: + user_id = kwargs['user_id'] + load_balancer = DEMUX_CLIENT.send({'method': 'get_load_balancer', + 'args': {'uuid': load_balancer_uuid, + 'tenant_id': tenant_id, + 'user_id': user_id}}) + print load_balancer_uuid, "load_balancer is_running:", load_balancer + if load_balancer is None: + return False + return 'state' in load_balancer \ + and load_balancer['state'] == "active" def is_terminated(load_balancer_uuid): diff --git a/dough/billing/driver/network.py b/dough/billing/driver/network.py index aa2f118..f09d954 100644 --- a/dough/billing/driver/network.py +++ b/dough/billing/driver/network.py @@ -69,7 +69,7 @@ def send(self, msg_body): KANYUN_CLIENT = Client(host=FLAGS.kanyun_host, port=FLAGS.kanyun_port) -def is_running(instance_uuid): +def is_running(instance_uuid, **kwargs): try: instance = NOVA_CLIENT.servers.get(instance_uuid) except Exception: 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..ae2e2be --- /dev/null +++ b/dough/client/dough_client.py @@ -0,0 +1,168 @@ +#!/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 json +import zmq + +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.'), + ] + +cli_opts = [ + cfg.StrOpt('monthly_report', + short='m', + default='name1', + help='monthly_report.'), + cfg.StrOpt('subscribe_item', + short='s', + default='default1', + help='subscribe_item.'), + cfg.StrOpt('unsubscribe_item', + short='u', + default='default1', + help='unsubscribe_item.'), + cfg.StrOpt('load_balancer', + short='l', + default='default1', + help='load_balancer.'), + ] + +FLAGS = flags.FLAGS +FLAGS.register_cli_opts(cli_opts) +FLAGS.register_opts(api_opts) +flags.FLAGS(sys.argv) + +from dough.billing.driver import load_balancer + +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 + + def subscribe_item(self, user_id, tenant_id, + resource_uuid, resource_name, region, item, + item_type, payment_type, timestamp): + request = STANDARD_PROTOCOL + request["method"] = "subscribe_item" + request["args"]["user_id"] = user_id + + request["args"]["tenant_id"] = tenant_id + request["args"]["resource_name"] = resource_name + request["args"]["region"] = region + request["args"]["resource_uuid"] = resource_uuid + request["args"]["item"] = item + request["args"]["item_type"] = item_type + request["args"]["payment_type"] = payment_type + request["args"]["timestamp"] = timestamp + + data = self.invoke(request) + return data + + def unsubscribe_item(self, user_id, tenant_id, region, resource_uuid, item, timestamp): + request = STANDARD_PROTOCOL + request["method"] = "unsubscribe_item" + + request["args"]["user_id"] = user_id + request["args"]["tenant_id"] = tenant_id + request["args"]["region"] = region + request["args"]["resource_uuid"] = resource_uuid + request["args"]["item"] = item + request["args"]["timestamp"] = timestamp + + data = self.invoke(request) + return data + + def load_balancer_get_all(self, user_id, tenant_id): + data = load_balancer.DEMUX_CLIENT.send({'method': 'get_all_load_balancers', + 'args': {'user_id': user_id, + 'tenant_id': tenant_id}}) + + return data + + def load_balancer_get(self, user_id, tenant_id, lb_id): + data = load_balancer.DEMUX_CLIENT.send({'method': 'get_load_balancer', + 'args': {'user_id': user_id, + 'tenant_id': tenant_id, + 'load_balancer_uuid': lb_id, + }}) + + return data + + def load_balancer_is_running(self, uuid): + ret = load_balancer.is_running(uuid) + return ret diff --git a/dough/db/sqlalchemy/api.py b/dough/db/sqlalchemy/api.py index 82cdd90..9ac222c 100644 --- a/dough/db/sqlalchemy/api.py +++ b/dough/db/sqlalchemy/api.py @@ -18,16 +18,14 @@ """Implementation of SQLAlchemy backend.""" -import datetime - -from sqlalchemy import and_ -from sqlalchemy import or_ -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import joinedload -from sqlalchemy.orm import joinedload_all -from sqlalchemy.sql import func -from sqlalchemy.sql.expression import asc -from sqlalchemy.sql.expression import desc +#from sqlalchemy import and_ +#from sqlalchemy import or_ +#from sqlalchemy.exc import IntegrityError +#from sqlalchemy.orm import joinedload +#from sqlalchemy.orm import joinedload_all +#from sqlalchemy.sql import func +#from sqlalchemy.sql.expression import asc +#from sqlalchemy.sql.expression import desc from sqlalchemy.sql.expression import literal_column from nova import utils @@ -241,6 +239,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).\ @@ -279,8 +286,8 @@ def product_get_all(context, filters=None): filters.items())) return model_query(context, models.Product).filter_by(**filters).all() -# subscriptions +# subscriptions def subscription_get(context, subscription_id): result = model_query(context, models.Subscription).\ filter_by(id=subscription_id).\ @@ -338,6 +345,7 @@ def subscription_error(context, subscription_id): def subscription_extend(context, subscription_id, datetime_to): + print "[DB]", subscription_id, "extend to", datetime_to session = get_session() with session.begin(): session.query(models.Subscription).\ @@ -352,9 +360,11 @@ 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', + 'status', 'resource_uuid'], filters.items())) return model_query(context, models.Subscription).filter_by(**filters).all() diff --git a/dough/db/sqlalchemy/models.py b/dough/db/sqlalchemy/models.py index 434a7ce..c0f4e01 100644 --- a/dough/db/sqlalchemy/models.py +++ b/dough/db/sqlalchemy/models.py @@ -22,7 +22,7 @@ from sqlalchemy.orm import relationship, backref -from sqlalchemy import Column, Integer, String +from sqlalchemy import Column, Integer, BigInteger, String from sqlalchemy import ForeignKey, DateTime, Boolean, Float from nova.db.sqlalchemy import models @@ -140,5 +140,5 @@ class Purchase(models.BASE, models.NovaBase): subscription_id = Column(Integer, ForeignKey(Subscription.id), nullable=False) - quantity = Column(Float, nullable=False) + quantity = Column(BigInteger, nullable=False) line_total = Column(Float, nullable=False) diff --git a/etc/dough/dough.conf.sample b/etc/dough/dough.conf.sample index 78cba15..03b09ee 100644 --- a/etc/dough/dough.conf.sample +++ b/etc/dough/dough.conf.sample @@ -1,17 +1,23 @@ +[dough-api] +log: /var/log/dough-api.log + +[farmer] +log: /var/log/dough-farmer.log + [DEFAULT] sql_connection=mysql://someuser:somepasswd@localhost/dough api_listen=localhost -api_listen_port=5557 +api_listen_port=5554 farmer_listen=localhost -farmer_listen_port=5558 +farmer_listen_port=5553 keystone_username=someusername keystone_password=somepassword keystone_tenant_name=sometenant keystone_auth_url=someurl demux_host=localhost -demux_port=5559 +demux_port=5556 kanyun_host=localhost -kanyun_port=5560 +kanyun_port=5552 mysql_host=localhost mysql_port=3306 mysql_user=nova diff --git a/tests/integration/db/test_api.py b/tests/integration/db/test_api.py index 72a8bd0..faf0097 100644 --- a/tests/integration/db/test_api.py +++ b/tests/integration/db/test_api.py @@ -656,7 +656,7 @@ def test_purchase_create(self): subscription_ref = db.subscription_create(self.context, values) values = { 'subscription_id': subscription_ref.id, - 'quantity': 1.56, + 'quantity': 1, 'line_total': 1.56 * product_ref.price, } expect = db.purchase_create(self.context, values) @@ -700,7 +700,7 @@ def test_purchase_destroy(self): subscription_ref = db.subscription_create(self.context, values) values = { 'subscription_id': subscription_ref.id, - 'quantity': 1.56, + 'quantity': 1, 'line_total': 1.56 * product_ref.price, } expect = db.purchase_create(self.context, values) @@ -746,7 +746,7 @@ def test_purchase_get_all_by_subscription_and_timeframe(self): subscription_ref = db.subscription_create(self.context, values) values = { 'subscription_id': subscription_ref.id, - 'quantity': 1.56, + 'quantity': 1, 'line_total': 1.56 * product_ref.price, } expect = db.purchase_create(self.context, values)