diff --git a/distil/api/v2.py b/distil/api/v2.py index dd48576..4e37ea5 100644 --- a/distil/api/v2.py +++ b/distil/api/v2.py @@ -32,4 +32,14 @@ def prices_get(): @rest.get('/costs') def costs_get(): - return api.render(costs=costs.get_costs()) \ No newline at end of file + return api.render(costs=costs.get_costs()) + + +@rest.get('/usages') +def usages_get(): + return api.render(usages={}) + + +@rest.get('/health') +def health_get(): + return api.render(health={}) diff --git a/distil/db/migration/alembic_migrations/env.py b/distil/db/migration/alembic_migrations/env.py index e5a8a42..c37e0df 100644 --- a/distil/db/migration/alembic_migrations/env.py +++ b/distil/db/migration/alembic_migrations/env.py @@ -23,7 +23,7 @@ from sqlalchemy import create_engine from sqlalchemy import pool from distil.db.sqlalchemy import model_base -from distil.openstack.common import importutils +from oslo_utils import importutils importutils.import_module('distil.db.sqlalchemy.models') diff --git a/distil/db/migration/alembic_migrations/versions/001_juno.py b/distil/db/migration/alembic_migrations/versions/001_init_tables.py similarity index 93% rename from distil/db/migration/alembic_migrations/versions/001_juno.py rename to distil/db/migration/alembic_migrations/versions/001_init_tables.py index e537c3e..5dfe0d3 100644 --- a/distil/db/migration/alembic_migrations/versions/001_juno.py +++ b/distil/db/migration/alembic_migrations/versions/001_init_tables.py @@ -100,16 +100,9 @@ def upgrade(): # mysql_engine=MYSQL_ENGINE, # mysql_charset=MYSQL_CHARSET) - op.create_table('last_run', - sa.Column('id', sa.Integer, primary_key=True, - sa.Sequence("last_run_id_seq")), - sa.Column('last_run', sa.DateTime(), nullable=True), - mysql_engine=MYSQL_ENGINE, - mysql_charset=MYSQL_CHARSET) def downgrade(): op.drop_table('project') op.drop_table('usage') op.drop_table('resource') - op.drop_table('last_run') diff --git a/distil/db/sqlalchemy/model_base.py b/distil/db/sqlalchemy/model_base.py index 7e7d321..6cf2e62 100644 --- a/distil/db/sqlalchemy/model_base.py +++ b/distil/db/sqlalchemy/model_base.py @@ -13,15 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from oslo.utils import timeutils -from oslo.db.sqlalchemy import models as oslo_models +from oslo_utils import timeutils +from oslo_db.sqlalchemy import models as oslo_models from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy.ext import declarative from sqlalchemy import Text from sqlalchemy.types import TypeDecorator -from distil.openstack.common import jsonutils +from oslo_serialization import jsonutils class JSONEncodedDict(TypeDecorator): diff --git a/distil/rater/odoo.py b/distil/rater/odoo.py index a9bbdfa..1538ec4 100644 --- a/distil/rater/odoo.py +++ b/distil/rater/odoo.py @@ -14,12 +14,24 @@ # limitations under the License. from distil import rater +from distil.rater import rate_file from distil.utils import odoo class OdooRater(rater.BaseRater): + def __init__(self, conf): + self.prices = odoo.Odoo().get_prices() + def rate(self, name, region=None): - erp = odoo.Odoo() - import pdb - pdb.set_trace() - pass \ No newline at end of file + if not self.prices: + return rate_file.FileRater().rate(name, region) + + for region in self.prices: + for category in self.prices[region]: + for product in self.prices[region][category]: + if product == name: + return {'rate': self.prices[name]['price'], + 'unit': self.prices[name]['unit'] + } + + return rate_file.FileRater().rate(name, region) diff --git a/distil/rater/file.py b/distil/rater/rate_file.py similarity index 100% rename from distil/rater/file.py rename to distil/rater/rate_file.py diff --git a/distil/service/api/v2/__init__.py b/distil/service/api/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/distil/service/api/v2/costs.py b/distil/service/api/v2/costs.py new file mode 100644 index 0000000..4a68df4 --- /dev/null +++ b/distil/service/api/v2/costs.py @@ -0,0 +1,23 @@ +# Copyright (c) 2016 Catalyst IT Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from oslo_config import cfg +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + +def get_costs(): + return {'id': 1} \ No newline at end of file diff --git a/distil/service/api/v2/prices.py b/distil/service/api/v2/prices.py index 7122803..2c876cc 100644 --- a/distil/service/api/v2/prices.py +++ b/distil/service/api/v2/prices.py @@ -21,9 +21,5 @@ LOG = logging.getLogger(__name__) CONF = cfg.CONF def get_prices(format=None): - import pdb - pdb.set_trace() - erp = odoo.Odoo() - erp.get_prices() - pdb.set_trace() - return {'id': 1} \ No newline at end of file + prices = odoo.Odoo().get_prices() + return prices \ No newline at end of file diff --git a/distil/utils/odoo.py b/distil/utils/odoo.py new file mode 100644 index 0000000..de0771a --- /dev/null +++ b/distil/utils/odoo.py @@ -0,0 +1,84 @@ +# Copyright (c) 2016 Catalyst IT Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import odoorpc + +from oslo_config import cfg +from oslo_log import log + +CONF = cfg.CONF + +PRODUCT_CATEGORY = ('Compute', 'Network', 'Block Storage', 'Object Storage') +REGION_MAPPING = {'nz-por-1': 'NZ-POR-1', 'nz_wlg_2': 'NZ-WLG-2'} + +class Odoo(object): + + def __init__(self): + self.odoo = odoorpc.ODOO(CONF.odoo.hostname, + protocol=CONF.odoo.protocol, + port=CONF.odoo.port, + version=CONF.odoo.version) + + self.odoo.login(CONF.odoo.database, CONF.odoo.user, CONF.odoo.password) + + self.order = self.odoo.env['sale.order'] + self.orderline = self.odoo.env['sale.order.line'] + self.tenant = self.odoo.env['cloud.tenant'] + self.partner = self.odoo.env['res.partner'] + self.pricelist = self.odoo.env['product.pricelist'] + self.product = self.odoo.env['product.product'] + self.category = self.odoo.env['product.category'] + + def get_prices(self, region=None): + # TODO(flwang): Need to cache the prices, now generally this method + # will take 30+ seconds to get the two regions prices. + if region: + regions = [REGION_MAPPING[region.lower()]] + else: + regions = REGION_MAPPING.values() + + prices = {} + for r in regions: + prices[r] = {} + for category in PRODUCT_CATEGORY: + prices[r][category.lower()] = {} + c = self.category.search([ + ('name', '=', category), + ('display_name', 'ilike', r) + ]) + product_ids = self.product.search([ + ('categ_id', '=', c[0]), + ('sale_ok', '=', True), + ('active', '=', True) + ]) + products = self.odoo.execute('product.product', + 'read', + product_ids) + for p in products: + name = p['name_template'][len(r)+1:] + price = round(p['lst_price'], 5) + # NOTE(flwang): default_code is Internal Reference on Odoo + # GUI + unit = p['default_code'] + desc = p['description'] + prices[r][category.lower()][name] = {'price': price, + 'unit': unit, + 'description': desc + } + + return prices + + def get_customers(self): + pass diff --git a/odoo/odoo-products-snapshot.py b/odoo/odoo-products-snapshot.py deleted file mode 100755 index 982ab28..0000000 --- a/odoo/odoo-products-snapshot.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python2 -import sys -import os -import pprint -import argparse -import math -import ConfigParser -from decimal import Decimal - -import odoorpc - -# requires distilclient>=0.5.1 -from distilclient.client import Client as DistilClient - - -parser = argparse.ArgumentParser() -parser.add_argument('--start', required=True, help='Start date') -parser.add_argument('--end', required=True, help='End date') - -args = parser.parse_args() - -conf = ConfigParser.ConfigParser() -conf.read(['glue.ini']) - -region = conf.get('openstack', 'region') - -oerp = odoorpc.ODOO( - conf.get('odoo', 'hostname'), - protocol=conf.get('odoo', 'protocol'), - port=conf.getint('odoo', 'port'), - version=conf.get('odoo', 'version') -) -oerp.login( - conf.get('odoo', 'database'), - conf.get('odoo', 'user'), - conf.get('odoo', 'password') -) - -# debug helper -def dump_all(model, fields, conds=None): - print '%s:' % model - ids = oerp.search(model, conds or []) - objs = oerp.read(model, ids) - for obj in objs: - print ' %s %s' % (obj['id'], {f:obj[f] for f in fields}) - -pricelist_model = oerp.env['product.pricelist'] -pricelist = oerp.search('product.pricelist', - [('name', '=', conf.get('odoo', 'export_pricelist'))]) - -product_category = oerp.search('product.category', - [('name', '=', conf.get('odoo', 'product_category'))]) - -product_ids = oerp.search('product.product', - [('categ_id', '=', product_category[0]), - ('sale_ok', '=', True), - ('active', '=', True)]) - -products = oerp.read('product.product', product_ids) - -prices = {} - -for p in products: - if not p['name_template'].startswith(region + '.'): - continue - base_name = p['name_template'][len(region)+1:] - # exported prices are for one unit -- do not take into account - # any bulk pricing rules. - unit_price = pricelist_model.price_get([pricelist[0]], p['id'], 1)[str(pricelist[0])] - prices[base_name] = unit_price - print '%s %s' % (base_name, unit_price) - -# create the snapshot in distil -dc = DistilClient( - os_username=os.getenv('OS_USERNAME'), - os_password=os.getenv('OS_PASSWORD'), - os_tenant_id=os.getenv('OS_TENANT_ID'), - os_auth_url=os.getenv('OS_AUTH_URL'), - os_region_name=os.getenv('OS_REGION_NAME')) - -dc.set_prices(args.start, args.end, prices) diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh new file mode 100644 index 0000000..fb3927a --- /dev/null +++ b/tools/config/check_uptodate.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +PROJECT_NAME=${PROJECT_NAME:-wellington} +CFGFILE_NAME=${PROJECT_NAME}.conf.sample + +if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then + CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME} +elif [ -e etc/${CFGFILE_NAME} ]; then + CFGFILE=etc/${CFGFILE_NAME} +else + echo "${0##*/}: can not find config file" + exit 1 +fi + +TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX` +trap "rm -rf $TEMPDIR" EXIT + +tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR} + +if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE} +then + echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date." + echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh." + exit 1 +fi diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh new file mode 100644 index 0000000..fa07a39 --- /dev/null +++ b/tools/config/generate_sample.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +print_hint() { + echo "Try \`${0##*/} --help' for more information." >&2 +} + +PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \ + --long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@") + +if [ $? != 0 ] ; then print_hint ; exit 1 ; fi + +eval set -- "$PARSED_OPTIONS" + +while true; do + case "$1" in + -h|--help) + echo "${0##*/} [options]" + echo "" + echo "options:" + echo "-h, --help show brief help" + echo "-b, --base-dir=DIR project base directory" + echo "-p, --package-name=NAME project package name" + echo "-o, --output-dir=DIR file output directory" + echo "-m, --module=MOD extra python module to interrogate for options" + echo "-l, --library=LIB extra library that registers options for discovery" + exit 0 + ;; + -b|--base-dir) + shift + BASEDIR=`echo $1 | sed -e 's/\/*$//g'` + shift + ;; + -p|--package-name) + shift + PACKAGENAME=`echo $1` + shift + ;; + -o|--output-dir) + shift + OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` + shift + ;; + -m|--module) + shift + MODULES="$MODULES -m $1" + shift + ;; + -l|--library) + shift + LIBRARIES="$LIBRARIES -l $1" + shift + ;; + --) + break + ;; + esac +done + +BASEDIR=${BASEDIR:-`pwd`} +if ! [ -d $BASEDIR ] +then + echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 +elif [[ $BASEDIR != /* ]] +then + BASEDIR=$(cd "$BASEDIR" && pwd) +fi + +PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}} +TARGETDIR=$BASEDIR/$PACKAGENAME +if ! [ -d $TARGETDIR ] +then + echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 +fi + +OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} +# NOTE(bnemec): Some projects put their sample config in etc/, +# some in etc/$PACKAGENAME/ +if [ -d $OUTPUTDIR/$PACKAGENAME ] +then + OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME +elif ! [ -d $OUTPUTDIR ] +then + echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 + exit 1 +fi + +BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` +find $TARGETDIR -type f -name "*.pyc" -delete +FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \ + -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) + +RC_FILE="`dirname $0`/oslo.config.generator.rc" +if test -r "$RC_FILE" +then + source "$RC_FILE" +fi + +for mod in ${WELLINGTON_CONFIG_GENERATOR_EXTRA_MODULES}; do + MODULES="$MODULES -m $mod" +done + +for lib in ${WELLINGTON_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do + LIBRARIES="$LIBRARIES -l $lib" +done + +export EVENTLET_NO_GREENDNS=yes + +OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) +[ "$OS_VARS" ] && eval "unset \$OS_VARS" +DEFAULT_MODULEPATH=wellington.openstack.common.config.generator +MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH} +OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample +python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE + +# Hook to allow projects to append custom config file snippets +CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) +for CONCAT_FILE in $CONCAT_FILES; do + cat $CONCAT_FILE >> $OUTPUTFILE +done diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc new file mode 100644 index 0000000..85d9b12 --- /dev/null +++ b/tools/config/oslo.config.generator.rc @@ -0,0 +1 @@ +export WELLINGTON_CONFIG_GENERATOR_EXTRA_MODULES="keystoneclient.middleware.auth_token" diff --git a/tools/install_venv b/tools/install_venv new file mode 100644 index 0000000..6a4aea2 --- /dev/null +++ b/tools/install_venv @@ -0,0 +1,3 @@ +#!/bin/sh + +tox -vvv -evenv -- python --version diff --git a/tools/install_venv.py b/tools/install_venv.py new file mode 100644 index 0000000..4d8feea --- /dev/null +++ b/tools/install_venv.py @@ -0,0 +1,75 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2010 OpenStack Foundation +# Copyright 2013 IBM Corp. +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ConfigParser +import os +import sys + +import install_venv_common as install_venv # flake8: noqa + + +def print_help(project, venv, root): + help = """ + %(project)s development environment setup is complete. + + %(project)s development uses virtualenv to track and manage Python + dependencies while in development and testing. + + To activate the %(project)s virtualenv for the extent of your current + shell session you can run: + + $ source %(venv)s/bin/activate + + Or, if you prefer, you can run commands in the virtualenv on a case by + case basis by running: + + $ %(root)s/tools/with_venv.sh + """ + print help % dict(project=project, venv=venv, root=root) + + +def main(argv): + root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + if os.environ.get('tools_path'): + root = os.environ['tools_path'] + venv = os.path.join(root, '.venv') + if os.environ.get('venv'): + venv = os.environ['venv'] + + pip_requires = os.path.join(root, 'requirements.txt') + test_requires = os.path.join(root, 'test-requirements.txt') + py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + setup_cfg = ConfigParser.ConfigParser() + setup_cfg.read('setup.cfg') + project = setup_cfg.get('metadata', 'name') + + install = install_venv.InstallVenv( + root, venv, pip_requires, test_requires, py_version, project) + options = install.parse_args(argv) + install.check_python_version() + install.check_dependencies() + install.create_virtualenv(no_site_packages=options.no_site_packages) + install.install_dependencies() + install.post_process() + print_help(project, venv, root) + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py new file mode 100644 index 0000000..d9e4de0 --- /dev/null +++ b/tools/install_venv_common.py @@ -0,0 +1,212 @@ +# Copyright 2013 OpenStack Foundation +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Provides methods needed by installation script for OpenStack development +virtual environments. + +Since this script is used to bootstrap a virtualenv from the system's Python +environment, it should be kept strictly compatible with Python 2.6. + +Synced in from openstack-common +""" + +from __future__ import print_function + +import optparse +import os +import subprocess +import sys + + +class InstallVenv(object): + + def __init__(self, root, venv, requirements, + test_requirements, py_version, + project): + self.root = root + self.venv = venv + self.requirements = requirements + self.test_requirements = test_requirements + self.py_version = py_version + self.project = project + + def die(self, message, *args): + print(message % args, file=sys.stderr) + sys.exit(1) + + def check_python_version(self): + if sys.version_info < (2, 6): + self.die("Need Python Version >= 2.6") + + def run_command_with_code(self, cmd, redirect_output=True, + check_exit_code=True): + """Runs a command in an out-of-process shell. + + Returns the output of that command. Working directory is self.root. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None + + proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return (output, proc.returncode) + + def run_command(self, cmd, redirect_output=True, check_exit_code=True): + return self.run_command_with_code(cmd, redirect_output, + check_exit_code)[0] + + def get_distro(self): + if (os.path.exists('/etc/fedora-release') or + os.path.exists('/etc/redhat-release')): + return Fedora( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) + else: + return Distro( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) + + def check_dependencies(self): + self.get_distro().install_virtualenv() + + def create_virtualenv(self, no_site_packages=True): + """Creates the virtual environment and installs PIP. + + Creates the virtual environment and installs PIP only into the + virtual environment. + """ + if not os.path.isdir(self.venv): + print('Creating venv...', end=' ') + if no_site_packages: + self.run_command(['virtualenv', '-q', '--no-site-packages', + self.venv]) + else: + self.run_command(['virtualenv', '-q', self.venv]) + print('done.') + else: + print("venv already exists...") + pass + + def pip_install(self, *args): + self.run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + def install_dependencies(self): + print('Installing dependencies with pip (this can take a while)...') + + # First things first, make sure our venv has the latest pip and + # setuptools and pbr + self.pip_install('pip>=1.4') + self.pip_install('setuptools') + self.pip_install('pbr') + + self.pip_install('-r', self.requirements) + self.pip_install('-r', self.test_requirements) + + def post_process(self): + self.get_distro().post_process() + + def parse_args(self, argv): + """Parses command-line arguments.""" + parser = optparse.OptionParser() + parser.add_option('-n', '--no-site-packages', + action='store_true', + help="Do not inherit packages from global Python " + "install") + return parser.parse_args(argv[1:])[0] + + +class Distro(InstallVenv): + + def check_cmd(self, cmd): + return bool(self.run_command(['which', cmd], + check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print('Installing virtualenv via easy_install...', end=' ') + if self.run_command(['easy_install', 'virtualenv']): + print('Succeeded') + return + else: + print('Failed') + + self.die('ERROR: virtualenv not found.\n\n%s development' + ' requires virtualenv, please install it using your' + ' favorite package management tool' % self.project) + + def post_process(self): + """Any distribution-specific post-processing gets done here. + + In particular, this is useful for applying patches to code inside + the venv. + """ + pass + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux + """ + + def check_pkg(self, pkg): + return self.run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def apply_patch(self, originalfile, patchfile): + self.run_command(['patch', '-N', originalfile, patchfile], + check_exit_code=False) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.die("Please install 'python-virtualenv'.") + + super(Fedora, self).install_virtualenv() + + def post_process(self): + """Workaround for a bug in eventlet. + + This currently affects RHEL6.1, but the fix can safely be + applied to all RHEL and Fedora distributions. + + This can be removed when the fix is applied upstream. + + Nova: https://bugs.launchpad.net/nova/+bug/884915 + Upstream: https://bitbucket.org/eventlet/eventlet/issue/89 + RHEL: https://bugzilla.redhat.com/958868 + """ + + if os.path.exists('contrib/redhat-eventlet.patch'): + # Install "patch" program if it's not there + if not self.check_pkg('patch'): + self.die("Please install 'patch'.") + + # Apply the eventlet patch + self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, + 'site-packages', + 'eventlet/green/subprocess.py'), + 'contrib/redhat-eventlet.patch') diff --git a/tools/with_venv.sh b/tools/with_venv.sh new file mode 100755 index 0000000..c8d2940 --- /dev/null +++ b/tools/with_venv.sh @@ -0,0 +1,4 @@ +#!/bin/bash +TOOLS=`dirname $0` +VENV=$TOOLS/../.venv +source $VENV/bin/activate && $@