diff --git a/.gitignore b/.gitignore index fc263c9..4c0d48c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,33 @@ work *.swp *~ *.swo +*.sw* +lib/ +man/ +build/ +work/ +env/ +dist/ +include/ +*.egg*/ +bin/activate +bin/activate.csh +bin/activate.fish +bin/activate_this.py +bin/ceilometer +bin/easy_install +bin/easy_install-2.7 +bin/keystone +bin/netaddr +bin/nosetests +bin/nosetests-2.7 +bin/pip +bin/pip-2.7 +bin/pybabel +bin/python +bin/python2 +bin/python2.7 +bin/waitress-serve +local/ +test_vm/ +env/ diff --git a/Makefile b/Makefile index 15103f4..f740285 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,9 @@ INSTALL_PATH=/opt/stack/artifice BILLING_PROGRAM=bill.py BINARY_PATH=/usr/local/bin -CONF_DIR=./work/${INSTALL_PATH}/etc/artifice +WORK_DIR=./work + +CONF_DIR=${WORK_DIR}/${INSTALL_PATH}/etc/artifice clean: @rm -rf ./work @@ -18,25 +20,35 @@ init: deb: clean init - @cp -r ./bin ./artifice ./scripts ./README.md ./INVOICES.md \ - requirements.txt setup.py ./work/${INSTALL_PATH} + @cp -r ./artifice \ + ./scripts \ + ./README.md \ + ./INVOICES.md \ + requirements.txt \ + setup.py \ + ${WORK_DIR}${INSTALL_PATH} + @mkdir ${WORK_DIR}${INSTALL_PATH}/bin + @cp ./bin/collect ./bin/collect.py \ + ./bin/usage ./bin/usage.py \ + ./bin/web ./bin/web.py \ + ${WORK_DIR}${INSTALL_PATH}/bin + @chmod 0755 ${WORK_DIR}${INSTALL_PATH}/bin/web + @cp -r ./packaging/fs/* ${WORK_DIR}/ @mkdir -p ${CONF_DIR} - @cp ./examples/conf.yaml ${CONF_DIR} - @cp ./examples/csv_rates.yaml ${CONF_DIR} + @mkdir -p ${WORK_DIR}/etc/init.d + @mkdir -p ${WORK_DIR}/etc/artifice + @chmod +x ${WORK_DIR}/etc/init.d/artifice + @cp ./examples/conf.yaml ${WORK_DIR}/etc/artifice/conf.yaml + @cp ./examples/csv_rates.yaml ${WORK_DIR}/etc/artifice/csv_rates.yaml @fpm -s dir -t deb -n ${NAME} -v ${VERSION} \ - --pre-install=packaging/scripts/pre_install.sh \ --post-install=packaging/scripts/post_install.sh \ - --depends 'postgresql >= 9.3' \ - --depends 'postgresql-contrib >= 9.3' \ --depends 'libpq-dev' \ - --deb-pre-depends pwgen \ + --deb-pre-depends "libmysql++-dev" \ --deb-pre-depends python2.7 \ --deb-pre-depends python-pip \ --deb-pre-depends python-dev \ + --deb-pre-depends python-virtualenv \ --template-scripts \ - --template-value pg_database=artifice \ - --template-value pg_user=artifice \ - --template-value pg_port=5432 \ --template-value install_path=${INSTALL_PATH} \ - -C ./work \ + -C ${WORK_DIR} \ . diff --git a/api/__init__.py b/artifice/api/__init__.py similarity index 100% rename from api/__init__.py rename to artifice/api/__init__.py diff --git a/api/artifice_api.py b/artifice/api/artifice_api.py similarity index 100% rename from api/artifice_api.py rename to artifice/api/artifice_api.py diff --git a/api/helpers.py b/artifice/api/helpers.py similarity index 100% rename from api/helpers.py rename to artifice/api/helpers.py diff --git a/api/keystone_api.py b/artifice/api/keystone_api.py similarity index 100% rename from api/keystone_api.py rename to artifice/api/keystone_api.py diff --git a/api/requirements.txt b/artifice/api/requirements.txt similarity index 100% rename from api/requirements.txt rename to artifice/api/requirements.txt diff --git a/api/tests/test_keystone.py b/artifice/api/tests/test_keystone.py similarity index 100% rename from api/tests/test_keystone.py rename to artifice/api/tests/test_keystone.py diff --git a/api/web.py b/artifice/api/web.py similarity index 99% rename from api/web.py rename to artifice/api/web.py index 3703f10..ec102da 100644 --- a/api/web.py +++ b/artifice/api/web.py @@ -48,6 +48,7 @@ def get_app(conf): global invoicer module, kls = config["main"]["export_provider"].split(":") + # TODO: Try/except block invoicer = getattr(importlib.import_module(module), kls) if config["main"].get("timezone"): diff --git a/artifice/initdb.py b/artifice/initdb.py new file mode 100644 index 0000000..0b32c4b --- /dev/null +++ b/artifice/initdb.py @@ -0,0 +1,31 @@ +from models import Base, __VERSION__ +from sqlalchemy import create_engine +from sqlalchemy.pool import NullPool + + +def provision(engine): + + Base.metadata.create_all(bind=engine) + +if __name__ == '__main__': + import argparse + a = argparse.ArgumentParser() + a.add_argument("--host", "--host") + a.add_argument("-p", "--port") + a.add_argument("-u", "--user") + a.add_argument("-d", "--database") + a.add_argument("-P", "--provider") + a.add_argument("-w", "--password") + + args = a.parse_args() + conn_string = "{provider}://{user}:{password}@{host}/{database}".format( + host=args.host, + port=args.port, + provider=args.provider, + user=args.user, + password=args.password, + database=args.database) + + engine = create_engine(conn_string, poolclass=NullPool) + provision(engine) + diff --git a/artifice/interface.py b/artifice/interface.py index bb1bc44..618ed8b 100644 --- a/artifice/interface.py +++ b/artifice/interface.py @@ -2,7 +2,7 @@ import requests import json import auth from ceilometerclient.v2.client import Client as ceilometer -from .models import resources +from artifice.models import resources from constants import date_format diff --git a/artifice/models/__init__.py b/artifice/models/__init__.py index b041112..5c4a448 100644 --- a/artifice/models/__init__.py +++ b/artifice/models/__init__.py @@ -1,5 +1,5 @@ from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import Column, Text, DateTime, DECIMAL, ForeignKey, String +from sqlalchemy import Column, Text, DateTime, Numeric, ForeignKey, String from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method from sqlalchemy import event, DDL @@ -7,10 +7,21 @@ from sqlalchemy import event, DDL from sqlalchemy.orm import relationship from sqlalchemy.schema import ForeignKeyConstraint +# Version digit. +__VERSION__ = 1.0 + Base = declarative_base() +class _Version(Base): + """ + A model that knows what version we are, stored in the DB. + """ + __tablename__ = "artifice_database_version" + id = Column(String(10), primary_key=True) + + class Resource(Base): """Database model for storing metadata associated with a resource.""" __tablename__ = 'resources' @@ -29,7 +40,7 @@ class UsageEntry(Base): # Service is things like incoming vs. outgoing, as well as instance # flavour service = Column(String(100), primary_key=True) - volume = Column(DECIMAL, nullable=False) + volume = Column(Numeric(precision=20, scale=2), nullable=False) resource_id = Column(String(100), primary_key=True) tenant_id = Column(String(100), primary_key=True) start = Column(DateTime, nullable=False) @@ -266,3 +277,14 @@ event.listen( DDL("DROP FUNCTION %s_exclusion_constraint_trigger()" % SalesOrder.__tablename__).execute_if(dialect="postgresql") ) + + +def insert_into_version(target, connection, **kw): + connection.execute("INSERT INTO %s (id) VALUES (%s)" % + (target.name, __VERSION__)) + +event.listen( + _Version.__table__, + "after_create", + insert_into_version +) diff --git a/artifice/artifice.py b/artifice/why_is_this_called_artifice.py similarity index 93% rename from artifice/artifice.py rename to artifice/why_is_this_called_artifice.py index f937fd2..d992a90 100644 --- a/artifice/artifice.py +++ b/artifice/why_is_this_called_artifice.py @@ -1,5 +1,5 @@ import yaml -from models import Session +from artifice.models import Session from interface import Artifice default_config = "/etc/artifice/config.yaml" @@ -23,4 +23,4 @@ def connect(config=None): # session.configure(bind=engine) artifice = Artifice(config) # artifice.artifice = session - return artifice \ No newline at end of file + return artifice diff --git a/bin/artifice b/bin/artifice deleted file mode 120000 index 3156fcc..0000000 --- a/bin/artifice +++ /dev/null @@ -1 +0,0 @@ -bill.py \ No newline at end of file diff --git a/bin/web b/bin/web new file mode 100755 index 0000000..6c9a3bb --- /dev/null +++ b/bin/web @@ -0,0 +1,13 @@ +#!/bin/bash +INSTALLED="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ORIGIN=`pwd` + +# Bring up our python environment +# Pass through all our command line opts as expected + +# Move ourselves to the code directory +# TODO: Fix this by removing relative imports from Artifice +cd $INSTALLED +cd ../ + +$INSTALLED/../env/bin/python $INSTALLED/web.py $@ diff --git a/bin/web.py b/bin/web.py new file mode 100644 index 0000000..947ecbf --- /dev/null +++ b/bin/web.py @@ -0,0 +1,25 @@ +#!/usr/bin/python +from artifice.api import web +import yaml +import sys + +import argparse +a = argparse.ArgumentParser("Web service for Artifice") + +a.add_argument("-c", "--config", dest="config", help="Path to config file", default="/etc/artifice/conf.yaml") +a.add_argument("-i", "--interface", dest="ip", help="IP address to serve on.", default="0.0.0.0") +a.add_argument("-p", "--port", help="port to serve on", default="8000") + +args = a.parse_args() + +conf = None + +try: + conf = yaml.load(args.config) +except IOError as e: + print "Couldn't load config file: %s" % e + sys.exit(1) + + +app = web.get_app(conf) +app.run(host=args.ip, port=args.port) diff --git a/examples/conf.yaml b/examples/conf.yaml index 9dfa763..cbd8952 100644 --- a/examples/conf.yaml +++ b/examples/conf.yaml @@ -1,12 +1,6 @@ --- ceilometer: host: http://localhost:8777/ -database: - database: artifice - host: localhost - password_path: /etc/artifice/database - port: '5432' - username: artifice invoice_object: delimiter: ',' output_file: '%(tenant)s-%(start)s-%(end)s.csv' @@ -14,7 +8,8 @@ invoice_object: rates: file: /etc/artifice/csv_rates.csv main: - invoice:object: billing.csv_invoice:Csv + export_provider: billing.csv_invoice:Csv + database_uri: postgres://artifice:123456@localhost:5432/artifice openstack: authentication_url: http://localhost:35357/v2.0 default_tenant: demo diff --git a/is_provisioned.py b/is_provisioned.py new file mode 100644 index 0000000..a9be38f --- /dev/null +++ b/is_provisioned.py @@ -0,0 +1,15 @@ +from artifice.models import Base, __VERSION__, _Version +from sqlalchemy.orm import scoped_session, create_session +from sqlalchemy import create_engine +from sqlalchemy.pool import NullPool +import sys, os + +uri = os.environ["DATABASE_URI"] +engine = create_engine(uri, poolclass=NullPool) +session = create_session(bind=engine) + +v = session.query(_Version).first() +if v is None: + sys.exit(0) + +sys.exit(1) diff --git a/packaging/fs/etc/init.d/artifice b/packaging/fs/etc/init.d/artifice new file mode 100644 index 0000000..cb8a196 --- /dev/null +++ b/packaging/fs/etc/init.d/artifice @@ -0,0 +1,63 @@ +#!/bin/sh +set -e + +### BEGIN INIT INFO +# Provides: artifice +# Required-Start: $local_fs $remote_fs $network $time +# Required-Stop: $local_fs $remote_fs $network $time +# Should-Start: $syslog +# Should-Stop: $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Artifice-Openstack billing server +### END INIT INFO + +ARTIFICE_PATH=/opt/stack/artifice +DAEMON="$ARTIFICE_PATH/bin/web" +NAME=artifice +PIDFILE="/var/run/artifice/${NAME}.pid" + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +# versions can be specified explicitly +case "$1" in + start) + start-stop-daemon --start --quiet --pidfile $PIDFILE\ + --startas $DAEMON + ;; + stop) + start-stop-daemon --stop --quiet --pidfile $PIDFILE\ + --oknodo + ;; + + restart) + + start-stop-daemon --stop --quiet --pidfile $PIDFILE --retry TERM/10/KILL/5 --quiet --oknodo + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --startas $DAEMON -- $NAME + ;; + + status) + if [ -f $PIDFILE ]; then + + PID=`cat $PIDFILE` + RUNNING=`ps aux | awk '{print $2}' | grep $PIDFILE` + + if [ $RUNNING = $PID ]; then + log_success_msg "$NAME is running" + else + log_failure_msg "$NAME is not running" + fi + else + log_failure_msg "$PIDFILE not found." + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/post_install.sh b/packaging/scripts/post_install.sh index d93248b..f8cc477 100644 --- a/packaging/scripts/post_install.sh +++ b/packaging/scripts/post_install.sh @@ -1,27 +1,32 @@ #!/bin/sh -PASSWORD=`cat <%= install_path %>/etc/artifice/database` +# PASSWORD=`cat <%= install_path %>/etc/artifice/database` +# pip install virtualenv -export DATABASE_URL="postgresql://<%= pg_user %>:$PASSWORD@localhost:<%=pg_port%>/<%=pg_database%>" - -pip install virtualenv +mkdir /var/run/artifice # Set up a virtualenv for ourselves in this directory virtualenv <%= install_path %>/env +# First, install an up-to-date pip into the virtualenv, since this is ridiculously ancient + +<%=install_path%>/env/bin/pip install --upgrade pip + +# Now iterate our requirements # this should now be limited to only this space <%=install_path%>/env/bin/pip install -r <%= install_path %>/requirements.txt -<%=install_path%>/env/bin/python <%= install_path %>/scripts/initdb.py - # And this. Woo. -<%=install_path%>/env/bin/python <%= install_path%>/setup.py install # register with python! -# Set up the /usr/local/artifice-bill script +cd <%=install_path%> +sudo ./env/bin/python ./setup.py install # register with python! + + +# Set up the artifice control scripts cat > /usr/local/bin/artifice-bill </env/bin/python <%=install_path%>/bin/bill.py \$@ +<%=install_path%>/env/bin/python <%=install_path%>/bin/collect.py \$@ EOF @@ -33,7 +38,7 @@ EOF chmod 0755 /usr/local/bin/artifice-usage chmod 0755 /usr/local/bin/artifice-bill -cp <%=install_path%>/etc/artifice/conf.yaml /etc/artifice/conf.yaml -cp <%=install_path%>/etc/artifice/database /etc/artifice/database - chown 0644 /etc/artifice/database +# cp <%=install_path%>/etc/artifice/conf.yaml /etc/artifice/conf.yaml +# cp <%=install_path%>/etc/artifice/database /etc/artifice/database + # chown 0644 /etc/artifice/database diff --git a/puppet/Modulefile b/puppet/Modulefile new file mode 100644 index 0000000..4b2c714 --- /dev/null +++ b/puppet/Modulefile @@ -0,0 +1,5 @@ +name 'catalyst-artifice' +version '0.0.1' +dependency 'reidmv/yamlfile', '0.2.0' +description "This module handles the installation and configuration of an + Openstack-Billing server, associated plugins, and storage backend (currently mysql and postgres.). Depends upon yamlfile to handle construction of a configuration file. diff --git a/puppet/manifests/config.pp b/puppet/manifests/config.pp new file mode 100644 index 0000000..f23764e --- /dev/null +++ b/puppet/manifests/config.pp @@ -0,0 +1,68 @@ +class artifice::config ( + $keystone_uri, + $keystone_tenant, + $keystone_password, + $keystone_username, + $database_uri, + $ceilometer_uri, + $region +) { + # target => '/tmp/example1.yaml', + # key => 'value/subkey/final', + # value => ['one', 'two', 'three'], + # + + $artifice_config_file = "/etc/artifice/conf.yaml" + + # OPENSTACK SETTINGS + # + yaml_setting {"artifice.config.ceilometer.uri": + target => $artifice_config_file, + key => "ceilometer/host", + value => $ceilometer_uri + } + + yaml_setting {"artifice.config.keystone.uri": + target => $artifice_config_file, + key => "openstack/authentication_url", + value => $keystone_uri + } + yaml_setting {"artifice.config.keystone.username": + target => $artifice_config_file, + key => "openstack/username", + value => $keystone_user + } + yaml_setting {"artifice.config.keystone.tenant": + target => $artifice_config_file, + key => "openstack/default_tenant", + value => $keystone_tenant + } + yaml_setting {"artifice.config.keystone.password": + target => $artifice_config_file, + key => "openstack/password", + value => $keystone_password + } + + # DATABASE SETTINGS + + yaml_setting {"artifice.config.database.uri": + target => $artifice_config_file, + key => "database/uri", + value => $database_uri + } + + # Config settings for plugins are stored in the plugins directory + + # file {"/etc/artifice/conf.yaml": + # ensure => present, + # content => template("artifice/etc/artifice/conf.yaml") + # } + + # Region + # + yaml_setting {"artifice.config.region": + target => $artifice_config_file, + key => "region", + value => $region + } +} diff --git a/puppet/manifests/database.pp b/puppet/manifests/database.pp new file mode 100644 index 0000000..e5490b8 --- /dev/null +++ b/puppet/manifests/database.pp @@ -0,0 +1,27 @@ +class artifice::database ( + $provider, + $host, + $port, + $user, + $password, + $database_name +) { + # I think the install path should + # + if $provider != "postgres" and $provider != "mysql" { + fail("Provider must be postgres or mysql") + } + $install_path = "/opt/stack/artifice" + + # Create is handled by the Galera setup earlier. + # exec {"create.database": + # command => $create_command, + # cwd => $pwd, + # onlyif => $unless_command + # } + exec {"sqlalchemy.create": + command => "/usr/bin/python $install_path/initdb.py", + environment => "DATABASE_URI=$provider://$user:$password@$host/$database_name", + onlyif => "/usr/bin/python $install_path/is_provisioned.py", + } +} diff --git a/puppet/manifests/init.pp b/puppet/manifests/init.pp new file mode 100644 index 0000000..f2daa1a --- /dev/null +++ b/puppet/manifests/init.pp @@ -0,0 +1,61 @@ +class artifice ( + $keystone_password, + $region, + $version, + $database_password, + $database_provider = "postgres", + $database_host = "localhost", + $database_port = 5432, + $database_name = "artifice", + $database_user = "artifice", + $csv_output_directory = '/var/lib/artifice/csv', + $ceilometer_uri = "http://localhost:8777", + $keystone_uri = "http://localhost:35357/v2.0", + $keystone_tenant = "demo", + $keystone_username = "admin" +) { + + + # Materialises the class + # I think.. this is better served as part of the package install + $config_file = "/etc/artifice/conf.yaml" + $install_path = "/opt/stack/artifice" + + class {"artifice::server": + # region => $region, + version => $version, + # require => Class["artifice::dependencies"] + } + + $database_uri = "$database_provider://${database_user}:${database_password}@${database_host}:${database_port}/${database_name}" + + class {"artifice::config": + keystone_uri => $keystone_uri, + keystone_tenant => $keystone_tenant, + keystone_username => $keystone_username, + keystone_password => $keystone_password, + database_uri => $database_uri, + ceilometer_uri => $ceilometer_uri, + require => Class["artifice::server"], + notify => Service["artifice"], + region => $region + } + + class {"artifice::database": + provider => $database_provider, + host => $database_host, + port => $database_port, + user => $database_user, + password => $database_password, + database_name => $database_name, + require => Class["artifice::server"] + } + + service {"artifice": + ensure => running, + require => [ + Class["artifice::server"], + Class["artifice::config"] + ] + } +} diff --git a/puppet/manifests/plugins/csv.pp b/puppet/manifests/plugins/csv.pp new file mode 100644 index 0000000..cd41bae --- /dev/null +++ b/puppet/manifests/plugins/csv.pp @@ -0,0 +1,29 @@ +class artifice::plugins::csv ( + $delimiter, + $output_path, + $output_pattern +) { + + # This should cause a runtime error if another plugin is loaded + yaml_setting {"artifice.billing.plugin": + target => $artifice::config_file, + key => "main/invoice:object", + value => "billing.csv_plugin:Csv" + } + yaml_setting {"artifice.csv.config.delimiter": + target => $artifice::config_file, + key => "invoice_object/delimiter", + value => $delimiter + } + yaml_setting {"artifice.csv.config.output_path": + target => $artifice::config_file, + key => "invoice_object/output_path", + value => $output_path + } + yaml_setting {"artifice.csv.config.output_file": + target => $artifice::config_file, + key => "invoice_object/output_file", + value => $output_pattern + } + # Rates information is pulled from the rates-file plugin +} diff --git a/puppet/manifests/plugins/csv/rates_file.pp b/puppet/manifests/plugins/csv/rates_file.pp new file mode 100644 index 0000000..86870b5 --- /dev/null +++ b/puppet/manifests/plugins/csv/rates_file.pp @@ -0,0 +1,11 @@ +class artifice::plugins::csv::rates_file ( + $path +) { + # Sets the path to the rates information, if any + yaml_setting {"artifice.plugins.rates": + target => $artifice::config_file, + key => "invoice_object/rates/file", + value => $path, + require => File[$path] + } +} diff --git a/puppet/manifests/plugins/csv/rates_server.pp b/puppet/manifests/plugins/csv/rates_server.pp new file mode 100644 index 0000000..e69de29 diff --git a/puppet/manifests/server.pp b/puppet/manifests/server.pp new file mode 100644 index 0000000..bbd2647 --- /dev/null +++ b/puppet/manifests/server.pp @@ -0,0 +1,27 @@ +class artifice::server( + $version +) { + # $path_to_package = $::package_path + "/artifice" + $version + ".deb" + + # package {"python2.7": + # ensure => present + # } + + package {"artifice": + name => "openstack-artifice", + ensure => present, + require => Package["python2.7"] + } + + package {"libpq-dev": + ensure => present + } + package {"python2.7": ensure => present} + package {"python-pip": ensure => present} + package {"python-dev": ensure => present} + package {"python-virtualenv": ensure => present} + + Package["python-virtualenv"] -> Package["artifice"] + # We don't try to ensure running here. + # +} diff --git a/puppet_generate.py b/puppet_generate.py new file mode 100644 index 0000000..52af78d --- /dev/null +++ b/puppet_generate.py @@ -0,0 +1,52 @@ +import sys +# pip install requirements-parser +import requirements + +class Requirements(object): + + def __init__(self): + self.reqs = [] + + def parse(self, stream): + self.reqs = requirements.parse(stream) + + def package_list(self): + final = """""" + for req in self.reqs: + final += """ +package {"%(package)s": + ensure => "%(version)s", + provider => pip +} +""" % {"package": req.name, "version": req.specs[0][1] } + return final + + def requirement_list(self): + return ",\n".join( [ """Package[%(package)s]""" % + {"package": req.name } for req in self.reqs ] ) + + +if __name__ == '__main__': + import argparse + a = argparse.ArgumentParser() + a.add_argument("-f", dest="filename") + a.add_argument("-l", dest="list_", action="store_true") + + args = a.parse_args() + + if args.filename == "-": + # We're following standardized posix thing + fh = sys.stdin + else: + try: + fh = open(args.filename) + except IOError as e: + print "Couldn't open %s" % args.filename + sys.exit(1) + + r = Requirements() + r.parse(fh) + if args.list_: + print r.requirement_list() + sys.exit(0) + print r.package_list() diff --git a/requirements.txt b/requirements.txt index 2120cd9..5e0e3f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,37 @@ -sqlalchemy>=0.8 -psycopg2>=2.5.1 -requests==1.1.0 -pyaml==13.07 -python-ceilometerclient==1.0.3 python-novaclient>=2.17 -python-keystoneclient==0.3.2 -urllib3==1.5 +Babel==1.3 Flask==0.10.1 +Jinja2==2.7.2 +MarkupSafe==0.18 +MySQL-python==1.2.5 +PyMySQL==0.6.1 +PyYAML==3.10 +SQLAlchemy==0.8.0 +WebOb==1.3.1 WebTest==2.0.14 +Werkzeug==0.9.4 +argparse==1.2.1 +beautifulsoup4==4.3.2 decorator==3.4.0 +httplib2==0.8 +iso8601==0.1.8 +itsdangerous==0.23 mock==1.0.1 +netaddr==0.7.10 +nose==1.3.0 +oslo.config==1.2.1 +pbr==0.6 +prettytable==0.7.2 +psycopg2==2.5.2 +pyaml==13.07.0 +python-ceilometerclient==1.0.3 +python-keystoneclient==0.3.2 +python-novaclient==2.17.0 pytz==2013.9 +requests==1.1.0 +requirements-parser==0.0.6 +simplejson==3.3.3 +six==1.5.2 +urllib3==1.5 +waitress==0.8.8 +wsgiref==0.1.2 diff --git a/setup.py b/setup.py index e69de29..bee164a 100644 --- a/setup.py +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +setup(name='openstack-artifice', + version='0.1', + description='Artifice, a set of APIs for creating billable items from Openstack-Ceilometer', + author='Aurynn Shaw', + author_email='aurynn@catalyst.net.nz', + contributors=["Chris Forbes", "Adrian Turjak"], + contributor_emails=["chris.forbes@catalyst.net.nz", "adriant@catalyst.net.nz"], + url='https://github.com/catalyst/artifice', + packages=["artifice", "artifice.api", "artifice.models"] + ) diff --git a/tests/test_api.py b/tests/test_api.py index f593af0..3b065d9 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,6 +1,6 @@ from webtest import TestApp from . import test_interface, helpers, constants -from api.web import get_app +from artifice.api.web import get_app from artifice import models from artifice import interface from datetime import datetime diff --git a/tests/test_models.py b/tests/test_models.py index dedd5f1..70afb33 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -68,7 +68,7 @@ class db(unittest.TestCase): t = self.db.query(Tenant).get("asfd") r = self.db.query(Resource).filter(Resource.id == "1234")[0] u = UsageEntry(service="cheese", - volume=1.234, + volume=1.23, resource=r, tenant=r, start=datetime.datetime.now() - datetime.timedelta(minutes=5),