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
|
*.pyc
|
||||||
*.deb
|
*.deb
|
||||||
.vagrant
|
.vagrant
|
||||||
work
|
work
|
||||||
|
*.swp
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
from csv import writer
|
from csv import writer
|
||||||
|
from artifice import invoice
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
class Csv(object):
|
class Csv(invoice.Invoice):
|
||||||
|
|
||||||
def __init__(self, tenant, config):
|
def __init__(self, tenant, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -11,8 +12,10 @@ class Csv(object):
|
|||||||
self.closed = False
|
self.closed = False
|
||||||
self.start = None
|
self.start = None
|
||||||
self.end = None
|
self.end = None
|
||||||
|
# Should the rates information be part of the CSV code,
|
||||||
|
# or part of the full run?
|
||||||
try:
|
try:
|
||||||
fh = open(config["rates_file"])
|
fh = open(config["rates"][ "file" ])
|
||||||
self.costs = yaml.load( fh.read() )
|
self.costs = yaml.load( fh.read() )
|
||||||
fh.close()
|
fh.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
@ -32,7 +35,13 @@ class Csv(object):
|
|||||||
if key == "cost":
|
if key == "cost":
|
||||||
# Ignore costs for now.
|
# Ignore costs for now.
|
||||||
appendee.append(None)
|
appendee.append(None)
|
||||||
|
continue
|
||||||
# What do we expect element to be?
|
# 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:
|
try:
|
||||||
appendee.append( element.get(key) )
|
appendee.append( element.get(key) )
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -40,7 +49,8 @@ class Csv(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
x = self.config["row_layout"].index("cost")
|
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:
|
except ValueError:
|
||||||
# Not in this array. Well okay.
|
# Not in this array. Well okay.
|
||||||
@ -89,4 +99,4 @@ class Csv(object):
|
|||||||
total += float(v["cost"])
|
total += float(v["cost"])
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
total += 0
|
total += 0
|
||||||
return total
|
return total
|
||||||
|
@ -39,6 +39,7 @@ from .models import resources, tenants, usage
|
|||||||
# Most of the time we use date_format
|
# Most of the time we use date_format
|
||||||
date_format = "%Y-%m-%dT%H:%M:%S"
|
date_format = "%Y-%m-%dT%H:%M:%S"
|
||||||
# Sometimes things also have milliseconds, so we look for that too.
|
# 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"
|
other_date_format = "%Y-%m-%dT%H:%M:%S.%f"
|
||||||
|
|
||||||
def get_meter(meter, start, end, auth):
|
def get_meter(meter, start, end, auth):
|
||||||
@ -130,6 +131,7 @@ class Artifice(object):
|
|||||||
)
|
)
|
||||||
self._tenancy = None
|
self._tenancy = None
|
||||||
|
|
||||||
|
|
||||||
def host_to_dc(self, host):
|
def host_to_dc(self, host):
|
||||||
"""
|
"""
|
||||||
:param host: The name to use.
|
:param host: The name to use.
|
||||||
@ -143,7 +145,8 @@ class Artifice(object):
|
|||||||
|
|
||||||
def tenant(self, name):
|
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.
|
# Returns a Tenant object for the given name.
|
||||||
# Uses Keystone API to perform a direct name lookup,
|
# Uses Keystone API to perform a direct name lookup,
|
||||||
|
@ -60,6 +60,14 @@ class Invoice(object):
|
|||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
raise NotImplementedError("Not implemented in base class")
|
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):
|
def bill(self, usage):
|
||||||
|
|
||||||
@ -86,4 +94,61 @@ class Invoice(object):
|
|||||||
raise NotImplementedError("Not implemented in base class")
|
raise NotImplementedError("Not implemented in base class")
|
||||||
|
|
||||||
def total(self):
|
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
|
- end
|
||||||
- amount
|
- amount
|
||||||
- cost
|
- 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:
|
main:
|
||||||
invoice:object: billing.csv_invoice:Csv
|
invoice:object: billing.csv_invoice:Csv
|
||||||
openstack:
|
openstack:
|
||||||
authentication_url: http://localhost:35357/v2.0
|
authentication_url: http://localhost:35357/v2.0
|
||||||
default_tenant: demo
|
default_tenant: demo
|
||||||
username: admin
|
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