diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..cc533227 --- /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/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..c978a52d --- /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 00000000..a4b269ca --- /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 00000000..cc2466ff --- /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 00000000..2b313584 --- /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 00000000..655acfd7 --- /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 00000000..1e9882df --- /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 00000000..9a29cf10 --- /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/tox.ini b/tox.ini new file mode 100644 index 00000000..5e0e314d --- /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