diff --git a/api/web.py b/api/web.py index dc5292b..c894185 100644 --- a/api/web.py +++ b/api/web.py @@ -15,6 +15,9 @@ db = Session() config = load_config() + +# Some useful constants + iso_time = "%Y-%m-%dT%H:%M:%S" iso_date = "%Y-%m-%d" @@ -47,9 +50,14 @@ def keystone(func): return _perform_keystone +# TODO: fill me in +def must(*args): + return lambda(func): func + @app.get("/usage") @app.get("/usage/{resource_id}") # also allow for querying by resource ID. @keystone +@must("resource_id", "tenant") def retrieve_usage(resource_id=None): """Retrieves usage for a given tenant ID. Tenant ID will be passed in via the query string. @@ -99,33 +107,108 @@ def retrieve_usage(resource_id=None): @app.post("/usage") @keystone -def add_usage(self): +@must("amount", "start", "end", "tenant") +def add_usage(): """ - Adds usage for a given tenant T. + Adds usage for a given tenant T and resource R. Expects to receive a Resource ID, a time range, and a volume. The volume will be parsed from JSON as a Decimal object. """ body = json.loads(request.body, parse_float=Decimal) + db.begin() + for resource in body["resources"]: + start = datetime.strptime(resource.get("start"), date_iso) + end = datetime.strptime(resource.get("end"), date_iso) + id_ = resource["id"] + u = usage.Usage( + resource=id_, + tenant=request.params["tenant"], + value=resource["amount"], + start=start, + end=end) + db.add(u) + try: + db.commit() + except Exception as e: + # Explodytime + status(500) + return(json.dumps( + {"status": "error", + "error" : "database transaction error" + })) + status(201) + return json.dumps({ + "status": "ok", + "saved": len(body["resources"]) + }) -@app.get("/bill") -@app.get("/bill/{id}") +@app.get("/bills/{id}") @keystone -def get_bill(): +@must("tenant", "start", "end") +def get_bill(id_): """ Returns either a single bill or a set of the most recent bills for a given Tenant. """ - pass + + # TODO: Put these into an input validator instead + try: + start = datetime.strptime(request.params["start"], date_iso) + except: + abort( + 403, + json.dumps( + {"status":"error", + "error": "start date is not ISO-compliant"}) + ) + try: + end = datetime.strptime(request.params["end"], date_iso) + except: + abort( + 403, + json.dumps( + {"status":"error", + "error": "end date is not ISO-compliant"}) + ) + + try: + bill = BillInterface(session).get(id_) + except: + abort(404) + + if not bill: + abort(404) + + resp = {"status": "ok", + "bill": [], + "total": str(bill.total), + "tenant": bill.tenant_id + } + + for resource in billed: + resp["bill"].append({ + 'resource_id': bill.resource_id, + 'volume': str( bill.volume ), + 'rate': str( bill.rate ), + # 'metadata': # TODO: This will be filled in with extra data + }) - - - -@app.get("/bill/{bill_id}") + return (200, json.dumps(resp)) + +@app.post("/usage/current") @keystone -def get_bill_by_id(bill_id=None): +@must("tenant_id") +def get_current_usage(): + """ + Is intended to return a running total of the current billing periods' + dataset. Performs a Rate transformer on each transformed datapoint and + returns the result. + + TODO: Implement + """ pass @app.post("/bill") @@ -147,6 +230,29 @@ def make_a_bill(): end = body.get("end", None) if not start or not end: return abort(403) # All three *must* be defined - bill = usage.Bill() + - # total = + bill = BillInterface(session) + thebill = bill.generate(body["tenant_id"], start, end) + # assumes the bill is saved + + if not thebill.is_saved: + # raise an error + abort(500) + + resp = {"status":"created", + "id": thebill.id, + "contents": [], + "total": None + } + for resource in thebill.resources: + total += Decimal(billed.total) + resp["contents"].append({ + 'resource_id': bill.resource_id, + 'volume': str( bill.volume ), + 'rate': str( bill.rate ), + # 'metadata': # TODO: This will be filled in with extra data + }) + + resp["total"] = thebill.total + return (201, json.dumps(resp)) diff --git a/bin/bill b/bin/bill deleted file mode 100755 index 9730583..0000000 --- a/bin/bill +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -INSTALLED="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -ORIGIN=`pwd` - -# Bring up our python environment -# Pass through all our command line opts as expected - -$INSTALLED/../env/bin/python $INSTALLED/bill.py $@ \ No newline at end of file diff --git a/bin/bill.py b/bin/bill.py deleted file mode 100644 index ae600f7..0000000 --- a/bin/bill.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python - -import sys, os - -try: - from artifice import interface -except ImportError: - loc, fn = os.path.split(__file__) - print loc - here = os.path.abspath(os.path.join(loc + "/../")) - sys.path.insert(0, here) - # # Are we potentially in a virtualenv? Add that in. - # if os.path.exists( os.path.join(here, "lib/python2.7" ) ): - # sys.path.insert(1, os.path.join(here, "lib/python2.7")) - from artifice import interface - -import datetime -import yaml - -date_format = "%Y-%m-%dT%H:%M:%S" -other_date_format = "%Y-%m-%dT%H:%M:%S.%f" - -date_fmt_fnc = lambda x: datetime.datetime.strptime(date_format) - -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser() - # Takes names to display. - # none means display them all. - parser.add_argument("-t", "--tenant", dest="tenants", - help='Tenant to display', action="append", default=[]) - - # Add some sections to show data from. - # Empty is display all - parser.add_argument("-s", "--section", dest="sections", - help="Sections to display", action="append") - - # Ranging - # We want to get stuff from, to. - - parser.add_argument( - "--from", - dest="start", - help="When to start our range, date format %s" % ( date_fmt.replace("%","%%") ) , - type=date_fmt_fnc, - default=datetime.datetime.now() - datetime.timedelta(days=31) - ) - parser.add_argument( - "--to", dest="end", - help="When to end our date range. Defaults to yesterday.", - type=date_fmt_fnc, - default=datetime.datetime.now() - datetime.timedelta(days=1)) - - parser.add_argument("--config", dest="config", help="Config file", - default="/opt/stack/artifice/etc/artifice/conf.yaml") - - args = parser.parse_args() - - try: - conf = yaml.load(open(args.config).read()) - except IOError: - # Whoops - print "couldn't load %s " % args.config - sys.exit(1) - - # Make ourselves a nice interaction object - # Password needs to be loaded from /etc/artifice/database - fh = open(conf["database"]["password_path"]) - password = fh.read() - fh.close() - # Make ourselves a nice interaction object - conf["database"]["password"] = password.replace("\n", "") - n = interface.Artifice(conf) - tenants = args.tenants - if not args.tenants: - # only parse this list of tenants - tenants = n.tenants - - print "\n # ----------------- bill summary for: ----------------- # " - print "Range: %s -> %s" % (args.start, args.end) - - for tenant_name in tenants: - # artifact = n.tenant(tenant_name).section(section).usage(args.start, args.end) - # data should now be an artifact-like construct. - # Data also knows about Samples, where as an Artifact doesn't. - - # An artifact knows its section - tenant = n.tenant(tenant_name) - # Makes a new invoice up for this tenant. - invoice = tenant.invoice(args.start, args.end) - print "\n# ------------------------ Tenant: %s ------------------------ #" % tenant.name - - # usage = tenant.usage(start, end) - usage = tenant.usage(args.start, args.end) - # A Usage set is the entirety of time for this Tenant. - # It's not time-limited at all. - # But the - usage.save() - invoice.bill(usage.vms) - invoice.bill(usage.volumes) - invoice.bill(usage.objects) - invoice.bill(usage.networks) - invoice.close() - - print invoice.total() - print "# --------------------- End of Tenant: %s --------------------- #" % tenant.name - - # for datacenter, sections in usage.iteritems(): - # # DC is the name of the DC/region. Or the internal code. W/E. - # print datacenter - - # for section_name in args.sections: - # assert section in sections - - # # section = sections[ section ] - # print sections[section_name] - # for resources in sections[section_name]: - # for resource in resources: - # print resource - # for meter in resource.meters: - # usage = meter.usage(start, end) - # if usage.has_been_saved(): - # continue - # print usage.volume() - # print usage.cost() - # usage.save() - # # Finally, bill it. - # # All of these things need to be converted to the - # # publicly-viewable version now. - # invoice.bill(datacenter, resource, meter, usage) - - # # Section is going to be in the set of vm, network, storage, image - # # # or just all of them. - # # # It's not going to be an individual meter name. - # # artifacts = section.usage(args.start, args.end) - # # for artifact in artifacts: - # # if artifact.has_been_saved: - # # # Does this artifact exist in the DB? - # # continue - # # artifact.save() # Save to the Artifact storage - # # # Saves to the invoice. - # # invoice.bill ( artifact ) - # # # artifact.bill( invoice.id ) - # # print "%s: %s" % (section.name, artifact.volume) diff --git a/bin/bill.py~ b/bin/bill.py~ deleted file mode 100644 index 5c26c05..0000000 --- a/bin/bill.py~ +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python - -import sys, os - -try: - from artifice import interface -except ImportError: - loc, fn = os.path.split(__file__) - print loc - here = os.path.abspath(os.path.join(loc +"/../")) - sys.path.insert(0, here) - # # Are we potentially in a virtualenv? Add that in. - # if os.path.exists( os.path.join(here, "lib/python2.7" ) ): - # sys.path.insert(1, os.path.join(here, "lib/python2.7")) - from artifice import interface - -import datetime -import yaml - -date_format = "%Y-%m-%dT%H:%M:%S" -other_date_format = "%Y-%m-%dT%H:%M:%S.%f" - -date_fmt_fnc = lambda x: datetime.datetime.strptime(date_format) - -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser() - # Takes names to display. - # none means display them all. - parser.add_argument("-t", "--tenant", dest="tenants", help='Tenant to display', action="append", default=[]) - - # Add some sections to show data from. - # Empty is display all - parser.add_argument("-s", "--section", dest="sections", help="Sections to display", action="append") - - - # Ranging - # We want to get stuff from, to. - - parser.add_argument( - "--from", - dest="start", - help="When to start our range, date format %s", - type=date_fmt_fnc, - default=datetime.datetime.now() - datetime.timedelta(days=31) - ) - parser.add_argument("--to", dest="end", help="When to end our date range. Defaults to yesterday.", - type=date_fmt_fnc, default=datetime.datetime.now() - datetime.timedelta(days=1) ) - - parser.add_argument("--config", dest="config", help="Config file", default="/opt/stack/artifice/etc/artifice/conf.yaml") - - args = parser.parse_args() - - try: - conf = yaml.load(open(args.config).read()) - except IOError: - # Whoops - print "couldn't load %s " % args.config - sys.exit(1) - - - # Make ourselves a nice interaction object - # Password needs to be loaded from /etc/artifice/database - fh = open(conf["database"]["password_path"]) - password = fh.read() - fh.close() - # Make ourselves a nice interaction object - conf["database"]["password"] = password - n = interface.Artifice(conf) - tenants = args.tenants - if not args.tenants: - # only parse this list of tenants - tenants = n.tenants - - print "\n # ----------------- bill summary for: ----------------- # " - print "Range: %s -> %s" % (args.start, args.end) - - for tenant_name in tenants: - # artifact = n.tenant(tenant_name).section(section).usage(args.start, args.end) - # data should now be an artifact-like construct. - # Data also knows about Samples, where as an Artifact doesn't. - - # An artifact knows its section - tenant = n.tenant(tenant_name) - # Makes a new invoice up for this tenant. - invoice = tenant.invoice(args.start, args.end) - print "\n# ------------------------ Tenant: %s ------------------------ #" % tenant.name - - # usage = tenant.usage(start, end) - usage = tenant.usage(args.start, args.end) - # A Usage set is the entirety of time for this Tenant. - # It's not time-limited at all. - # But the - usage.save() - invoice.bill(usage.vms) - invoice.bill(usage.volumes) - # invoice.bill(usage.objects) - invoice.close() - - print invoice.total() - print "# --------------------- End of Tenant: %s --------------------- #" % tenant.name - - # for datacenter, sections in usage.iteritems(): - # # DC is the name of the DC/region. Or the internal code. W/E. - # print datacenter - - # for section_name in args.sections: - # assert section in sections - - # # section = sections[ section ] - # print sections[section_name] - # for resources in sections[section_name]: - # for resource in resources: - # print resource - # for meter in resource.meters: - # usage = meter.usage(start, end) - # if usage.has_been_saved(): - # continue - # print usage.volume() - # print usage.cost() - # usage.save() - # # Finally, bill it. - # # All of these things need to be converted to the - # # publicly-viewable version now. - # invoice.bill(datacenter, resource, meter, usage) - - # # Section is going to be in the set of vm, network, storage, image - # # # or just all of them. - # # # It's not going to be an individual meter name. - # # artifacts = section.usage(args.start, args.end) - # # for artifact in artifacts: - # # if artifact.has_been_saved: - # # # Does this artifact exist in the DB? - # # continue - # # artifact.save() # Save to the Artifact storage - # # # Saves to the invoice. - # # invoice.bill ( artifact ) - # # # artifact.bill( invoice.id ) - # # print "%s: %s" % (section.name, artifact.volume) diff --git a/bin/usage.py~ b/bin/usage.py~ deleted file mode 100644 index 5e5200e..0000000 --- a/bin/usage.py~ +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python - -import os, sys - -try: - from artifice import interface -except ImportError: - loc, fn = os.path.split(__file__) - print loc - here = os.path.abspath(os.path.join(loc +"/../")) - sys.path.insert(0, here) - # # Are we potentially in a virtualenv? Add that in. - # if os.path.exists( os.path.join(here, "lib/python2.7" ) ): - # sys.path.insert(1, os.path.join(here, "lib/python2.7")) - from artifice import interface - -import datetime -import yaml - -date_format = "%Y-%m-%dT%H:%M:%S" -other_date_format = "%Y-%m-%dT%H:%M:%S.%f" -date_fmt = "%Y-%m-%d" - -def date_fmt_fnc(val): - return datetime.datetime.strptime(val, date_fmt) - -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser() - # Takes names to display. - # none means display them all. - parser.add_argument("-t", "--tenant", dest="tenants", help='Tenant to display', action="append", default=[]) - - # Add some sections to show data from. - # Empty is display all - parser.add_argument("-s", "--section", dest="sections", help="Sections to display", action="append") - - - # Ranging - # We want to get stuff from, to. - - parser.add_argument( - "--from", - dest="start", - help="When to start our range, date format %s", - type=date_fmt_fnc, - default=datetime.datetime.now() - datetime.timedelta(days=31) - ) - parser.add_argument("--to", dest="end", help="When to end our date range. Defaults to yesterday.", - type=date_fmt_fnc, default=datetime.datetime.now() - datetime.timedelta(days=1) ) - - parser.add_argument("-c", "--config", dest="config", help="Config file", default="/opt/stack/artifice/etc/artifice/conf.yaml") - - args = parser.parse_args() - print "\n # ----------------- usage summary for: ----------------- # " - print "Range: %s -> %s" % (args.start, args.end) - try: - conf = yaml.load(open(args.config).read()) - except IOError: - # Whoops - print "couldn't load %s " % args.config - sys.exit(1) - - fh = open(conf["database"]["password_path"]) - password = fh.read() - fh.close() - # Make ourselves a nice interaction object - conf["database"]["password"] = password - instance = interface.Artifice(conf) - tenants = args.tenants - if not args.tenants: - # only parse this list of tenants - tenants = instance.tenants - - for tenant_name in tenants: - # artifact = n.tenant(tenant_name).section(section).usage(args.start, args.end) - # data should now be an artifact-like construct. - # Data also knows about Samples, where as an Artifact doesn't. - - # An artifact knows its section - tenant = instance.tenant(tenant_name) - # Makes a new invoice up for this tenant. - invoice = tenant.invoice(args.start, args.end) - print "\n# ------------------------ Tenant: %s ------------------------ #" % tenant.name - - - # usage = tenant.usage(start, end) - usage = tenant.usage(args.start, args.end) - # A Usage set is the entirety of time for this Tenant. - # It's not time-limited at all. - - invoice.bill(usage.vms) - invoice.bill(usage.volumes) - # invoice.bill(usage.objects) - - print "Total invoice value: %s" % invoice.total() - print "# --------------------- End of Tenant: %s --------------------- #" % tenant.name