From 84a7dc47e2f666ff11b5816387b7ada2371e48c2 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 2 May 2013 14:50:57 -0400 Subject: [PATCH] Added project infrastructure needs. --- .gitignore | 30 ++++ .gitreview | 4 + CONTRIBUTING.rst | 17 +++ LICENSE | 176 +++++++++++++++++++++++ MANIFEST.in | 6 + README.rst | 4 + nova/test.py | 270 +++++++++++++++++++++++++++++++++++ requirements.txt | 31 ++++ setup.cfg | 56 ++++++++ setup.py | 21 +++ test-requirements.txt | 18 +++ tools/__init__.py | 0 tools/install_venv_common.py | 216 ++++++++++++++++++++++++++++ tools/patch_tox_venv.py | 38 +++++ tox.ini | 34 +++++ 15 files changed, 921 insertions(+) create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 CONTRIBUTING.rst create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 nova/test.py create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 test-requirements.txt create mode 100644 tools/__init__.py create mode 100644 tools/install_venv_common.py create mode 100644 tools/patch_tox_venv.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..1f39df9b2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled files +*.py[co] +*.a +*.o +*.so + +# Sphinx +_build + +# Packages/installer info +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Other +.testrepository +.tox +.*.swp +.coverage +cover +AUTHORS +ChangeLog diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000000..424fb37ef7 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.openstack.org +port=29418 +project=openstack/ironic.git diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000000..cc53322755 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,17 @@ +If you would like to contribute to the development of OpenStack, +you must follow the steps in the "If you're a developer, start here" +section of this page: + + http://wiki.openstack.org/HowToContribute + +Once those steps have been completed, changes to OpenStack +should be submitted for review via the Gerrit tool, following +the workflow documented at: + + http://wiki.openstack.org/GerritWorkflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/ironic diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..68c771a099 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000..c978a52dae --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000..a4b269caaf --- /dev/null +++ b/README.rst @@ -0,0 +1,4 @@ +Ironic +====== + +Provision Bare Metal machines with Nova. diff --git a/nova/test.py b/nova/test.py new file mode 100644 index 0000000000..cc2466ff37 --- /dev/null +++ b/nova/test.py @@ -0,0 +1,270 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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. + +"""Base classes for our unit tests. + +Allows overriding of flags for use of fakes, and some black magic for +inline callbacks. + +""" + +import eventlet +eventlet.monkey_patch(os=False) + +import os +import shutil +import sys +import uuid + +import fixtures +import mox +from oslo.config import cfg +import stubout +import testtools + +from nova import context +from nova import db +from nova.db import migration +from nova.network import manager as network_manager +from nova.openstack.common.db.sqlalchemy import session +from nova.openstack.common import log as logging +from nova.openstack.common import timeutils +from nova import paths +from nova import service +from nova.tests import conf_fixture +from nova.tests import policy_fixture + + +test_opts = [ + cfg.StrOpt('sqlite_clean_db', + default='clean.sqlite', + help='File name of clean sqlite db'), + ] + +CONF = cfg.CONF +CONF.register_opts(test_opts) +CONF.import_opt('sql_connection', + 'nova.openstack.common.db.sqlalchemy.session') +CONF.import_opt('sqlite_db', 'nova.openstack.common.db.sqlalchemy.session') +CONF.set_override('use_stderr', False) + +logging.setup('nova') + +_DB_CACHE = None + + +class Database(fixtures.Fixture): + + def __init__(self, db_session, db_migrate, sql_connection, + sqlite_db, sqlite_clean_db): + self.sql_connection = sql_connection + self.sqlite_db = sqlite_db + self.sqlite_clean_db = sqlite_clean_db + + self.engine = db_session.get_engine() + self.engine.dispose() + conn = self.engine.connect() + if sql_connection == "sqlite://": + if db_migrate.db_version() > db_migrate.INIT_VERSION: + return + else: + testdb = paths.state_path_rel(sqlite_db) + if os.path.exists(testdb): + return + db_migrate.db_sync() + self.post_migrations() + if sql_connection == "sqlite://": + conn = self.engine.connect() + self._DB = "".join(line for line in conn.connection.iterdump()) + self.engine.dispose() + else: + cleandb = paths.state_path_rel(sqlite_clean_db) + shutil.copyfile(testdb, cleandb) + + def setUp(self): + super(Database, self).setUp() + + if self.sql_connection == "sqlite://": + conn = self.engine.connect() + conn.connection.executescript(self._DB) + self.addCleanup(self.engine.dispose) + else: + shutil.copyfile(paths.state_path_rel(self.sqlite_clean_db), + paths.state_path_rel(self.sqlite_db)) + + def post_migrations(self): + """Any addition steps that are needed outside of the migrations.""" + ctxt = context.get_admin_context() + network = network_manager.VlanManager() + bridge_interface = CONF.flat_interface or CONF.vlan_interface + network.create_networks(ctxt, + label='test', + cidr=CONF.fixed_range, + multi_host=CONF.multi_host, + num_networks=CONF.num_networks, + network_size=CONF.network_size, + cidr_v6=CONF.fixed_range_v6, + gateway=CONF.gateway, + gateway_v6=CONF.gateway_v6, + bridge=CONF.flat_network_bridge, + bridge_interface=bridge_interface, + vpn_start=CONF.vpn_start, + vlan_start=CONF.vlan_start, + dns1=CONF.flat_network_dns) + for net in db.network_get_all(ctxt): + network.set_network_host(ctxt, net) + + +class ReplaceModule(fixtures.Fixture): + """Replace a module with a fake module.""" + + def __init__(self, name, new_value): + self.name = name + self.new_value = new_value + + def _restore(self, old_value): + sys.modules[self.name] = old_value + + def setUp(self): + super(ReplaceModule, self).setUp() + old_value = sys.modules.get(self.name) + sys.modules[self.name] = self.new_value + self.addCleanup(self._restore, old_value) + + +class ServiceFixture(fixtures.Fixture): + """Run a service as a test fixture.""" + + def __init__(self, name, host=None, **kwargs): + name = name + host = host and host or uuid.uuid4().hex + kwargs.setdefault('host', host) + kwargs.setdefault('binary', 'nova-%s' % name) + self.kwargs = kwargs + + def setUp(self): + super(ServiceFixture, self).setUp() + self.service = service.Service.create(**self.kwargs) + self.service.start() + self.addCleanup(self.service.kill) + + +class MoxStubout(fixtures.Fixture): + """Deal with code around mox and stubout as a fixture.""" + + def setUp(self): + super(MoxStubout, self).setUp() + # emulate some of the mox stuff, we can't use the metaclass + # because it screws with our generators + self.mox = mox.Mox() + self.stubs = stubout.StubOutForTesting() + self.addCleanup(self.mox.UnsetStubs) + self.addCleanup(self.stubs.UnsetAll) + self.addCleanup(self.stubs.SmartUnsetAll) + self.addCleanup(self.mox.VerifyAll) + + +class TestingException(Exception): + pass + + +class TestCase(testtools.TestCase): + """Test case base class for all unit tests.""" + + def setUp(self): + """Run before each test method to initialize test environment.""" + super(TestCase, self).setUp() + test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) + try: + test_timeout = int(test_timeout) + except ValueError: + # If timeout value is invalid do not set a timeout. + test_timeout = 0 + if test_timeout > 0: + self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) + self.useFixture(fixtures.NestedTempfile()) + self.useFixture(fixtures.TempHomeDir()) + + if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or + os.environ.get('OS_STDOUT_CAPTURE') == '1'): + stdout = self.useFixture(fixtures.StringStream('stdout')).stream + self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) + if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or + os.environ.get('OS_STDERR_CAPTURE') == '1'): + stderr = self.useFixture(fixtures.StringStream('stderr')).stream + self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) + + self.log_fixture = self.useFixture(fixtures.FakeLogger()) + self.useFixture(conf_fixture.ConfFixture(CONF)) + + global _DB_CACHE + if not _DB_CACHE: + _DB_CACHE = Database(session, migration, + sql_connection=CONF.sql_connection, + sqlite_db=CONF.sqlite_db, + sqlite_clean_db=CONF.sqlite_clean_db) + self.useFixture(_DB_CACHE) + + mox_fixture = self.useFixture(MoxStubout()) + self.mox = mox_fixture.mox + self.stubs = mox_fixture.stubs + self.addCleanup(self._clear_attrs) + self.useFixture(fixtures.EnvironmentVariable('http_proxy')) + self.policy = self.useFixture(policy_fixture.PolicyFixture()) + CONF.set_override('fatal_exception_format_errors', True) + + def _clear_attrs(self): + # Delete attributes that don't start with _ so they don't pin + # memory around unnecessarily for the duration of the test + # suite + for key in [k for k in self.__dict__.keys() if k[0] != '_']: + del self.__dict__[key] + + def flags(self, **kw): + """Override flag variables for a test.""" + group = kw.pop('group', None) + for k, v in kw.iteritems(): + CONF.set_override(k, v, group) + + def start_service(self, name, host=None, **kwargs): + svc = self.useFixture(ServiceFixture(name, host, **kwargs)) + return svc.service + + +class APICoverage(object): + + cover_api = None + + def test_api_methods(self): + self.assertTrue(self.cover_api is not None) + api_methods = [x for x in dir(self.cover_api) + if not x.startswith('_')] + test_methods = [x[5:] for x in dir(self) + if x.startswith('test_')] + self.assertThat( + test_methods, + testtools.matchers.ContainsAll(api_methods)) + + +class TimeOverride(fixtures.Fixture): + """Fixture to start and remove time override.""" + + def setUp(self): + super(TimeOverride, self).setUp() + timeutils.set_time_override() + self.addCleanup(timeutils.clear_time_override) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..2b313584b7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,31 @@ +d2to1>=0.2.10,<0.3 +pbr>=0.5,<0.6 +SQLAlchemy>=0.7.8,<0.7.99 +Cheetah>=2.4.4 +amqplib>=0.6.1 +anyjson>=0.2.4 +argparse +boto +eventlet>=0.9.17 +kombu>=1.0.4 +lxml>=2.3 +routes>=1.12.3 +WebOb==1.2.3 +greenlet>=0.3.1 +PasteDeploy>=1.5.0 +paste +sqlalchemy-migrate>=0.7.2 +netaddr>=0.7.6 +suds>=0.4 +paramiko +pyasn1 +Babel>=0.9.6 +iso8601>=0.1.4 +httplib2 +python-cinderclient>=1.0.1 +python-quantumclient>=2.2.0,<3.0.0 +python-glanceclient>=0.5.0,<2 +python-keystoneclient>=0.2.0 +stevedore>=0.7 +websockify<0.4 +oslo.config>=1.1.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..655acfd7d2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,56 @@ +[metadata] +name = ironic +version = 2013.2 +summary = OpenStack Bare Metal Provisioning +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 2.6 + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[files] +packages = + ironic + +[entry_points] +console_scripts = + nova-baremetal-deploy-helper = nova.cmd.baremetal_deploy_helper:main + nova-baremetal-manage = nova.cmd.baremetal_manage:main + +[build_sphinx] +all_files = 1 +build-dir = doc/build +source-dir = doc/source + +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + +[compile_catalog] +directory = ironic/locale +domain = ironic + +[update_catalog] +domain = ironic +output_dir = ironic/locale +input_file = ironic/locale/ironic.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = ironic/locale/nova.pot diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..1e9882df0e --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# 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 setuptools + +setuptools.setup( + setup_requires=['d2to1>=0.2.10,<0.3', 'pbr>=0.5,<0.6'], + d2to1=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000000..9a29cf10ce --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,18 @@ +# Packages needed for dev testing +distribute>=0.6.24 + +# Install bounded pep8/pyflakes first, then let flake8 install +pep8==1.4.5 +pyflakes==0.7.2 +flake8==2.0 +hacking>=0.5.3,<0.6 + +coverage>=3.6 +discover +fixtures>=0.3.12 +mox==0.5.3 +MySQL-python +python-subunit +sphinx>=1.1.2 +testrepository>=0.0.13 +testtools>=0.9.27 diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py new file mode 100644 index 0000000000..f0a1722c38 --- /dev/null +++ b/tools/install_venv_common.py @@ -0,0 +1,216 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +Synced in from openstack-common +""" + +import argparse +import os +import subprocess +import sys + + +class InstallVenv(object): + + def __init__(self, root, venv, pip_requires, test_requires, py_version, + project): + self.root = root + self.venv = venv + self.pip_requires = pip_requires + self.test_requires = test_requires + self.py_version = py_version + self.project = project + + def die(self, message, *args): + print >> sys.stderr, message % args + 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.pip_requires, + self.test_requires, self.py_version, self.project) + else: + return Distro(self.root, self.venv, self.pip_requires, + self.test_requires, 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...', + if no_site_packages: + self.run_command(['virtualenv', '-q', '--no-site-packages', + self.venv]) + else: + self.run_command(['virtualenv', '-q', self.venv]) + print 'done.' + print 'Installing pip in venv...', + if not self.run_command(['tools/with_venv.sh', 'easy_install', + 'pip>1.0']).strip(): + self.die("Failed to install pip.") + 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 + # distribute. + # NOTE: we keep pip at version 1.1 since the most recent version causes + # the .venv creation to fail. See: + # https://bugs.launchpad.net/nova/+bug/1047120 + self.pip_install('pip==1.1') + self.pip_install('distribute') + + # Install greenlet by hand - just listing it in the requires file does + # not + # get it installed in the right order + self.pip_install('greenlet') + + self.pip_install('-r', self.pip_requires) + self.pip_install('-r', self.test_requires) + + def post_process(self): + self.get_distro().post_process() + + def parse_args(self, argv): + """Parses command-line arguments.""" + parser = argparse.ArgumentParser() + parser.add_argument('-n', '--no-site-packages', + action='store_true', + help="Do not inherit packages from global Python " + "install") + return parser.parse_args(argv[1:]) + + +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...', + 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/which_linden/eventlet/issue/89 + """ + + # 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/patch_tox_venv.py b/tools/patch_tox_venv.py new file mode 100644 index 0000000000..ac2fc92433 --- /dev/null +++ b/tools/patch_tox_venv.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Red Hat, Inc. +# +# 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 os +import sys + +import tools.install_venv_common as install_venv + + +def main(argv): + root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + venv = os.environ['VIRTUAL_ENV'] + + 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]) + project = 'Nova' + install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, + py_version, project) + #NOTE(dprince): For Tox we only run post_process (which patches files, etc) + install.post_process() + +if __name__ == '__main__': + main(sys.argv) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000000..5e0e314d79 --- /dev/null +++ b/tox.ini @@ -0,0 +1,34 @@ +[tox] +envlist = py26,py27,pep8 + +[testenv] +setenv = VIRTUAL_ENV={envdir} + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = + python tools/patch_tox_venv.py + python setup.py testr --slowest --testr-args='{posargs}' + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv:pep8] +commands = + flake8 + +[testenv:cover] +setenv = VIRTUAL_ENV={envdir} +commands = + python tools/patch_tox_venv.py + python setup.py testr --coverage {posargs} + +[testenv:venv] +commands = {posargs} + +[flake8] +ignore = E12,E711,E721,E712,H302,H303,H403,H404,F +builtins = _ +exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build