
system to create a working .deb, based on the makefile. Adds a new script to start up the web daemon. Adds a new script to test if the database is provisioned Adds a new script used by Puppet to provision the database Adds puppet manifests (mirrored in main puppet) Moves api/ to artifice/api Alters some of the relative imports Moves artifice.py to why_is_this_called_artifice.py, as it was causing import issues. Change-Id: Id8a909f7ffcc64a5c4e3281c6b5ba83cef73b596
317 lines
9.1 KiB
Python
317 lines
9.1 KiB
Python
import requests
|
|
import json
|
|
import auth
|
|
from ceilometerclient.v2.client import Client as ceilometer
|
|
from artifice.models import resources
|
|
from constants import date_format
|
|
|
|
|
|
def add_dates(start, end):
|
|
return [
|
|
{
|
|
"field": "timestamp",
|
|
"op": "ge",
|
|
"value": start.strftime(date_format)
|
|
},
|
|
{
|
|
"field": "timestamp",
|
|
"op": "lt",
|
|
"value": end.strftime(date_format)
|
|
}
|
|
]
|
|
|
|
|
|
class Artifice(object):
|
|
"""Produces billable artifacts"""
|
|
def __init__(self, config):
|
|
super(Artifice, self).__init__()
|
|
self.config = config
|
|
|
|
# This is the Keystone client connection, which provides our
|
|
# OpenStack authentication
|
|
self.auth = auth.Keystone(
|
|
username=config["openstack"]["username"],
|
|
password=config["openstack"]["password"],
|
|
tenant_name=config["openstack"]["default_tenant"],
|
|
auth_url=config["openstack"]["authentication_url"]
|
|
)
|
|
|
|
self.ceilometer = ceilometer(
|
|
self.config["ceilometer"]["host"],
|
|
# Uses a lambda as ceilometer apparently wants
|
|
# to use it as a callable?
|
|
token=lambda: self.auth.auth_token
|
|
)
|
|
self._tenancy = None
|
|
|
|
def tenant(self, id_):
|
|
"""
|
|
Returns a Tenant object describing the specified Tenant by
|
|
name, or raises a NotFound error.
|
|
"""
|
|
|
|
data = self.auth.tenants.get(id_)
|
|
t = Tenant(data, self)
|
|
return t
|
|
|
|
@property
|
|
def tenants(self):
|
|
"""All the tenants in our system"""
|
|
if not self._tenancy:
|
|
self._tenancy = []
|
|
for tenant in self.auth.tenants.list():
|
|
t = Tenant(tenant, self)
|
|
self._tenancy.append(t)
|
|
return self._tenancy
|
|
|
|
|
|
class Tenant(object):
|
|
|
|
def __init__(self, tenant, conn):
|
|
self.tenant = tenant
|
|
# Conn is the niceometer object we were instanced from
|
|
self.conn = conn
|
|
self._meters = set()
|
|
self._resources = None
|
|
|
|
def __getitem__(self, item):
|
|
|
|
try:
|
|
return getattr(self.tenant, item)
|
|
except AttributeError:
|
|
try:
|
|
return self.tenant[item]
|
|
except KeyError:
|
|
raise KeyError("No such key '%s' in tenant" % item)
|
|
|
|
def __getattr__(self, attr):
|
|
if attr not in self.tenant:
|
|
return object.__getattribute__(self, attr)
|
|
return self.tenant[attr]
|
|
|
|
def resources(self, start, end):
|
|
if not self._resources:
|
|
|
|
date_fields = [
|
|
{"field": "project_id",
|
|
"op": "eq",
|
|
"value": self.tenant["id"]
|
|
},
|
|
]
|
|
date_fields.extend(add_dates(start, end))
|
|
# Sets up our resources as Ceilometer objects.
|
|
# That's cool, I think.
|
|
self._resources = self.conn.ceilometer.resources.list(date_fields)
|
|
return self._resources
|
|
|
|
def usage(self, start, end):
|
|
"""
|
|
Usage is the meat of Artifice, returning a dict of location to
|
|
sub-information
|
|
"""
|
|
vms = []
|
|
networks = []
|
|
ips = []
|
|
storage = []
|
|
volumes = []
|
|
|
|
for resource in self.resources(start, end):
|
|
rels = [link["rel"] for link in resource.links if link["rel"] != 'self']
|
|
if "storage.objects" in rels:
|
|
storage.append(Resource(resource, self.conn))
|
|
pass
|
|
elif "network.incoming.bytes" in rels:
|
|
networks.append(Resource(resource, self.conn))
|
|
elif "volume" in rels:
|
|
volumes.append(Resource(resource, self.conn))
|
|
elif 'instance' in rels:
|
|
vms.append(Resource(resource, self.conn))
|
|
elif 'ip.floating' in rels:
|
|
ips.append(Resource(resource, self.conn))
|
|
|
|
region_tmpl = {
|
|
"vms": vms,
|
|
"networks": networks,
|
|
"objects": storage,
|
|
"volumes": volumes,
|
|
"ips": ips
|
|
}
|
|
|
|
return Usage(region_tmpl, start, end, self.conn)
|
|
|
|
|
|
class Usage(object):
|
|
"""
|
|
This is a dict-like object containing all the datacenters and
|
|
meters available in those datacenters.
|
|
"""
|
|
|
|
def __init__(self, contents, start, end, conn):
|
|
self.contents = contents
|
|
self.start = start
|
|
self.end = end
|
|
self.conn = conn
|
|
|
|
self._vms = []
|
|
self._objects = []
|
|
self._volumes = []
|
|
self._networks = []
|
|
self._ips = []
|
|
|
|
def values(self):
|
|
return (self.vms + self.objects + self.volumes +
|
|
self.networks + self.ips)
|
|
|
|
@property
|
|
def vms(self):
|
|
if not self._vms:
|
|
vms = []
|
|
for vm in self.contents["vms"]:
|
|
VM = resources.VM(vm, self.start, self.end)
|
|
vms.append(VM)
|
|
self._vms = vms
|
|
return self._vms
|
|
|
|
@property
|
|
def objects(self):
|
|
if not self._objects:
|
|
objs = []
|
|
for object_ in self.contents["objects"]:
|
|
obj = resources.Object(object_, self.start, self.end)
|
|
objs.append(obj)
|
|
self._objs = objs
|
|
return self._objs
|
|
|
|
@property
|
|
def networks(self):
|
|
if not self._networks:
|
|
networks = []
|
|
for obj in self.contents["networks"]:
|
|
obj = resources.Network(obj, self.start, self.end)
|
|
networks.append(obj)
|
|
self._networks = networks
|
|
return self._networks
|
|
|
|
@property
|
|
def ips(self):
|
|
if not self._ips:
|
|
ips = []
|
|
for obj in self.contents["ips"]:
|
|
obj = resources.FloatingIP(obj, self.start, self.end)
|
|
ips.append(obj)
|
|
self._ips = ips
|
|
return self._ips
|
|
|
|
@property
|
|
def volumes(self):
|
|
if not self._volumes:
|
|
objs = []
|
|
for obj in self.contents["volumes"]:
|
|
obj = resources.Volume(obj, self.start, self.end)
|
|
objs.append(obj)
|
|
self._volumes = objs
|
|
return self._volumes
|
|
# def __getitem__(self, item):
|
|
|
|
# return self.contents[item]
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def next(self):
|
|
# pass
|
|
keys = self.contents.keys()
|
|
for key in keys:
|
|
yield key
|
|
raise StopIteration()
|
|
|
|
|
|
class Resource(object):
|
|
|
|
def __init__(self, resource, conn):
|
|
self.resource = resource
|
|
self.conn = conn
|
|
self._meters = {}
|
|
|
|
# def __getitem__(self, item):
|
|
# return self.resource
|
|
|
|
def meter(self, name, start, end):
|
|
pass # Return a named meter
|
|
for meter in self.resource.links:
|
|
if meter["rel"] == name:
|
|
m = Meter(self, meter["href"], self.conn, start, end)
|
|
self._meters[name] = m
|
|
return m
|
|
raise AttributeError("no such meter %s" % name)
|
|
|
|
def __getitem__(self, name):
|
|
# print name
|
|
# print self.resource
|
|
# print self.resource[name]
|
|
return getattr(self.resource, name)
|
|
# return self.resource.name
|
|
|
|
@property
|
|
def meters(self):
|
|
if not self._meters:
|
|
meters = []
|
|
for link in self.resource["links"]:
|
|
if link["rel"] == "self":
|
|
continue
|
|
meter = Meter(self, link, self.conn)
|
|
meters.append(meter)
|
|
self._meters = meters
|
|
return self._meters
|
|
|
|
|
|
class Meter(object):
|
|
|
|
def __init__(self, resource, link, conn, start=None, end=None):
|
|
self.resource = resource
|
|
self.link = link
|
|
self.conn = conn
|
|
self.start = start
|
|
self.end = end
|
|
|
|
self.measurements = self.get_meter(start, end,
|
|
self.conn.auth.auth_token)
|
|
|
|
self.type = set([a["counter_type"] for a in self.measurements])
|
|
if len(self.type) > 1:
|
|
# That's a big problem
|
|
raise RuntimeError("Too many types for measurement!")
|
|
elif len(self.type) == 0:
|
|
raise RuntimeError("No types!")
|
|
else:
|
|
self.type = self.type.pop()
|
|
|
|
self.name = set([a["counter_name"] for a in self.measurements])
|
|
if len(self.name) > 1:
|
|
# That's a big problem
|
|
raise RuntimeError("Too many names for measurement!")
|
|
elif len(self.name) == 0:
|
|
raise RuntimeError("No types!")
|
|
else:
|
|
self.name = self.name.pop()
|
|
|
|
def get_meter(self, start, end, auth):
|
|
# Meter is a href; in this case, it has a set of fields with it already.
|
|
date_fields = add_dates(start, end)
|
|
fields = []
|
|
for field in date_fields:
|
|
fields.append(("q.field", field["field"]))
|
|
fields.append(("q.op", field["op"]))
|
|
fields.append(("q.value", field["value"]))
|
|
|
|
r = requests.get(
|
|
self.link,
|
|
headers={
|
|
"X-Auth-Token": auth,
|
|
"Content-Type": "application/json"}
|
|
)
|
|
return json.loads(r.text)
|
|
|
|
def usage(self):
|
|
return self.measurements
|