Adding a rates file for CSV output. Updated testing for CSV stuff. Tweaks related to generating the debfiles and testing the debfiles.

This commit is contained in:
Aurynn Shaw
2013-09-04 16:28:40 +12:00
parent 0ec26b2933
commit 5a402fb12b
11 changed files with 111 additions and 20 deletions

View File

@@ -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 \

View File

@@ -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

View File

@@ -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:

View File

@@ -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:<type>", "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"]

View File

@@ -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

View File

@@ -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()

View File

@@ -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:

6
examples/csv_rates.yaml Normal file
View File

@@ -0,0 +1,6 @@
---
storage.objects.size: 15
m1.nano: 5
network.incoming.bytes: 5
network.outgoing.bytes: 1
volume.size: 25

View File

@@ -0,0 +1,6 @@
---
storage.objects.size: 15
m1.nano: 5
network.incoming.bytes: 5
network.outgoing.bytes: 1
volume.size: 25

View File

@@ -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]) )

View File

@@ -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 )