Initial pass at creating file loader mixins for example CSV invoice class.
This commit is contained in:
parent
3b1ff457be
commit
b2e9daa173
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
*.pyc
|
||||
*.deb
|
||||
.vagrant
|
||||
work
|
||||
work
|
||||
*.swp
|
||||
|
@ -1,8 +1,9 @@
|
||||
import os
|
||||
from csv import writer
|
||||
from artifice import invoice
|
||||
import yaml
|
||||
|
||||
class Csv(object):
|
||||
class Csv(invoice.Invoice):
|
||||
|
||||
def __init__(self, tenant, config):
|
||||
self.config = config
|
||||
@ -11,8 +12,10 @@ class Csv(object):
|
||||
self.closed = False
|
||||
self.start = None
|
||||
self.end = None
|
||||
# Should the rates information be part of the CSV code,
|
||||
# or part of the full run?
|
||||
try:
|
||||
fh = open(config["rates_file"])
|
||||
fh = open(config["rates"][ "file" ])
|
||||
self.costs = yaml.load( fh.read() )
|
||||
fh.close()
|
||||
except IOError:
|
||||
@ -32,7 +35,13 @@ class Csv(object):
|
||||
if key == "cost":
|
||||
# Ignore costs for now.
|
||||
appendee.append(None)
|
||||
continue
|
||||
# What do we expect element to be?
|
||||
if key == "type":
|
||||
# Fetch the 'pretty' name from the mappings, if any
|
||||
# The default is that this returns the internal name
|
||||
appendee.append(self.pretty_name(element.get(key)))
|
||||
continue
|
||||
try:
|
||||
appendee.append( element.get(key) )
|
||||
except AttributeError:
|
||||
@ -40,7 +49,8 @@ class Csv(object):
|
||||
|
||||
try:
|
||||
x = self.config["row_layout"].index("cost")
|
||||
appendee[ x ] = element.amount.volume() * self.costs.get( element.type, 0 )
|
||||
appendee[ x ] = element.amount.volume() * \
|
||||
self.costs.get( element.type, 0 )
|
||||
|
||||
except ValueError:
|
||||
# Not in this array. Well okay.
|
||||
@ -89,4 +99,4 @@ class Csv(object):
|
||||
total += float(v["cost"])
|
||||
except (TypeError, ValueError):
|
||||
total += 0
|
||||
return total
|
||||
return total
|
||||
|
@ -39,6 +39,7 @@ from .models import resources, tenants, usage
|
||||
# Most of the time we use date_format
|
||||
date_format = "%Y-%m-%dT%H:%M:%S"
|
||||
# Sometimes things also have milliseconds, so we look for that too.
|
||||
# Because why not be annoying in all the ways?
|
||||
other_date_format = "%Y-%m-%dT%H:%M:%S.%f"
|
||||
|
||||
def get_meter(meter, start, end, auth):
|
||||
@ -130,6 +131,7 @@ class Artifice(object):
|
||||
)
|
||||
self._tenancy = None
|
||||
|
||||
|
||||
def host_to_dc(self, host):
|
||||
"""
|
||||
:param host: The name to use.
|
||||
@ -143,7 +145,8 @@ class Artifice(object):
|
||||
|
||||
def tenant(self, name):
|
||||
"""
|
||||
Returns a Tenant object describing the specified Tenant by name, or raises a NotFound error.
|
||||
Returns a Tenant object describing the specified Tenant by
|
||||
name, or raises a NotFound error.
|
||||
"""
|
||||
# Returns a Tenant object for the given name.
|
||||
# Uses Keystone API to perform a direct name lookup,
|
||||
|
@ -60,6 +60,14 @@ class Invoice(object):
|
||||
|
||||
def close(self):
|
||||
raise NotImplementedError("Not implemented in base class")
|
||||
|
||||
def pretty_name(self, name):
|
||||
return name
|
||||
|
||||
def rate(self, name):
|
||||
"""Returns the rate for a given internal name
|
||||
Expected"""
|
||||
return 0
|
||||
|
||||
def bill(self, usage):
|
||||
|
||||
@ -86,4 +94,61 @@ class Invoice(object):
|
||||
raise NotImplementedError("Not implemented in base class")
|
||||
|
||||
def total(self):
|
||||
raise NotImplementedError("Not implemented in the base class")
|
||||
raise NotImplementedError("Not implemented in the base class")
|
||||
|
||||
import csv
|
||||
class RatesFile(object):
|
||||
# Mixin
|
||||
# Adds a rates file loader, expecting various things from the
|
||||
# configuration
|
||||
|
||||
def rate(self, name):
|
||||
if not self.__rates:
|
||||
self.__rates = {}
|
||||
try:
|
||||
fh = open(config["rates"][ "file" ])
|
||||
reader = csv.reader(fh) # Makes no opinions on the file structure
|
||||
for row in reader:
|
||||
# The default layout is expected to be:
|
||||
# location | rate name | rate measurement | rate value
|
||||
self.__rates[row[1]] = {
|
||||
"cost": row[3],
|
||||
"region": row[0],
|
||||
"measures": row[2]
|
||||
}
|
||||
fh.close()
|
||||
except KeyError:
|
||||
# couldn't actually find the useful info for rateS?
|
||||
print "Couldn't find rates info configuration option!"
|
||||
raise
|
||||
except IOError:
|
||||
print "Couldn't open the file!"
|
||||
raise
|
||||
return self.__rates[name]["cost"] # ignore the regions-ness for now
|
||||
|
||||
class NamesFile(object):
|
||||
|
||||
# Mixin
|
||||
# Adds a name prettifier
|
||||
#
|
||||
def pretty_name(self, name):
|
||||
if not self.__names:
|
||||
self.__names = {}
|
||||
try:
|
||||
fh = open(config["rates"][ "file" ])
|
||||
reader = csv.reader(fh) # Makes no opinions on the file structure
|
||||
for row in reader:
|
||||
# The default layout is expected to be:
|
||||
# internal name | external name
|
||||
self.__names[row[0]] = row[1]
|
||||
|
||||
fh.close()
|
||||
except KeyError:
|
||||
# couldn't actually find the useful info for rateS?
|
||||
print "Couldn't find rates info configuration option!"
|
||||
raise
|
||||
except IOError:
|
||||
print "Couldn't open the file!"
|
||||
raise
|
||||
return self.__names[name]
|
||||
|
||||
|
@ -18,11 +18,13 @@ invoice_object:
|
||||
- end
|
||||
- amount
|
||||
- cost
|
||||
rates_file: /opt/stack/artifice/etc/artifice/csv_rates.yaml
|
||||
rates:
|
||||
file: /opt/stack/artifice/etc/artifice/csv_rates.csv
|
||||
names: /opt/stack/artifice/etc/artifice/csv_names.csv
|
||||
main:
|
||||
invoice:object: billing.csv_invoice:Csv
|
||||
openstack:
|
||||
authentication_url: http://localhost:35357/v2.0
|
||||
default_tenant: demo
|
||||
username: admin
|
||||
password: openstack
|
||||
password: openstack
|
||||
|
0
examples/csv_names.csv
Normal file
0
examples/csv_names.csv
Normal file
|
0
examples/csv_rates.csv
Normal file
0
examples/csv_rates.csv
Normal file
|
Loading…
x
Reference in New Issue
Block a user