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:
1
Makefile
1
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 \
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
28
bin/bill.py
28
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
|
||||
|
||||
@@ -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()
|
||||
@@ -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
6
examples/csv_rates.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
storage.objects.size: 15
|
||||
m1.nano: 5
|
||||
network.incoming.bytes: 5
|
||||
network.outgoing.bytes: 1
|
||||
volume.size: 25
|
||||
6
tests/data/csv_rates.yaml
Normal file
6
tests/data/csv_rates.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
storage.objects.size: 15
|
||||
m1.nano: 5
|
||||
network.incoming.bytes: 5
|
||||
network.outgoing.bytes: 1
|
||||
volume.size: 25
|
||||
@@ -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]) )
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
Reference in New Issue
Block a user