diff --git a/Makefile b/Makefile index 34f3b96..36de168 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ deb: clean init requirements.txt setup.py ./work/${INSTALL_PATH} @mkdir -p ${CONF_DIR} @cp ./examples/conf.yaml ${CONF_DIR} + @cp ./examples/csv_rates.yaml ${CONF_DIR} @ln -s ./work${INSTALL_PATH}/bin/${BILLING_PROGRAM} ./work${BINARY_PATH}/artifice-bill @fpm -s dir -t deb -n ${NAME} -v ${VERSION} \ --pre-install=packaging/scripts/pre_install.sh \ diff --git a/artifice/billing/csv_invoice.py b/artifice/billing/csv_invoice.py index 474cf89..511f26f 100644 --- a/artifice/billing/csv_invoice.py +++ b/artifice/billing/csv_invoice.py @@ -1,5 +1,6 @@ import os from csv import writer +import yaml class Csv(object): @@ -10,17 +11,45 @@ class Csv(object): self.closed = False self.start = None self.end = None + try: + fh = open(config["rates_file"]) + self.costs = yaml.load( fh.read() ) + fh.close() + except IOError: + # That's problem + print "couldn't load %s" % config["rates_file"] + raise + except KeyError: + # Couldn't find it! + print "Missing rates_file in config!" + raise def bill(self, usage): - # Usage is an ordered list? + # Usage is one of VMs, Storage, or Volumes. for element in usage: appendee = [] for key in self.config["row_layout"]: + if key == "cost": + # Ignore costs for now. + appendee.append(None) # What do we expect element to be? try: appendee.append( element.get(key) ) except AttributeError: appendee.append("") + + try: + x = self.config["row_layout"].index("cost") + print element.amount + print element.type + appendee[ x ] = element.amount * self.costs.get( element.type, 0 ) + + except ValueError: + # Not in this array. Well okay. + # We're not storing cost info, apparently. + raise RuntimeError("No costing information in CSV layout.") + + print appendee self.add_line(appendee) def add_line(self, line): @@ -58,5 +87,8 @@ class Csv(object): # Cheatery # Creates a dict on the fly from the row layout and the line value v = dict([(k, v) for k, v in zip(self.config["row_layout"], line)]) - total += v["cost"] or 0 + try: + total += float(v["cost"]) + except (TypeError, ValueError): + total += 0 return total \ No newline at end of file diff --git a/artifice/interface.py b/artifice/interface.py index cd941cd..9bae594 100644 --- a/artifice/interface.py +++ b/artifice/interface.py @@ -160,9 +160,10 @@ class Tenant(object): def __getitem__(self, item): try: - return getattr(self.tenant, item) + return self.tenant[item] except: - raise KeyError("No such key %s" % item) + print self.tenant + raise KeyError("No such key '%s' in tenant" % item) def __getattr__(self, attr): if attr not in self.tenant: diff --git a/artifice/models/resources.py b/artifice/models/resources.py index 6f50dfa..3ef7150 100644 --- a/artifice/models/resources.py +++ b/artifice/models/resources.py @@ -64,7 +64,9 @@ class BaseModelConstruct(object): class VM(BaseModelConstruct): - + # The only relevant meters of interest are the type of the interest + # and the amount of network we care about. + # Oh, and floating IPs. relevant_meters = ["instance:", "network.incoming.bytes", "network.outgoing.bytes"] def _fetch_meter_name(self, name): @@ -74,7 +76,7 @@ class VM(BaseModelConstruct): @property def amount(self): - return self.size + return 1 @property def type(self): @@ -110,7 +112,6 @@ class VM(BaseModelConstruct): def name(self): return self._raw["metadata"]["display_name"] - class Object(BaseModelConstruct): relevant_meters = ["storage.objects.size"] @@ -145,3 +146,5 @@ class Volume(BaseModelConstruct): # Size of the thing over time. return self._raw.meter("volume.size", self.start, self.end).volume() +class Network(BaseModelConstruct): + relevant_meters = ["ip.floating"] \ No newline at end of file diff --git a/bin/bill.py b/bin/bill.py index 510320e..7eb2360 100644 --- a/bin/bill.py +++ b/bin/bill.py @@ -1,7 +1,21 @@ #!/usr/bin/env python -from artifice import interface +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" @@ -27,12 +41,20 @@ if __name__ == '__main__': 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="/etc/niceometer/conf.yaml") + 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 - n = niceometer.Niceometer(conf["username"], conf["password"], conf["admin_tenant"]) + n = interface.Artifice(conf["username"], conf["password"], conf["admin_tenant"]) tenants = args.tenants if not args.tenants: # only parse this list of tenants diff --git a/bin/usage.py b/bin/usage.py index edeb9fe..3ad6691 100644 --- a/bin/usage.py +++ b/bin/usage.py @@ -19,8 +19,10 @@ 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" -date_fmt_fnc = lambda x: datetime.datetime.strptime(date_fmt) +def date_fmt_fnc(val): + return datetime.datetime.strptime(val, date_fmt) if __name__ == '__main__': import argparse @@ -81,6 +83,9 @@ if __name__ == '__main__': 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 + + invoice.bill(usage.vms) + invoice.bill(usage.volumes) + invoice.bill(usage.objects) print invoice.total() \ No newline at end of file diff --git a/examples/conf.yaml b/examples/conf.yaml index 76f6db9..8769df4 100644 --- a/examples/conf.yaml +++ b/examples/conf.yaml @@ -1,6 +1,6 @@ --- ceilometer: - host: http://localhost:8777/v2 + host: http://localhost:8777/ database: database: artifice host: localhost @@ -18,6 +18,7 @@ invoice_object: - end - amount - cost + rates_file: /opt/stack/artifice/etc/artifice/csv_rates.yaml main: invoice:object: billing.csv_invoice:Csv openstack: diff --git a/examples/csv_rates.yaml b/examples/csv_rates.yaml new file mode 100644 index 0000000..f187a66 --- /dev/null +++ b/examples/csv_rates.yaml @@ -0,0 +1,6 @@ +--- +storage.objects.size: 15 +m1.nano: 5 +network.incoming.bytes: 5 +network.outgoing.bytes: 1 +volume.size: 25 \ No newline at end of file diff --git a/tests/data/csv_rates.yaml b/tests/data/csv_rates.yaml new file mode 100644 index 0000000..f187a66 --- /dev/null +++ b/tests/data/csv_rates.yaml @@ -0,0 +1,6 @@ +--- +storage.objects.size: 15 +m1.nano: 5 +network.incoming.bytes: 5 +network.outgoing.bytes: 1 +volume.size: 25 \ No newline at end of file diff --git a/tests/test_csv.py b/tests/test_csv.py index 33431ea..15d37c9 100644 --- a/tests/test_csv.py +++ b/tests/test_csv.py @@ -5,14 +5,22 @@ import os import glob import mock -import csv +import csv, yaml + +try: + fn = os.path.abspath(__file__) + path, f = os.path.split(fn) +except NameError: + path = os.getcwd() + test_interface.config["invoice_object"] = { "output_path": "./", "output_file": "%(tenant)s-%(start)s-%(end)s.csv", "delimiter": ",", - "row_layout": ["location", "type", "start", "end", "amount", "cost"] + "row_layout": ["location", "type", "start", "end", "amount", "cost"], + "rates_file": os.path.join( path, "data/csv_rates.yaml") } test_interface.config["main"]["invoice:object"] = "billing.csv_invoice:Csv" @@ -66,5 +74,11 @@ class TestInvoice(test_interface.TestInterface): rows = [row for row in r] # slurp fh.close() + # We need to grab the costing info here + + fh = open(test_interface.config["invoice_object"]["rates_file"]) + y = yaml.load(fh.read()) + fh.close() + for uvm, cvm in zip(self.usage.vms, rows): - self.assertEqual( uvm.amount, cvm[-2] ) + self.assertEqual( uvm.amount * y.get(uvm.type, 0) , float(cvm[-2]) ) diff --git a/tests/test_interface.py b/tests/test_interface.py index 2998866..ec7dcbf 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -198,10 +198,10 @@ class TestInterface(unittest.TestCase): auth_url= config["openstack"]["authentication_url"] ) tenants = None - try: - tenants = artifice.tenants - except Exception as e: - self.fail(e) + # try: + tenants = artifice.tenants + # except Exception as e: + # self.fail(e) # self.assertEqual ( len(tenants.vms), 1 )