From 4e90f08824bc947b8fc968b19ff1a3729ee6a222 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 18 Mar 2013 12:18:09 -0700 Subject: [PATCH] Make hacking a flake8 plugin. --- .gitignore | 30 ++ .gitreview | 4 + .testr.conf | 4 + CONTRIBUTING.rst | 17 ++ LICENSE | 176 ++++++++++++ MANIFEST.in | 7 + README.rst | 304 ++++++++++++++++++++ clean-vlans | 25 -- conf/README | 20 -- conf/analyze_opts.py | 80 ------ conf/extract_opts.py | 269 ----------------- conf/generate_sample.sh | 31 -- db/schema_diff.py | 270 ----------------- doc/source/_templates/.placeholder | 0 doc/source/_theme/layout.html | 83 ++++++ doc/source/_theme/theme.conf | 4 + doc/source/conf.py | 62 ++++ doc/source/index.rst | 21 ++ doc/source/static/basic.css | 416 +++++++++++++++++++++++++++ doc/source/static/default.css | 230 +++++++++++++++ doc/source/static/header-line.gif | Bin 0 -> 48 bytes doc/source/static/header_bg.jpg | Bin 0 -> 3738 bytes doc/source/static/jquery.tweet.js | 154 ++++++++++ doc/source/static/nature.css | 245 ++++++++++++++++ doc/source/static/openstack_logo.png | Bin 0 -> 3670 bytes doc/source/static/tweaks.css | 94 ++++++ enable-pre-commit-hook.sh | 42 --- esx/guest_tool.py | 403 -------------------------- flakes.py | 15 - hacking.py => hacking/__init__.py | 0 install_venv.py | 74 ----- install_venv_common.py | 220 -------------- lintstack.py | 199 ------------- lintstack.sh | 59 ---- nova-manage.bash_completion | 37 --- patch_tox_venv.py | 38 --- pip-requires | 30 -- regression_tester.py | 81 ------ requirements.txt | 3 + run_pep8.sh | 27 -- setup.cfg | 48 ++++ setup.py | 21 ++ test-requirements.txt | 7 + test-requires | 16 -- tox.ini | 31 ++ with_venv.sh | 7 - xenserver/cleanup_sm_locks.py | 123 -------- xenserver/destroy_cached_images.py | 69 ----- xenserver/stress_test.py | 172 ----------- xenserver/vdi_chain_cleanup.py | 128 --------- xenserver/vm_vdi_cleaner.py | 329 --------------------- 51 files changed, 1961 insertions(+), 2764 deletions(-) create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 .testr.conf create mode 100644 CONTRIBUTING.rst create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst delete mode 100755 clean-vlans delete mode 100644 conf/README delete mode 100755 conf/analyze_opts.py delete mode 100644 conf/extract_opts.py delete mode 100755 conf/generate_sample.sh delete mode 100755 db/schema_diff.py create mode 100644 doc/source/_templates/.placeholder create mode 100644 doc/source/_theme/layout.html create mode 100644 doc/source/_theme/theme.conf create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 doc/source/static/basic.css create mode 100644 doc/source/static/default.css create mode 100644 doc/source/static/header-line.gif create mode 100644 doc/source/static/header_bg.jpg create mode 100644 doc/source/static/jquery.tweet.js create mode 100644 doc/source/static/nature.css create mode 100644 doc/source/static/openstack_logo.png create mode 100644 doc/source/static/tweaks.css delete mode 100755 enable-pre-commit-hook.sh delete mode 100644 esx/guest_tool.py delete mode 100644 flakes.py rename hacking.py => hacking/__init__.py (100%) delete mode 100644 install_venv.py delete mode 100644 install_venv_common.py delete mode 100755 lintstack.py delete mode 100755 lintstack.sh delete mode 100644 nova-manage.bash_completion delete mode 100644 patch_tox_venv.py delete mode 100644 pip-requires delete mode 100755 regression_tester.py create mode 100644 requirements.txt delete mode 100755 run_pep8.sh create mode 100644 setup.cfg create mode 100755 setup.py create mode 100644 test-requirements.txt delete mode 100644 test-requires create mode 100644 tox.ini delete mode 100755 with_venv.sh delete mode 100755 xenserver/cleanup_sm_locks.py delete mode 100644 xenserver/destroy_cached_images.py delete mode 100644 xenserver/stress_test.py delete mode 100644 xenserver/vdi_chain_cleanup.py delete mode 100755 xenserver/vm_vdi_cleaner.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f39df9 --- /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 0000000..6eb747d --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.openstack.org +port=29418 +project=openstack-dev/hacking.git diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..1641f86 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..db7e515 --- /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/hacking diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /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 0000000..70dc0d7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include AUTHORS +include ChangeLog +include README.rst +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..eb8af6f --- /dev/null +++ b/README.rst @@ -0,0 +1,304 @@ +Introduction +============ + +hacking is a set of flake8 plugins that test and enforce: + +OpenStack Style Commandments +============================ + +- Step 1: Read http://www.python.org/dev/peps/pep-0008/ +- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again +- Step 3: Read on + + +General +------- +- Put two newlines between top-level code (funcs, classes, etc) +- Use only UNIX style newlines ("\n"), not Windows style ("\r\n") +- Put one newline between methods in classes and anywhere else +- Long lines should be wrapped in parentheses + in preference to using a backslash for line continuation. +- Do not write "except:", use "except Exception:" at the very least +- Include your name with TODOs as in "#TODO(termie)" +- Do not shadow a built-in or reserved word. Example:: + + def list(): + return [1, 2, 3] + + mylist = list() # BAD, shadows `list` built-in + + class Foo(object): + def list(self): + return [1, 2, 3] + + mylist = Foo().list() # OKAY, does not shadow built-in + +- Use the "is not" operator when testing for unequal identities. Example:: + + if not X is Y: # BAD, intended behavior is ambiguous + pass + + if X is not Y: # OKAY, intuitive + pass + +- Use the "not in" operator for evaluating membership in a collection. Example:: + + if not X in Y: # BAD, intended behavior is ambiguous + pass + + if X not in Y: # OKAY, intuitive + pass + + if not (X in Y or X in Z): # OKAY, still better than all those 'not's + pass + + +Imports +------- +- Do not import objects, only modules (*) +- Do not import more than one module per line (*) +- Do not use wildcard ``*`` import (*) +- Do not make relative imports +- Do not make new nova.db imports in nova/virt/* +- Order your imports by the full module path +- Organize your imports according to the following template + +(*) exceptions are: + +- imports from ``migrate`` package +- imports from ``sqlalchemy`` package +- imports from ``nova.db.sqlalchemy.session`` module +- imports from ``nova.db.sqlalchemy.migration.versioning_api`` package + +Example:: + + # vim: tabstop=4 shiftwidth=4 softtabstop=4 + {{stdlib imports in human alphabetical order}} + \n + {{third-party lib imports in human alphabetical order}} + \n + {{nova imports in human alphabetical order}} + \n + \n + {{begin your code}} + + +Human Alphabetical Order Examples +--------------------------------- +Example:: + + import httplib + import logging + import random + import StringIO + import time + import unittest + + import eventlet + import webob.exc + + import nova.api.ec2 + from nova.api import openstack + from nova.auth import users + from nova.endpoint import cloud + import nova.flags + from nova import test + + +Docstrings +---------- +Example:: + + """A one line docstring looks like this and ends in a period.""" + + + """A multi line docstring has a one-line summary, less than 80 characters. + + Then a new paragraph after a newline that explains in more detail any + general information about the function, class or method. Example usages + are also great to have here if it is a complex class for function. + + When writing the docstring for a class, an extra line should be placed + after the closing quotations. For more in-depth explanations for these + decisions see http://www.python.org/dev/peps/pep-0257/ + + If you are going to describe parameters and return values, use Sphinx, the + appropriate syntax is as follows. + + :param foo: the foo parameter + :param bar: the bar parameter + :returns: return_type -- description of the return value + :returns: description of the return value + :raises: AttributeError, KeyError + """ + + +Dictionaries/Lists +------------------ +If a dictionary (dict) or list object is longer than 80 characters, its items +should be split with newlines. Embedded iterables should have their items +indented. Additionally, the last item in the dictionary should have a trailing +comma. This increases readability and simplifies future diffs. + +Example:: + + my_dictionary = { + "image": { + "name": "Just a Snapshot", + "size": 2749573, + "properties": { + "user_id": 12, + "arch": "x86_64", + }, + "things": [ + "thing_one", + "thing_two", + ], + "status": "ACTIVE", + }, + } + + +Calling Methods +--------------- +Calls to methods 80 characters or longer should format each argument with +newlines. This is not a requirement, but a guideline:: + + unnecessarily_long_function_name('string one', + 'string two', + kwarg1=constants.ACTIVE, + kwarg2=['a', 'b', 'c']) + + +Rather than constructing parameters inline, it is better to break things up:: + + list_of_strings = [ + 'what_a_long_string', + 'not as long', + ] + + dict_of_numbers = { + 'one': 1, + 'two': 2, + 'twenty four': 24, + } + + object_one.call_a_method('string three', + 'string four', + kwarg1=list_of_strings, + kwarg2=dict_of_numbers) + + +Internationalization (i18n) Strings +----------------------------------- +In order to support multiple languages, we have a mechanism to support +automatic translations of exception and log strings. + +Example:: + + msg = _("An error occurred") + raise HTTPBadRequest(explanation=msg) + +If you have a variable to place within the string, first internationalize the +template string then do the replacement. + +Example:: + + msg = _("Missing parameter: %s") % ("flavor",) + LOG.error(msg) + +If you have multiple variables to place in the string, use keyword parameters. +This helps our translators reorder parameters when needed. + +Example:: + + msg = _("The server with id %(s_id)s has no key %(m_key)s") + LOG.error(msg % {"s_id": "1234", "m_key": "imageId"}) + + +Creating Unit Tests +------------------- +For every new feature, unit tests should be created that both test and +(implicitly) document the usage of said feature. If submitting a patch for a +bug that had no unit test, a new passing unit test should be added. If a +submitted bug fix does have a unit test, be sure to add a new one that fails +without the patch and passes with the patch. + +For more information on creating unit tests and utilizing the testing +infrastructure in OpenStack Nova, please read nova/tests/README.rst. + + +Running Tests +------------- +The testing system is based on a combination of tox and testr. The canonical +approach to running tests is to simply run the command `tox`. This will +create virtual environments, populate them with depenedencies and run all of +the tests that OpenStack CI systems run. Behind the scenes, tox is running +`testr run --parallel`, but is set up such that you can supply any additional +testr arguments that are needed to tox. For example, you can run: +`tox -- --analyze-isolation` to cause tox to tell testr to add +--analyze-isolation to its argument list. + +It is also possible to run the tests inside of a virtual environment +you have created, or it is possible that you have all of the dependencies +installed locally already. In this case, you can interact with the testr +command directly. Running `testr run` will run the entire test suite. `testr +run --parallel` will run it in parallel (this is the default incantation tox +uses.) More information about testr can be found at: +http://wiki.openstack.org/testr + + +openstack-common +---------------- + +A number of modules from openstack-common are imported into the project. + +These modules are "incubating" in openstack-common and are kept in sync +with the help of openstack-common's update.py script. See: + + http://wiki.openstack.org/CommonLibrary#Incubation + +The copy of the code should never be directly modified here. Please +always update openstack-common first and then run the script to copy +the changes across. + +OpenStack Trademark +------------------- + +OpenStack is a registered trademark of the OpenStack Foundation, and uses the +following capitalization: + + OpenStack + + +Commit Messages +--------------- +Using a common format for commit messages will help keep our git history +readable. Follow these guidelines: + + First, provide a brief summary of 50 characters or less. Summaries + of greater then 72 characters will be rejected by the gate. + + The first line of the commit message should provide an accurate + description of the change, not just a reference to a bug or + blueprint. It must be followed by a single blank line. + + If the change relates to a specific driver (libvirt, xenapi, qpid, etc...), + begin the first line of the commit message with the driver name, lowercased, + followed by a colon. + + Following your brief summary, provide a more detailed description of + the patch, manually wrapping the text at 72 characters. This + description should provide enough detail that one does not have to + refer to external resources to determine its high-level functionality. + + Once you use 'git review', two lines will be appended to the commit + message: a blank line followed by a 'Change-Id'. This is important + to correlate this commit with a specific review in Gerrit, and it + should not be modified. + +For further information on constructing high quality commit messages, +and how to split up commits into a series of changes, consult the +project wiki: + + http://wiki.openstack.org/GitCommitMessages diff --git a/clean-vlans b/clean-vlans deleted file mode 100755 index 284e5dc..0000000 --- a/clean-vlans +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# 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. - -export LC_ALL=C - -sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down -sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo brctl delbr foo -sudo ifconfig -a | grep vlan | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down -sudo ifconfig -a | grep vlan | cut -f1 -d" " | xargs -n1 -ifoo ip link del foo diff --git a/conf/README b/conf/README deleted file mode 100644 index fc24652..0000000 --- a/conf/README +++ /dev/null @@ -1,20 +0,0 @@ -This generate_sample.sh tool is used to generate etc/nova/nova.conf.sample - -Run it from the top-level working directory i.e. - - $> ./tools/conf/generate_sample.sh - -Watch out for warnings about modules like libvirt, qpid and zmq not -being found - these warnings are significant because they result -in options not appearing in the generated config file. - - -The analyze_opts.py tool is used to find options which appear in -/etc/nova/nova.conf but not in etc/nova/nova.conf.sample -This helps identify options in the nova.conf file which are not used by nova. -The tool also identifies any options which are set to the default value. - -Run it from the top-level working directory i.e. - - $> ./tools/conf/analyze_opts.py - diff --git a/conf/analyze_opts.py b/conf/analyze_opts.py deleted file mode 100755 index 3088d63..0000000 --- a/conf/analyze_opts.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2012, Cloudscaling -# 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. -''' -find_unused_options.py - -Compare the nova.conf file with the nova.conf.sample file to find any unused -options or default values in nova.conf -''' -import argparse -import os -import sys - -sys.path.append(os.getcwd()) -from oslo.config import iniparser - - -class PropertyCollecter(iniparser.BaseParser): - def __init__(self): - super(PropertyCollecter, self).__init__() - self.key_value_pairs = {} - - def assignment(self, key, value): - self.key_value_pairs[key] = value - - def new_section(self, section): - pass - - @classmethod - def collect_properties(cls, lineiter, sample_format=False): - def clean_sample(f): - for line in f: - if line.startswith("# ") and line != '# nova.conf sample #\n': - line = line[2:] - yield line - pc = cls() - if sample_format: - lineiter = clean_sample(lineiter) - pc.parse(lineiter) - return pc.key_value_pairs - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='''Compare the nova.conf - file with the nova.conf.sample file to find any unused options or - default values in nova.conf''') - - parser.add_argument('-c', action='store', - default='/etc/nova/nova.conf', - help='path to nova.conf\ - (defaults to /etc/nova/nova.conf)') - parser.add_argument('-s', default='./etc/nova/nova.conf.sample', - help='path to nova.conf.sample\ - (defaults to ./etc/nova/nova.conf.sample') - options = parser.parse_args() - - conf_file_options = PropertyCollecter.collect_properties(open(options.c)) - sample_conf_file_options = PropertyCollecter.collect_properties( - open(options.s), sample_format=True) - - for k, v in sorted(conf_file_options.items()): - if k not in sample_conf_file_options: - print "Unused:", k - for k, v in sorted(conf_file_options.items()): - if k in sample_conf_file_options and v == sample_conf_file_options[k]: - print "Default valued:", k diff --git a/conf/extract_opts.py b/conf/extract_opts.py deleted file mode 100644 index 89f335d..0000000 --- a/conf/extract_opts.py +++ /dev/null @@ -1,269 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 SINA Corporation -# 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. -# -# @author: Zhongyue Luo, SINA Corporation. -# - -"""Extracts OpenStack config option info from module(s).""" - -import imp -import os -import re -import socket -import sys -import textwrap - -from oslo.config import cfg - -from nova.openstack.common import importutils - - -STROPT = "StrOpt" -BOOLOPT = "BoolOpt" -INTOPT = "IntOpt" -FLOATOPT = "FloatOpt" -LISTOPT = "ListOpt" -MULTISTROPT = "MultiStrOpt" - -OPT_TYPES = { - STROPT: 'string value', - BOOLOPT: 'boolean value', - INTOPT: 'integer value', - FLOATOPT: 'floating point value', - LISTOPT: 'list value', - MULTISTROPT: 'multi valued', -} - -OPTION_COUNT = 0 -OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT, - FLOATOPT, LISTOPT, - MULTISTROPT])) - -PY_EXT = ".py" -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) -WORDWRAP_WIDTH = 60 - - -def main(srcfiles): - mods_by_pkg = dict() - for filepath in srcfiles: - pkg_name = filepath.split(os.sep)[1] - mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]), - os.path.basename(filepath).split('.')[0]]) - mods_by_pkg.setdefault(pkg_name, list()).append(mod_str) - # NOTE(lzyeval): place top level modules before packages - pkg_names = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys()) - pkg_names.sort() - ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys()) - ext_names.sort() - pkg_names.extend(ext_names) - - # opts_by_group is a mapping of group name to an options list - # The options list is a list of (module, options) tuples - opts_by_group = {'DEFAULT': []} - - for pkg_name in pkg_names: - mods = mods_by_pkg.get(pkg_name) - mods.sort() - for mod_str in mods: - if mod_str.endswith('.__init__'): - mod_str = mod_str[:mod_str.rfind(".")] - - mod_obj = _import_module(mod_str) - if not mod_obj: - continue - - for group, opts in _list_opts(mod_obj): - opts_by_group.setdefault(group, []).append((mod_str, opts)) - - print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', [])) - for group, opts in opts_by_group.items(): - print_group_opts(group, opts) - - print "# Total option count: %d" % OPTION_COUNT - - -def _import_module(mod_str): - try: - if mod_str.startswith('bin.'): - imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:])) - return sys.modules[mod_str[4:]] - else: - return importutils.import_module(mod_str) - except (ValueError, AttributeError), err: - return None - except ImportError, ie: - sys.stderr.write("%s\n" % str(ie)) - return None - except Exception, e: - return None - - -def _guess_groups(opt, mod_obj): - groups = [] - - # is it in the DEFAULT group? - if (opt.dest in cfg.CONF and - not isinstance(cfg.CONF[opt.dest], cfg.CONF.GroupAttr)): - groups.append('DEFAULT') - - # what other groups is it in? - for key, value in cfg.CONF.items(): - if not isinstance(value, cfg.CONF.GroupAttr): - continue - if opt.dest not in value: - continue - groups.append(key) - - if len(groups) == 1: - return groups[0] - - group = None - for g in groups: - if g in mod_obj.__name__: - group = g - break - - if group is None and 'DEFAULT' in groups: - sys.stderr.write("Guessing that " + opt.dest + - " in " + mod_obj.__name__ + - " is in DEFAULT group out of " + - ','.join(groups) + "\n") - return 'DEFAULT' - - if group is None: - sys.stderr.write("Unable to guess what group " + opt.dest + - " in " + mod_obj.__name__ + - " is in out of " + ','.join(groups) + "\n") - sys.exit(1) - - sys.stderr.write("Guessing that " + opt.dest + - " in " + mod_obj.__name__ + - " is in the " + group + - " group out of " + ','.join(groups) + "\n") - return group - - -def _list_opts(obj): - def is_opt(o): - return (isinstance(o, cfg.Opt) and - not isinstance(o, cfg.SubCommandOpt)) - - opts = list() - for attr_str in dir(obj): - attr_obj = getattr(obj, attr_str) - if is_opt(attr_obj): - opts.append(attr_obj) - elif (isinstance(attr_obj, list) and - all(map(lambda x: is_opt(x), attr_obj))): - opts.extend(attr_obj) - - ret = {} - for opt in opts: - ret.setdefault(_guess_groups(opt, obj), []).append(opt) - return ret.items() - - -def print_group_opts(group, opts_by_module): - print "[%s]" % group - print - global OPTION_COUNT - for mod, opts in opts_by_module: - OPTION_COUNT += len(opts) - print '#' - print '# Options defined in %s' % mod - print '#' - print - for opt in opts: - _print_opt(opt) - print - - -def _get_my_ip(): - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.error: - return None - - -def _sanitize_default(s): - """Set up a reasonably sensible default for pybasedir, my_ip and host.""" - if s.startswith(BASEDIR): - return s.replace(BASEDIR, '/usr/lib/python/site-packages') - elif BASEDIR in s: - return s.replace(BASEDIR, '') - elif s == _get_my_ip(): - return '10.0.0.1' - elif s == socket.getfqdn(): - return 'nova' - elif s.strip() != s: - return '"%s"' % s - return s - - -def _print_opt(opt): - opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help - if not opt_help: - sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name) - opt_type = None - try: - opt_type = OPTION_REGEX.search(str(type(opt))).group(0) - except (ValueError, AttributeError), err: - sys.stderr.write("%s\n" % str(err)) - sys.exit(1) - opt_help += ' (' + OPT_TYPES[opt_type] + ')' - print '#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)) - try: - if opt_default is None: - print '#%s=' % opt_name - elif opt_type == STROPT: - assert(isinstance(opt_default, basestring)) - print '#%s=%s' % (opt_name, _sanitize_default(opt_default)) - elif opt_type == BOOLOPT: - assert(isinstance(opt_default, bool)) - print '#%s=%s' % (opt_name, str(opt_default).lower()) - elif opt_type == INTOPT: - assert(isinstance(opt_default, int) and - not isinstance(opt_default, bool)) - print '#%s=%s' % (opt_name, opt_default) - elif opt_type == FLOATOPT: - assert(isinstance(opt_default, float)) - print '#%s=%s' % (opt_name, opt_default) - elif opt_type == LISTOPT: - assert(isinstance(opt_default, list)) - print '#%s=%s' % (opt_name, ','.join(opt_default)) - elif opt_type == MULTISTROPT: - assert(isinstance(opt_default, list)) - if not opt_default: - opt_default = [''] - for default in opt_default: - print '#%s=%s' % (opt_name, default) - print - except Exception: - sys.stderr.write('Error in option "%s"\n' % opt_name) - sys.exit(1) - - -if __name__ == '__main__': - if len(sys.argv) < 2: - print "usage: python %s [srcfile]...\n" % sys.argv[0] - sys.exit(0) - main(sys.argv[1:]) diff --git a/conf/generate_sample.sh b/conf/generate_sample.sh deleted file mode 100755 index 283d7a6..0000000 --- a/conf/generate_sample.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 SINA Corporation -# All Rights Reserved. -# Author: Zhongyue Luo -# -# 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. - -FILES=$(find nova -type f -name "*.py" ! -path "nova/tests/*" -exec \ - grep -l "Opt(" {} \; | sort -u) -BINS=$(echo bin/nova-*) - -PYTHONPATH=./:${PYTHONPATH} \ - python $(dirname "$0")/extract_opts.py ${FILES} ${BINS} > \ - etc/nova/nova.conf.sample - -# Remove compiled files created by imp.import_source() -for bin in ${BINS}; do - [ -f ${bin}c ] && rm ${bin}c -done diff --git a/db/schema_diff.py b/db/schema_diff.py deleted file mode 100755 index 406a2b6..0000000 --- a/db/schema_diff.py +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env python - -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 OpenStack Foundation -# -# 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. - -""" -Utility for diff'ing two versions of the DB schema. - -Each release cycle the plan is to compact all of the migrations from that -release into a single file. This is a manual and, unfortunately, error-prone -process. To ensure that the schema doesn't change, this tool can be used to -diff the compacted DB schema to the original, uncompacted form. - - -The schema versions are specified by providing a git ref (a branch name or -commit hash) and a SQLAlchemy-Migrate version number: -Run like: - - ./tools/db/schema_diff.py mysql master:latest my_branch:82 -""" -import datetime -import glob -import os -import subprocess -import sys - - -### Dump - - -def dump_db(db_driver, db_name, migration_version, dump_filename): - db_driver.create(db_name) - try: - migrate(db_driver, db_name, migration_version) - db_driver.dump(db_name, dump_filename) - finally: - db_driver.drop(db_name) - - -### Diff - - -def diff_files(filename1, filename2): - pipeline = ['diff -U 3 %(filename1)s %(filename2)s' % locals()] - - # Use colordiff if available - if subprocess.call(['which', 'colordiff']) == 0: - pipeline.append('colordiff') - - pipeline.append('less -R') - - cmd = ' | '.join(pipeline) - subprocess.check_call(cmd, shell=True) - - -### Database - - -class MySQL(object): - def create(self, name): - subprocess.check_call(['mysqladmin', '-u', 'root', 'create', name]) - - def drop(self, name): - subprocess.check_call(['mysqladmin', '-f', '-u', 'root', 'drop', name]) - - def dump(self, name, dump_filename): - subprocess.check_call( - 'mysqldump -u root %(name)s > %(dump_filename)s' % locals(), - shell=True) - - def url(self, name): - return 'mysql://root@localhost/%s' % name - - -class Postgres(object): - def create(self, name): - subprocess.check_call(['createdb', name]) - - def drop(self, name): - subprocess.check_call(['dropdb', name]) - - def dump(self, name, dump_filename): - subprocess.check_call( - 'pg_dump %(name)s > %(dump_filename)s' % locals(), - shell=True) - - def url(self, name): - return 'postgres://localhost/%s' % name - - -def _get_db_driver_class(db_type): - if db_type == "mysql": - return MySQL - elif db_type == "postgres": - return Postgres - else: - raise Exception(_("database %s not supported") % db_type) - - -### Migrate - - -MIGRATE_REPO = os.path.join(os.getcwd(), "nova/db/sqlalchemy/migrate_repo") - - -def migrate(db_driver, db_name, migration_version): - earliest_version = _migrate_get_earliest_version() - - # NOTE(sirp): sqlalchemy-migrate currently cannot handle the skipping of - # migration numbers. - _migrate_cmd( - db_driver, db_name, 'version_control', str(earliest_version - 1)) - - upgrade_cmd = ['upgrade'] - if migration_version != 'latest': - upgrade_cmd.append(str(migration_version)) - - _migrate_cmd(db_driver, db_name, *upgrade_cmd) - - -def _migrate_cmd(db_driver, db_name, *cmd): - manage_py = os.path.join(MIGRATE_REPO, 'manage.py') - - args = ['python', manage_py] - args += cmd - args += ['--repository=%s' % MIGRATE_REPO, - '--url=%s' % db_driver.url(db_name)] - - subprocess.check_call(args) - - -def _migrate_get_earliest_version(): - versions_glob = os.path.join(MIGRATE_REPO, 'versions', '???_*.py') - - versions = [] - for path in glob.iglob(versions_glob): - filename = os.path.basename(path) - prefix = filename.split('_', 1)[0] - try: - version = int(prefix) - except ValueError: - pass - versions.append(version) - - versions.sort() - return versions[0] - - -### Git - - -def git_current_branch_name(): - ref_name = git_symbolic_ref('HEAD', quiet=True) - current_branch_name = ref_name.replace('refs/heads/', '') - return current_branch_name - - -def git_symbolic_ref(ref, quiet=False): - args = ['git', 'symbolic-ref', ref] - if quiet: - args.append('-q') - proc = subprocess.Popen(args, stdout=subprocess.PIPE) - stdout, stderr = proc.communicate() - return stdout.strip() - - -def git_checkout(branch_name): - subprocess.check_call(['git', 'checkout', branch_name]) - - -def git_has_uncommited_changes(): - return subprocess.call(['git', 'diff', '--quiet', '--exit-code']) == 1 - - -### Command - - -def die(msg): - print >> sys.stderr, "ERROR: %s" % msg - sys.exit(1) - - -def usage(msg=None): - if msg: - print >> sys.stderr, "ERROR: %s" % msg - - prog = "schema_diff.py" - args = ["", "", - ""] - - print >> sys.stderr, "usage: %s %s" % (prog, ' '.join(args)) - sys.exit(1) - - -def parse_options(): - try: - db_type = sys.argv[1] - except IndexError: - usage("must specify DB type") - - try: - orig_branch, orig_version = sys.argv[2].split(':') - except IndexError: - usage('original branch and version required (e.g. master:82)') - - try: - new_branch, new_version = sys.argv[3].split(':') - except IndexError: - usage('new branch and version required (e.g. master:82)') - - return db_type, orig_branch, orig_version, new_branch, new_version - - -def main(): - timestamp = datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S") - - ORIG_DB = 'orig_db_%s' % timestamp - NEW_DB = 'new_db_%s' % timestamp - - ORIG_DUMP = ORIG_DB + ".dump" - NEW_DUMP = NEW_DB + ".dump" - - options = parse_options() - db_type, orig_branch, orig_version, new_branch, new_version = options - - # Since we're going to be switching branches, ensure user doesn't have any - # uncommited changes - if git_has_uncommited_changes(): - die("You have uncommited changes. Please commit them before running " - "this command.") - - db_driver = _get_db_driver_class(db_type)() - - users_branch = git_current_branch_name() - git_checkout(orig_branch) - - try: - # Dump Original Schema - dump_db(db_driver, ORIG_DB, orig_version, ORIG_DUMP) - - # Dump New Schema - git_checkout(new_branch) - dump_db(db_driver, NEW_DB, new_version, NEW_DUMP) - - diff_files(ORIG_DUMP, NEW_DUMP) - finally: - git_checkout(users_branch) - - if os.path.exists(ORIG_DUMP): - os.unlink(ORIG_DUMP) - - if os.path.exists(NEW_DUMP): - os.unlink(NEW_DUMP) - - -if __name__ == "__main__": - main() diff --git a/doc/source/_templates/.placeholder b/doc/source/_templates/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html new file mode 100644 index 0000000..8a01e1a --- /dev/null +++ b/doc/source/_theme/layout.html @@ -0,0 +1,83 @@ +{% extends "basic/layout.html" %} +{% set css_files = css_files + ['_static/tweaks.css'] %} +{% set script_files = script_files + ['_static/jquery.tweet.js'] %} + +{%- macro sidebar() %} + {%- if not embedded %}{% if not theme_nosidebar|tobool %} +
+
+ {%- block sidebarlogo %} + {%- if logo %} + + {%- endif %} + {%- endblock %} + {%- block sidebartoc %} + {%- if display_toc %} +

{{ _('Table Of Contents') }}

+ {{ toc }} + {%- endif %} + {%- endblock %} + {%- block sidebarrel %} + {%- if prev %} +

{{ _('Previous topic') }}

+

{{ prev.title }}

+ {%- endif %} + {%- if next %} +

{{ _('Next topic') }}

+

{{ next.title }}

+ {%- endif %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- if show_source and has_source and sourcename %} +

{{ _('This Page') }}

+ + {%- endif %} + {%- endblock %} + {%- if customsidebar %} + {% include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- if pagename != "search" %} + + + {%- endif %} + {%- endblock %} +
+
+ {%- endif %}{% endif %} +{%- endmacro %} + +{% block relbar1 %}{% endblock relbar1 %} + +{% block header %} + +{% endblock %} diff --git a/doc/source/_theme/theme.conf b/doc/source/_theme/theme.conf new file mode 100644 index 0000000..1cc4004 --- /dev/null +++ b/doc/source/_theme/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = nature.css +pygments_style = tango diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..01dc3b5 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.insert(0, os.path.abspath('../..')) +# -- General configuration ---------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] + +# autodoc generation is a bit aggressive and a nuisance when doing heavy +# text edit cycles. +# execute "export SPHINX_DEBUG=1" in your terminal to disable + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'hacking' +copyright = u'2013, OpenStack Foundation' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme_path = ["."] +html_theme = '_theme' +html_static_path = ['static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack LLC', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..4bf30d4 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,21 @@ +hacking +======= + +hacking is a set of flake8 plugins to test or enforce the more stringent +style guidelines that the OpenStack project operates under. + +Contents +-------- + +.. toctree:: + :maxdepth: 1 + + api/autoindex + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/source/static/basic.css b/doc/source/static/basic.css new file mode 100644 index 0000000..d909ce3 --- /dev/null +++ b/doc/source/static/basic.css @@ -0,0 +1,416 @@ +/** + * Sphinx stylesheet -- basic theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 0; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +/* -- other body styles ----------------------------------------------------- */ + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlight { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} diff --git a/doc/source/static/default.css b/doc/source/static/default.css new file mode 100644 index 0000000..c8091ec --- /dev/null +++ b/doc/source/static/default.css @@ -0,0 +1,230 @@ +/** + * Sphinx stylesheet -- default theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +div.body p, div.body dd, div.body li { + text-align: left; + line-height: 130%; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: left; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} diff --git a/doc/source/static/header-line.gif b/doc/source/static/header-line.gif new file mode 100644 index 0000000000000000000000000000000000000000..3601730e03488b7b5f92dc992d23ad753357c167 GIT binary patch literal 48 zcmZ?wbhEHbWMg1uXkcVG`smgF|Nj+#vM@3*Ff!;c00Bsbfr-7RpY8O^Kn4bD08FwB Aga7~l literal 0 HcmV?d00001 diff --git a/doc/source/static/header_bg.jpg b/doc/source/static/header_bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f788c41c26481728fa4329c17c87bde36001adc1 GIT binary patch literal 3738 zcmd5-YdDna8vedHnM0NtYi6>>At7O=uyTsZup5R_40A9)aXQa}U(l^=gSg=J*&3mKp$aM0r>UIFDe9Zy(vs} zWf)kqO2Y_n0$>ZQ0D&hY4tWjpY?Ii5?V)h*kc0fz?%ZIj3|{;F8E5l%d0)&*Hx~ulvc_*73u8%R zsVMV~ne!JY);&pWott~QIZYJFTXliYc2};JEU{X7W6;ZPfz;)U;U4#mEuK@K*=SC3BR-m&x9(Nna@>b@%FS34|P^jtsXRb5>z9gtPp;_MI2F3o*k z>csA-?CX4b;~4P-*L$+Mmb|51F)eD*wCc`Jt(9}C${Zo=!Uin=u_yMC^;`X!x$##4 z+~}dkT`NF@Uhw0r+6g_)?e!h8IX+OE^C96>UOsv0GPMD6(kr#ljhXRnA=O>Qj@%iT zqBF7aQ*}BG)h@6r0%#azk!r9yrN6>9dq~>KadV$~cGG?Hjk>~it^5rd#zS4KE*p+4 z;;B)%oBK8PNTs=A)a-z`n?3zJ%+h{`=>ijk4sYKr*>`eN1H`~Lo|Tm!o6qN{S* zeNl=NcpGzD55)XnLC|>g)~w={=c#4*x^;mk4Zo_FOFlffP@!?1`c+TogTVR4kp9-q z`d5cMBzNxk6qjPRK9*WY3uHS=bnm_QJvSMBBS_A#3i=ywsg6^|9rfruW0MhdGwHDO z?1gJRMQVecKE^gV{%uo(b)zl^Hd&vmnwFh88h*-?FJ;y=Hdqvt!K|s<$>xlzR=G4{ zZgGOCF43IXS?62B)w*N&dXt%U8X^Bjx}^%Yf>VFpFoKSGP%k?ems;&&J)|Dx(qtQD zu2tS)<_Qz4#LhBKYkl@Og}G)^5+F4P($Fk>)}{uMVv|;Sz2i4$XJ_WTw*;n>3N805rnXhbC52SC={E3rXRlrs|I6f;o|Cn%eje59{axu9sivy4oYmg=j|fLt3<3 zFce84aNb8GbK;y>RbBu71YBcYKL3@M3N25yoE%BtG z^K!`WTQ|fb-Ysa7T)mEw&4_b)PWYgc!)3W)H+neR9o^f|AXdgY1`gN+pvgzbbk`M z*Ts6${7M`2)9XIPy^MoXTiiP2GTp_OtgWMshnH)M&ZSO0)cet!oWo_0_&hV(0?Qdb zdo(sw{I#{hI`SWPM`N=U^#+MgN-*rZ#J7Cm7Jj89`5ehd_{z&9->Jc7$F(X4)&|`K z5rEgd;@dhi-IzJnSVpMd!Gf_G-QW+ zjVMrIas1)g%)GJ;(=oaK};O^)NYdS1`XR?K_;I7qj zhii5}x^he{U3M+GF+WpYws#=Pt#S9xB_X5QE7W+_rQdwMhukJnQj}5cnCz_sIJ#r0 zJa5drkRPI$X(4YdpCswJe#5aN4Jjw3V3Nzt&`lcKBI~#;!>jq7j8y# zvHrFg_#P376A45^hp-KU*P=R;DVdPK*w7D@Gw+`XsSpm^L-VkCooZF61sPAnnjsT# zND4C{>G#P10F_&txEoE!rX%Iy*L}Kna=Q%fDLJ_rF*LujRITZ)$g!?UYLkCXOoz-S z_p`Hny*Rh--l)aYQC&-2dd%;%VKGC1<1DJm_n~`nk4^yS`}&P zM}5bOypW0hwtvrwnE>}g1Mq+B>09qPp1b$hn6kC_iqF`tX#G-t7D$n}Ky9t}sUqiI zOe@odQ?JueZ+sg`-zoQ}J4if6vv1c9x{BDme+F6z{8esU^Kio zK_oPy9}@nlGywSOZy9`^- zzBg>C9|rgWF{pcCogEV@;d}VHrgeBl=5Dr*th4V!1`Z9Zrz9le1zHC#sM3{j#G2R?WMhl6b_yyoEAxX>Zixl$16`+^d$ihNtuIBUafyiCEv#oksNL<4= z*oDXsc7-(ww^9-b-6_|bITySG1N2C-7p0L4+V@R%j=4@ygc=89bmSNy38$S=ZiDyP z0SrqrVA;zi8kYBZ2@Mx(2Lx~-*bc@d1#4R($RJv$9ZTfx_t7Kc|HIHnd&@I386P?& z?d6Vd(48n${cTNFFCoSIUj#O{mmt%M&xCIFmR9Y3f{2UnF4e9@uFZOaYiY|CLdbDa z%xS9x4SHi7Fr-1?CnDqRK?)n&$TTBW5J?O&o{TnNCnLw*{QmT7{c}flSbp9&xi*zF z1TdUn&_!$_WxQbMKGkgsl}B%+N5ZV%Hy6_zJ>dejD89yCBMw9(d}z2fWjYH_nV6!F zqe_rI2H5Pi0^~S6)jjnu%lqZN*eQq6!||a24+edpSH_{C8Ew^g8dw2qdrH!@*E7K* z)00Bb8uUsai%v6Oa^L@3E02r|EG%EdV>q;=#2Q9Wjv3l?dAur$4bzyOl3M6 z1hf%&o*#2R&xnS1z4&R`Uq%`Ut0_P{BOwt;FuDb$1")); + }); + return $(returning); + }, + linkUser: function() { + var returning = []; + var regexp = /[\@]+([A-Za-z0-9-_]+)/gi; + this.each(function() { + returning.push(this.replace(regexp,"@$1")); + }); + return $(returning); + }, + linkHash: function() { + var returning = []; + var regexp = / [\#]+([A-Za-z0-9-_]+)/gi; + this.each(function() { + returning.push(this.replace(regexp, ' #$1')); + }); + return $(returning); + }, + capAwesome: function() { + var returning = []; + this.each(function() { + returning.push(this.replace(/\b(awesome)\b/gi, '$1')); + }); + return $(returning); + }, + capEpic: function() { + var returning = []; + this.each(function() { + returning.push(this.replace(/\b(epic)\b/gi, '$1')); + }); + return $(returning); + }, + makeHeart: function() { + var returning = []; + this.each(function() { + returning.push(this.replace(/(<)+[3]/gi, "")); + }); + return $(returning); + } + }); + + function relative_time(time_value) { + var parsed_date = Date.parse(time_value); + var relative_to = (arguments.length > 1) ? arguments[1] : new Date(); + var delta = parseInt((relative_to.getTime() - parsed_date) / 1000); + var pluralize = function (singular, n) { + return '' + n + ' ' + singular + (n == 1 ? '' : 's'); + }; + if(delta < 60) { + return 'less than a minute ago'; + } else if(delta < (45*60)) { + return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago'; + } else if(delta < (24*60*60)) { + return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago'; + } else { + return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago'; + } + } + + function build_url() { + var proto = ('https:' == document.location.protocol ? 'https:' : 'http:'); + if (s.list) { + return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?"; + } else if (s.query == null && s.username.length == 1) { + return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?'; + } else { + var query = (s.query || 'from:'+s.username.join('%20OR%20from:')); + return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?'; + } + } + + return this.each(function(){ + var list = $('
    ').appendTo(this); + var intro = '

    '+s.intro_text+'

    '; + var outro = '

    '+s.outro_text+'

    '; + var loading = $('

    '+s.loading_text+'

    '); + + if(typeof(s.username) == "string"){ + s.username = [s.username]; + } + + if (s.loading_text) $(this).append(loading); + $.getJSON(build_url(), function(data){ + if (s.loading_text) loading.remove(); + if (s.intro_text) list.before(intro); + $.each((data.results || data), function(i,item){ + // auto join text based on verb tense and content + if (s.join_text == "auto") { + if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) { + var join_text = s.auto_join_text_reply; + } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) { + var join_text = s.auto_join_text_url; + } else if (item.text.match(/^((\w+ed)|just) .*/im)) { + var join_text = s.auto_join_text_ed; + } else if (item.text.match(/^(\w*ing) .*/i)) { + var join_text = s.auto_join_text_ing; + } else { + var join_text = s.auto_join_text_default; + } + } else { + var join_text = s.join_text; + }; + + var from_user = item.from_user || item.user.screen_name; + var profile_image_url = item.profile_image_url || item.user.profile_image_url; + var join_template = ' '+join_text+' '; + var join = ((s.join_text) ? join_template : ' '); + var avatar_template = ''+from_user+'\'s avatar'; + var avatar = (s.avatar_size ? avatar_template : ''); + var date = ''+relative_time(item.created_at)+''; + var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ ''; + + // until we create a template option, arrange the items below to alter a tweet's display. + list.append('
  • ' + avatar + date + join + text + '
  • '); + + list.children('li:first').addClass('tweet_first'); + list.children('li:odd').addClass('tweet_even'); + list.children('li:even').addClass('tweet_odd'); + }); + if (s.outro_text) list.after(outro); + }); + + }); + }; +})(jQuery); diff --git a/doc/source/static/nature.css b/doc/source/static/nature.css new file mode 100644 index 0000000..a98bd42 --- /dev/null +++ b/doc/source/static/nature.css @@ -0,0 +1,245 @@ +/* + * nature.css_t + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- nature theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Arial, sans-serif; + font-size: 100%; + background-color: #111; + color: #555; + margin: 0; + padding: 0; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 {{ theme_sidebarwidth|toint }}px; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.document { + background-color: #eee; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 30px 30px; + font-size: 0.9em; +} + +div.footer { + color: #555; + width: 100%; + padding: 13px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #444; + text-decoration: underline; +} + +div.related { + background-color: #6BA81E; + line-height: 32px; + color: #fff; + text-shadow: 0px 1px 0 #444; + font-size: 0.9em; +} + +div.related a { + color: #E2F3CC; +} + +div.sphinxsidebar { + font-size: 0.75em; + line-height: 1.5em; +} + +div.sphinxsidebarwrapper{ + padding: 20px 0; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Arial, sans-serif; + color: #222; + font-size: 1.2em; + font-weight: normal; + margin: 0; + padding: 5px 10px; + background-color: #ddd; + text-shadow: 1px 1px 0 white +} + +div.sphinxsidebar h4{ + font-size: 1.1em; +} + +div.sphinxsidebar h3 a { + color: #444; +} + + +div.sphinxsidebar p { + color: #888; + padding: 5px 20px; +} + +div.sphinxsidebar p.topless { +} + +div.sphinxsidebar ul { + margin: 10px 20px; + padding: 0; + color: #000; +} + +div.sphinxsidebar a { + color: #444; +} + +div.sphinxsidebar input { + border: 1px solid #ccc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar input[type=text]{ + margin-left: 20px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #005B81; + text-decoration: none; +} + +a:hover { + color: #E32E00; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Arial, sans-serif; + background-color: #BED4EB; + font-weight: normal; + color: #212224; + margin: 30px 0px 10px 0px; + padding: 5px 0 5px 10px; + text-shadow: 0px 1px 0 white +} + +div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 150%; background-color: #C8D5E3; } +div.body h3 { font-size: 120%; background-color: #D8DEE3; } +div.body h4 { font-size: 110%; background-color: #D8DEE3; } +div.body h5 { font-size: 100%; background-color: #D8DEE3; } +div.body h6 { font-size: 100%; background-color: #D8DEE3; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + line-height: 1.5em; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.highlight{ + background-color: white; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 10px; + background-color: White; + color: #222; + line-height: 1.2em; + border: 1px solid #C6C9CB; + font-size: 1.1em; + margin: 1.5em 0 1.5em 0; + -webkit-box-shadow: 1px 1px 1px #d8d8d8; + -moz-box-shadow: 1px 1px 1px #d8d8d8; +} + +tt { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ + font-size: 1.1em; + font-family: monospace; +} + +.viewcode-back { + font-family: Arial, sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/doc/source/static/openstack_logo.png b/doc/source/static/openstack_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..146faec5cfe3773824f4caf39e4480e4974d10df GIT binary patch literal 3670 zcmV-c4yo~pP)CW75Qp#l)U;+N6jaIz6Nf$t6dNV>^>ETzcpQ=%tMaf0k|rg72+IW`z$FyfE+D{1@tt$t5DmX)*;QV?c;%+5Z&egAgfXTQJq-mZkC z>pFAHu}U=Axde_?s!99ZfDg_+9TYzDa6N1R3adhx&2Mb7>9w`KpMNz!>U5t2XQ8lZ zu+!+H7(PRwF@jAkwvI;|8|=Z_dfzV`Kpi;I!e=|Ql+HAdEag?VZ^Ilw9XJj9N1#1a z?UFC!)X62`CRIe^9YCLKbJ` z&O@f0zt{Z1YDF1utg2$F+rzvrncys+g37Xsd8)idSW(=}t#~qF#qBo29*@^ZCs<$W zpa144=o4g0z63h_ttPfIpH-FyG^MAH+6B~r$(4qw+Uv{2d#h`$lq+i+#Tf%CAzDFUh!pzX(6nW{EASJAQkhm!+}aGpHc z;(+N`S*@tYmump1T37E}J;!$0#F>^M*mT_X1x~bvnp&qP9IHI#bj-0z8FR+=p+e#*w3ugV#wX``sR-CI1!YiQsfc@Om<;1MBw zlfqH9z4Q|m*C?URU1OG(`UYn>Q8<|I!mby#FlN5MMFE8;Pyh$skbR?ngFLt?%nWSkS-#W5umy>@^DyAERP~{E&`M%0(qi&((^ahqL}u^jT<2dcf)p< z%Fxc9J$nh_`>_oNYC?oy`rIDY46Yrw4si3Qn~oXV%dJ}IlUD-40>QipyGa_dV0Z%J ztcEXm5yxR0gySJ04{nnbm#vP=Hq&GI<8VxcZ34pRjt6m%pE2H|!+HBJQrdBdyKHJR z2O_}hp!5bXuwniQYTF>yI|=cjT+2l`9T3|H+l4%ryPxWQm(ODW#8Ctj_CplcO=)qj zD#d~V6BahR9NY1kE5rF)_j<|!Cqnpq0uOKhL%w z>y8OyeTM1?REXc{0|3b=#WPZneh80PxL=Ljau1~+CgtMgg-vccMDX-L z9^7An_;!lFAi`#G_1F*OdM|Z$EVQs0m0$?mY}(baOZ%Zpd62#Pyg!3Jd4d zD^8+lSir&T6Y9-p9L#Wz6$5nXLjdOl?7Lv!TeMr}F14ranauW9=L>ubu*x>Bcrgwp zjrT@{rL*2Fc}Ilwn07QvdJfMOO2=(1Px)6&ih7lg839!Bx&}lQER~T`^7_x@fXo({ zCZMeZYt*!VgMTg>PR)PBaIwubzRY%jjE`-s zG;B}>2!lD=QLOTfQOEZKIEz*;yTJ9(Af0zNv;IDq7#Fr#W{Ap+7Sq1N3TL21X|h2t z=Dk>^bGSsRX-u+cZ23mMB_Ioc0yNIfcfLWB>$hVU3W3>d&a?IM+bGRGt+t}aiv(eh z(D6Z9N>U2|Qxle(!UVTeEKE6W))3WI5z48Rs8d5v0GwmyC8iQiUJO8KS?QwHl2abL zNW+hadDdPc8z%MSOG$l&WR@!!&M{WLmrnS=-0G#&`a)chX>mN9W1>|yqve@lL8a`f zXRmn$B8P=dLxE!2rIi}a*gh%FI4j?C;b@L=WgypiTRf==n6DKr9mUExo6a@{wLM-I z9%V9{!;5G!<8fMYikfEbrGXRQN-9*24}kIIpP&dEg@fiLqAY5|jjv}$P3x0avZODU zdX`c|G>h`1f=3uEu)L9C)H5%frni#HZXcX`TD{iQ-e2qXxj_f%|WW;byDMc%7+uBy}Y?KLC?jp%yyyeBNkqQ-*osw2ex&97Q{#C7%CdSDMNIV zTdC(LEm?&qPcNOjM)h9Grs|M(gsuhV8@96?m4WkQ>j{bJIs)m^neL%ua!i+N8>Lh+ zKu#7rF~VOH@hb{zGXYwys!Um4Vkf+H8Hj6?^eI%kT%j+HA0K=6qdQ@nfR57Q`Jm9T zc)Yg9-`e~BRE!xoKZ z=mP|0Kihr}V1$5sHw$QekmoL)lQ;~@H$S)}s3xuwypiubB?1%OyBpwC08TH!=?BrQ zhOp`PTu;%u0}Q=XKGb7d$g8*;de8c1UI|Re2R;;Radh_D!FIZg+JP`oJg>5 z;&B7eVAomZe>j~hOOIVRO_Q7eSGz37hxmnsG!n%HX`C6gSqFcg(RLmikn%EPR*wel zrsc;>!vQ<>2ZW`lk`MbNLopFd#_9mh8iKPH;KbjC@xJU${pdxuTF{uO(eG#9t*>XP z_4Seh`r_#q$^xeiuy(=eSouv66cpS!t3n`|j`6xnmSs1q@;0!I)m<6eYHHGMRdB87 ziruozT=gn@yp`B9oGxD-b7PqhZum|oJCfLB38&8v51ijj-Pb`qvCr3FtJ0aFms2h3(n0-}3jJ~J$ zCzep7-MIZFbo$(m8zWm?SoRl__blLE+!fFBVVk1&XLg+vmVNcTk9O2+q?x#F0LZUN zu6oM~C)(7^0|az4nM}@aZf<@RkH0CR8<-Yn-fZe+Dbr#iJWSt#tnR4^h<@ePXWmeHIO4q^X zCbiy(=k3R1o1}0E+7x*OOe-qnIXG{#N_rqK*1NH}Qz6aumTR`YTgo5K=q=61;5@b- zrgUA_Qz=)(TPN!tCZE|{?B0*r9ov5Fcip6xQ2;Yqs*2_o7TFKGp0|~bcP@6+a(rz^ zXXmmyBfT}ucw_t(6s+f^t_)nc>RKW<-q_&J35vN+RPLsR?VAsQeHLyCR7AWvxFOVc zAg-xl=j*RipzaKWx3lAf?ei`PoM;bbAL>svH?JqQwjSulb9bghytRt%*5x-no>xlf zh7qj0LYRXVDU})?Btsy7^71*ujsEP_ACyd)P)*ULWBCXox@PUfwmQ#)Vl&oeIqpQY zHMgU+xe0EhQ)RmjdB3JHGdrsvJ9?A=WwOrn)J?BH{+D&O_@SKdrj2|8Z{hS1T(k>&Zlt;p=tqw*mVY1aLt=u^eAHkW>8cb#@q& z4-SLa@ii zCt7NGrLv)1Scy9ew-sOwwLYn2a6T#KzJgnbacm7Z20q6tcs~C!0DI+r(=$l+x{=W0A}~0&W)ll4*&oF07*qoM6N<$f~n6U7ytkO literal 0 HcmV?d00001 diff --git a/doc/source/static/tweaks.css b/doc/source/static/tweaks.css new file mode 100644 index 0000000..3f3fb3f --- /dev/null +++ b/doc/source/static/tweaks.css @@ -0,0 +1,94 @@ +body { + background: #fff url(../_static/header_bg.jpg) top left no-repeat; +} + +#header { + width: 950px; + margin: 0 auto; + height: 102px; +} + +#header h1#logo { + background: url(../_static/openstack_logo.png) top left no-repeat; + display: block; + float: left; + text-indent: -9999px; + width: 175px; + height: 55px; +} + +#navigation { + background: url(../_static/header-line.gif) repeat-x 0 bottom; + display: block; + float: left; + margin: 27px 0 0 25px; + padding: 0; +} + +#navigation li{ + float: left; + display: block; + margin-right: 25px; +} + +#navigation li a { + display: block; + font-weight: normal; + text-decoration: none; + background-position: 50% 0; + padding: 20px 0 5px; + color: #353535; + font-size: 14px; +} + +#navigation li a.current, #navigation li a.section { + border-bottom: 3px solid #cf2f19; + color: #cf2f19; +} + +div.related { + background-color: #cde2f8; + border: 1px solid #b0d3f8; +} + +div.related a { + color: #4078ba; + text-shadow: none; +} + +div.sphinxsidebarwrapper { + padding-top: 0; +} + +pre { + color: #555; +} + +div.documentwrapper h1, div.documentwrapper h2, div.documentwrapper h3, div.documentwrapper h4, div.documentwrapper h5, div.documentwrapper h6 { + font-family: 'PT Sans', sans-serif !important; + color: #264D69; + border-bottom: 1px dotted #C5E2EA; + padding: 0; + background: none; + padding-bottom: 5px; +} + +div.documentwrapper h3 { + color: #CF2F19; +} + +a.headerlink { + color: #fff !important; + margin-left: 5px; + background: #CF2F19 !important; +} + +div.body { + margin-top: -25px; + margin-left: 230px; +} + +div.document { + width: 960px; + margin: 0 auto; +} \ No newline at end of file diff --git a/enable-pre-commit-hook.sh b/enable-pre-commit-hook.sh deleted file mode 100755 index be1ed27..0000000 --- a/enable-pre-commit-hook.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# Copyright 2011 OpenStack Foundation -# -# 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. - -PRE_COMMIT_SCRIPT=.git/hooks/pre-commit - -make_hook() { - echo "exec ./run_tests.sh -N -p" >> $PRE_COMMIT_SCRIPT - chmod +x $PRE_COMMIT_SCRIPT - - if [ -w $PRE_COMMIT_SCRIPT -a -x $PRE_COMMIT_SCRIPT ]; then - echo "pre-commit hook was created successfully" - else - echo "unable to create pre-commit hook" - fi -} - -# NOTE(jk0): Make sure we are in nova's root directory before adding the hook. -if [ ! -d ".git" ]; then - echo "unable to find .git; moving up a directory" - cd .. - if [ -d ".git" ]; then - make_hook - else - echo "still unable to find .git; hook not created" - fi -else - make_hook -fi - diff --git a/esx/guest_tool.py b/esx/guest_tool.py deleted file mode 100644 index 2c27930..0000000 --- a/esx/guest_tool.py +++ /dev/null @@ -1,403 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Citrix Systems, Inc. -# Copyright 2011 OpenStack Foundation -# -# 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. - -""" -Guest tools for ESX to set up network in the guest. -On Windows we require pyWin32 installed on Python. -""" - -import array -import gettext -import logging -import os -import platform -import socket -import struct -import subprocess -import sys -import time - -gettext.install('nova', unicode=1) - -PLATFORM_WIN = 'win32' -PLATFORM_LINUX = 'linux2' -ARCH_32_BIT = '32bit' -ARCH_64_BIT = '64bit' -NO_MACHINE_ID = 'No machine id' - -# Logging -FORMAT = "%(asctime)s - %(levelname)s - %(message)s" -if sys.platform == PLATFORM_WIN: - LOG_DIR = os.path.join(os.environ.get('ALLUSERSPROFILE'), 'openstack') -elif sys.platform == PLATFORM_LINUX: - LOG_DIR = '/var/log/openstack' -else: - LOG_DIR = 'logs' -if not os.path.exists(LOG_DIR): - os.mkdir(LOG_DIR) -LOG_FILENAME = os.path.join(LOG_DIR, 'openstack-guest-tools.log') -logging.basicConfig(filename=LOG_FILENAME, format=FORMAT) - -if sys.hexversion < 0x3000000: - _byte = ord # 2.x chr to integer -else: - _byte = int # 3.x byte to integer - - -class ProcessExecutionError: - """Process Execution Error Class.""" - - def __init__(self, exit_code, stdout, stderr, cmd): - self.exit_code = exit_code - self.stdout = stdout - self.stderr = stderr - self.cmd = cmd - - def __str__(self): - return str(self.exit_code) - - -def _bytes2int(bytes): - """Convert bytes to int.""" - intgr = 0 - for byt in bytes: - intgr = (intgr << 8) + _byte(byt) - return intgr - - -def _parse_network_details(machine_id): - """ - Parse the machine_id to get MAC, IP, Netmask and Gateway fields per NIC. - machine_id is of the form ('NIC_record#NIC_record#', '') - Each of the NIC will have record NIC_record in the form - 'MAC;IP;Netmask;Gateway;Broadcast;DNS' where ';' is field separator. - Each record is separated by '#' from next record. - """ - logging.debug(_("Received machine_id from vmtools : %s") % machine_id[0]) - network_details = [] - if machine_id[1].strip() == "1": - pass - else: - for machine_id_str in machine_id[0].split('#'): - network_info_list = machine_id_str.split(';') - if len(network_info_list) % 6 != 0: - break - no_grps = len(network_info_list) / 6 - i = 0 - while i < no_grps: - k = i * 6 - network_details.append(( - network_info_list[k].strip().lower(), - network_info_list[k + 1].strip(), - network_info_list[k + 2].strip(), - network_info_list[k + 3].strip(), - network_info_list[k + 4].strip(), - network_info_list[k + 5].strip().split(','))) - i += 1 - logging.debug(_("NIC information from vmtools : %s") % network_details) - return network_details - - -def _get_windows_network_adapters(): - """Get the list of windows network adapters.""" - import win32com.client - wbem_locator = win32com.client.Dispatch('WbemScripting.SWbemLocator') - wbem_service = wbem_locator.ConnectServer('.', 'root\cimv2') - wbem_network_adapters = wbem_service.InstancesOf('Win32_NetworkAdapter') - network_adapters = [] - for adapter in wbem_network_adapters: - if (adapter.NetConnectionStatus == 2 or - adapter.NetConnectionStatus == 7): - adapter_name = adapter.NetConnectionID - mac_address = adapter.MacAddress.lower() - config = adapter.associators_( - 'Win32_NetworkAdapterSetting', - 'Win32_NetworkAdapterConfiguration')[0] - ip_address = '' - subnet_mask = '' - if config.IPEnabled: - ip_address = config.IPAddress[0] - subnet_mask = config.IPSubnet[0] - #config.DefaultIPGateway[0] - network_adapters.append({'name': adapter_name, - 'mac-address': mac_address, - 'ip-address': ip_address, - 'subnet-mask': subnet_mask}) - return network_adapters - - -def _get_linux_network_adapters(): - """Get the list of Linux network adapters.""" - import fcntl - max_bytes = 8096 - arch = platform.architecture()[0] - if arch == ARCH_32_BIT: - offset1 = 32 - offset2 = 32 - elif arch == ARCH_64_BIT: - offset1 = 16 - offset2 = 40 - else: - raise OSError(_("Unknown architecture: %s") % arch) - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - names = array.array('B', '\0' * max_bytes) - outbytes = struct.unpack('iL', fcntl.ioctl( - sock.fileno(), - 0x8912, - struct.pack('iL', max_bytes, names.buffer_info()[0])))[0] - adapter_names = [names.tostring()[n_cnt:n_cnt + offset1].split('\0', 1)[0] - for n_cnt in xrange(0, outbytes, offset2)] - network_adapters = [] - for adapter_name in adapter_names: - ip_address = socket.inet_ntoa(fcntl.ioctl( - sock.fileno(), - 0x8915, - struct.pack('256s', adapter_name))[20:24]) - subnet_mask = socket.inet_ntoa(fcntl.ioctl( - sock.fileno(), - 0x891b, - struct.pack('256s', adapter_name))[20:24]) - raw_mac_address = '%012x' % _bytes2int(fcntl.ioctl( - sock.fileno(), - 0x8927, - struct.pack('256s', adapter_name))[18:24]) - mac_address = ":".join([raw_mac_address[m_counter:m_counter + 2] - for m_counter in range(0, len(raw_mac_address), 2)]).lower() - network_adapters.append({'name': adapter_name, - 'mac-address': mac_address, - 'ip-address': ip_address, - 'subnet-mask': subnet_mask}) - return network_adapters - - -def _get_adapter_name_and_ip_address(network_adapters, mac_address): - """Get the adapter name based on the MAC address.""" - adapter_name = None - ip_address = None - for network_adapter in network_adapters: - if network_adapter['mac-address'] == mac_address.lower(): - adapter_name = network_adapter['name'] - ip_address = network_adapter['ip-address'] - break - return adapter_name, ip_address - - -def _get_win_adapter_name_and_ip_address(mac_address): - """Get Windows network adapter name.""" - network_adapters = _get_windows_network_adapters() - return _get_adapter_name_and_ip_address(network_adapters, mac_address) - - -def _get_linux_adapter_name_and_ip_address(mac_address): - """Get Linux network adapter name.""" - network_adapters = _get_linux_network_adapters() - return _get_adapter_name_and_ip_address(network_adapters, mac_address) - - -def _execute(cmd_list, process_input=None, check_exit_code=True): - """Executes the command with the list of arguments specified.""" - cmd = ' '.join(cmd_list) - logging.debug(_("Executing command: '%s'") % cmd) - env = os.environ.copy() - obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) - result = None - if process_input is not None: - result = obj.communicate(process_input) - else: - result = obj.communicate() - obj.stdin.close() - if obj.returncode: - logging.debug(_("Result was %s") % obj.returncode) - if check_exit_code and obj.returncode != 0: - (stdout, stderr) = result - raise ProcessExecutionError(exit_code=obj.returncode, - stdout=stdout, - stderr=stderr, - cmd=cmd) - time.sleep(0.1) - return result - - -def _windows_set_networking(): - """Set IP address for the windows VM.""" - program_files = os.environ.get('PROGRAMFILES') - program_files_x86 = os.environ.get('PROGRAMFILES(X86)') - vmware_tools_bin = None - if os.path.exists(os.path.join(program_files, 'VMware', 'VMware Tools', - 'vmtoolsd.exe')): - vmware_tools_bin = os.path.join(program_files, 'VMware', - 'VMware Tools', 'vmtoolsd.exe') - elif os.path.exists(os.path.join(program_files, 'VMware', 'VMware Tools', - 'VMwareService.exe')): - vmware_tools_bin = os.path.join(program_files, 'VMware', - 'VMware Tools', 'VMwareService.exe') - elif program_files_x86 and os.path.exists(os.path.join(program_files_x86, - 'VMware', 'VMware Tools', - 'VMwareService.exe')): - vmware_tools_bin = os.path.join(program_files_x86, 'VMware', - 'VMware Tools', 'VMwareService.exe') - if vmware_tools_bin: - cmd = ['"' + vmware_tools_bin + '"', '--cmd', 'machine.id.get'] - for network_detail in _parse_network_details(_execute(cmd, - check_exit_code=False)): - (mac_address, ip_address, subnet_mask, gateway, broadcast, - dns_servers) = network_detail - name_and_ip = _get_win_adapter_name_and_ip_address(mac_address) - adapter_name, current_ip_address = name_and_ip - if adapter_name and not ip_address == current_ip_address: - cmd = ['netsh', 'interface', 'ip', 'set', 'address', - 'name="%s"' % adapter_name, 'source=static', ip_address, - subnet_mask, gateway, '1'] - _execute(cmd) - # Windows doesn't let you manually set the broadcast address - for dns_server in dns_servers: - if dns_server: - cmd = ['netsh', 'interface', 'ip', 'add', 'dns', - 'name="%s"' % adapter_name, dns_server] - _execute(cmd) - else: - logging.warn(_("VMware Tools is not installed")) - - -def _filter_duplicates(all_entries): - final_list = [] - for entry in all_entries: - if entry and entry not in final_list: - final_list.append(entry) - return final_list - - -def _set_rhel_networking(network_details=None): - """Set IPv4 network settings for RHEL distros.""" - network_details = network_details or [] - all_dns_servers = [] - for network_detail in network_details: - (mac_address, ip_address, subnet_mask, gateway, broadcast, - dns_servers) = network_detail - all_dns_servers.extend(dns_servers) - name_and_ip = _get_linux_adapter_name_and_ip_address(mac_address) - adapter_name, current_ip_address = name_and_ip - if adapter_name and not ip_address == current_ip_address: - interface_file_name = ('/etc/sysconfig/network-scripts/ifcfg-%s' % - adapter_name) - # Remove file - os.remove(interface_file_name) - # Touch file - _execute(['touch', interface_file_name]) - interface_file = open(interface_file_name, 'w') - interface_file.write('\nDEVICE=%s' % adapter_name) - interface_file.write('\nUSERCTL=yes') - interface_file.write('\nONBOOT=yes') - interface_file.write('\nBOOTPROTO=static') - interface_file.write('\nBROADCAST=%s' % broadcast) - interface_file.write('\nNETWORK=') - interface_file.write('\nGATEWAY=%s' % gateway) - interface_file.write('\nNETMASK=%s' % subnet_mask) - interface_file.write('\nIPADDR=%s' % ip_address) - interface_file.write('\nMACADDR=%s' % mac_address) - interface_file.close() - if all_dns_servers: - dns_file_name = "/etc/resolv.conf" - os.remove(dns_file_name) - _execute(['touch', dns_file_name]) - dns_file = open(dns_file_name, 'w') - dns_file.write("; generated by OpenStack guest tools") - unique_entries = _filter_duplicates(all_dns_servers) - for dns_server in unique_entries: - dns_file.write("\nnameserver %s" % dns_server) - dns_file.close() - _execute(['/sbin/service', 'network', 'restart']) - - -def _set_ubuntu_networking(network_details=None): - """Set IPv4 network settings for Ubuntu.""" - network_details = network_details or [] - all_dns_servers = [] - interface_file_name = '/etc/network/interfaces' - # Remove file - os.remove(interface_file_name) - # Touch file - _execute(['touch', interface_file_name]) - interface_file = open(interface_file_name, 'w') - for device, network_detail in enumerate(network_details): - (mac_address, ip_address, subnet_mask, gateway, broadcast, - dns_servers) = network_detail - all_dns_servers.extend(dns_servers) - name_and_ip = _get_linux_adapter_name_and_ip_address(mac_address) - adapter_name, current_ip_address = name_and_ip - - if adapter_name: - interface_file.write('\nauto %s' % adapter_name) - interface_file.write('\niface %s inet static' % adapter_name) - interface_file.write('\nbroadcast %s' % broadcast) - interface_file.write('\ngateway %s' % gateway) - interface_file.write('\nnetmask %s' % subnet_mask) - interface_file.write('\naddress %s\n' % ip_address) - logging.debug(_("Successfully configured NIC %(device)d with " - "NIC info %(detail)s"), {'device': device, - 'detail': network_detail}) - interface_file.close() - - if all_dns_servers: - dns_file_name = "/etc/resolv.conf" - os.remove(dns_file_name) - _execute(['touch', dns_file_name]) - dns_file = open(dns_file_name, 'w') - dns_file.write("; generated by OpenStack guest tools") - unique_entries = _filter_duplicates(all_dns_servers) - for dns_server in unique_entries: - dns_file.write("\nnameserver %s" % dns_server) - dns_file.close() - - logging.debug(_("Restarting networking....\n")) - _execute(['/etc/init.d/networking', 'restart']) - - -def _linux_set_networking(): - """Set IP address for the Linux VM.""" - vmware_tools_bin = None - if os.path.exists('/usr/sbin/vmtoolsd'): - vmware_tools_bin = '/usr/sbin/vmtoolsd' - elif os.path.exists('/usr/bin/vmtoolsd'): - vmware_tools_bin = '/usr/bin/vmtoolsd' - elif os.path.exists('/usr/sbin/vmware-guestd'): - vmware_tools_bin = '/usr/sbin/vmware-guestd' - elif os.path.exists('/usr/bin/vmware-guestd'): - vmware_tools_bin = '/usr/bin/vmware-guestd' - if vmware_tools_bin: - cmd = [vmware_tools_bin, '--cmd', 'machine.id.get'] - network_details = _parse_network_details(_execute(cmd, - check_exit_code=False)) - # TODO(sateesh): For other distros like suse, debian, BSD, etc. - if(platform.dist()[0] == 'Ubuntu'): - _set_ubuntu_networking(network_details) - elif (platform.dist()[0] == 'redhat'): - _set_rhel_networking(network_details) - else: - logging.warn(_("Distro '%s' not supported") % platform.dist()[0]) - else: - logging.warn(_("VMware Tools is not installed")) - -if __name__ == '__main__': - pltfrm = sys.platform - if pltfrm == PLATFORM_WIN: - _windows_set_networking() - elif pltfrm == PLATFORM_LINUX: - _linux_set_networking() - else: - raise NotImplementedError(_("Platform not implemented: '%s'") % pltfrm) diff --git a/flakes.py b/flakes.py deleted file mode 100644 index 7f96116..0000000 --- a/flakes.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - wrapper for pyflakes to ignore gettext based warning: - "undefined name '_'" - - Synced in from openstack-common -""" -import sys - -import pyflakes.checker -from pyflakes.scripts import pyflakes - -if __name__ == "__main__": - orig_builtins = set(pyflakes.checker._MAGIC_GLOBALS) - pyflakes.checker._MAGIC_GLOBALS = orig_builtins | set(['_']) - sys.exit(pyflakes.main()) diff --git a/hacking.py b/hacking/__init__.py similarity index 100% rename from hacking.py rename to hacking/__init__.py diff --git a/install_venv.py b/install_venv.py deleted file mode 100644 index dbc7333..0000000 --- a/install_venv.py +++ /dev/null @@ -1,74 +0,0 @@ -# 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. -# -# Copyright 2010 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. - -import os -import sys - -import install_venv_common as install_venv - - -def print_help(venv, root): - help = """ - Nova development environment setup is complete. - - Nova development uses virtualenv to track and manage Python dependencies - while in development and testing. - - To activate the Nova virtualenv for the extent of your current shell - session you can run: - - $ source %s/bin/activate - - Or, if you prefer, you can run commands in the virtualenv on a case by case - basis by running: - - $ %s/tools/with_venv.sh - - Also, make test will automatically use the virtualenv. - """ - print help % (venv, 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, 'tools', 'pip-requires') - test_requires = os.path.join(root, 'tools', 'test-requires') - 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) - 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(venv, root) - -if __name__ == '__main__': - main(sys.argv) diff --git a/install_venv_common.py b/install_venv_common.py deleted file mode 100644 index 4130656..0000000 --- a/install_venv_common.py +++ /dev/null @@ -1,220 +0,0 @@ -# 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 yum_install(self, pkg, **kwargs): - print "Attempting to install '%s' via yum" % pkg - self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) - - 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.yum_install('python-virtualenv', check_exit_code=False) - - 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.yum_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/lintstack.py b/lintstack.py deleted file mode 100755 index 5c4fb0a..0000000 --- a/lintstack.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2012, AT&T Labs, Yun Mao -# 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. - -"""pylint error checking.""" - -import cStringIO as StringIO -import json -import re -import sys - -from pylint import lint -from pylint.reporters import text - -# Note(maoy): E1103 is error code related to partial type inference -ignore_codes = ["E1103"] -# Note(maoy): the error message is the pattern of E0202. It should be ignored -# for nova.tests modules -ignore_messages = ["An attribute affected in nova.tests"] -# Note(maoy): we ignore all errors in openstack.common because it should be -# checked elsewhere. We also ignore nova.tests for now due to high false -# positive rate. -ignore_modules = ["nova/openstack/common/", "nova/tests/"] - -KNOWN_PYLINT_EXCEPTIONS_FILE = "tools/pylint_exceptions" - - -class LintOutput(object): - - _cached_filename = None - _cached_content = None - - def __init__(self, filename, lineno, line_content, code, message, - lintoutput): - self.filename = filename - self.lineno = lineno - self.line_content = line_content - self.code = code - self.message = message - self.lintoutput = lintoutput - - @classmethod - def from_line(cls, line): - m = re.search(r"(\S+):(\d+): \[(\S+)(, \S+)?] (.*)", line) - matched = m.groups() - filename, lineno, code, message = (matched[0], int(matched[1]), - matched[2], matched[-1]) - if cls._cached_filename != filename: - with open(filename) as f: - cls._cached_content = list(f.readlines()) - cls._cached_filename = filename - line_content = cls._cached_content[lineno - 1].rstrip() - return cls(filename, lineno, line_content, code, message, - line.rstrip()) - - @classmethod - def from_msg_to_dict(cls, msg): - """From the output of pylint msg, to a dict, where each key - is a unique error identifier, value is a list of LintOutput - """ - result = {} - for line in msg.splitlines(): - obj = cls.from_line(line) - if obj.is_ignored(): - continue - key = obj.key() - if key not in result: - result[key] = [] - result[key].append(obj) - return result - - def is_ignored(self): - if self.code in ignore_codes: - return True - if any(self.filename.startswith(name) for name in ignore_modules): - return True - if any(msg in self.message for msg in ignore_messages): - return True - return False - - def key(self): - if self.code in ["E1101", "E1103"]: - # These two types of errors are like Foo class has no member bar. - # We discard the source code so that the error will be ignored - # next time another Foo.bar is encountered. - return self.message, "" - return self.message, self.line_content.strip() - - def json(self): - return json.dumps(self.__dict__) - - def review_str(self): - return ("File %(filename)s\nLine %(lineno)d:%(line_content)s\n" - "%(code)s: %(message)s" % self.__dict__) - - -class ErrorKeys(object): - - @classmethod - def print_json(cls, errors, output=sys.stdout): - print >>output, "# automatically generated by tools/lintstack.py" - for i in sorted(errors.keys()): - print >>output, json.dumps(i) - - @classmethod - def from_file(cls, filename): - keys = set() - for line in open(filename): - if line and line[0] != "#": - d = json.loads(line) - keys.add(tuple(d)) - return keys - - -def run_pylint(): - buff = StringIO.StringIO() - reporter = text.ParseableTextReporter(output=buff) - args = ["--include-ids=y", "-E", "nova"] - lint.Run(args, reporter=reporter, exit=False) - val = buff.getvalue() - buff.close() - return val - - -def generate_error_keys(msg=None): - print "Generating", KNOWN_PYLINT_EXCEPTIONS_FILE - if msg is None: - msg = run_pylint() - errors = LintOutput.from_msg_to_dict(msg) - with open(KNOWN_PYLINT_EXCEPTIONS_FILE, "w") as f: - ErrorKeys.print_json(errors, output=f) - - -def validate(newmsg=None): - print "Loading", KNOWN_PYLINT_EXCEPTIONS_FILE - known = ErrorKeys.from_file(KNOWN_PYLINT_EXCEPTIONS_FILE) - if newmsg is None: - print "Running pylint. Be patient..." - newmsg = run_pylint() - errors = LintOutput.from_msg_to_dict(newmsg) - - print "Unique errors reported by pylint: was %d, now %d." \ - % (len(known), len(errors)) - passed = True - for err_key, err_list in errors.items(): - for err in err_list: - if err_key not in known: - print err.lintoutput - print - passed = False - if passed: - print "Congrats! pylint check passed." - redundant = known - set(errors.keys()) - if redundant: - print "Extra credit: some known pylint exceptions disappeared." - for i in sorted(redundant): - print json.dumps(i) - print "Consider regenerating the exception file if you will." - else: - print ("Please fix the errors above. If you believe they are false" - " positives, run 'tools/lintstack.py generate' to overwrite.") - sys.exit(1) - - -def usage(): - print """Usage: tools/lintstack.py [generate|validate] - To generate pylint_exceptions file: tools/lintstack.py generate - To validate the current commit: tools/lintstack.py - """ - - -def main(): - option = "validate" - if len(sys.argv) > 1: - option = sys.argv[1] - if option == "generate": - generate_error_keys() - elif option == "validate": - validate() - else: - usage() - - -if __name__ == "__main__": - main() diff --git a/lintstack.sh b/lintstack.sh deleted file mode 100755 index d8591d0..0000000 --- a/lintstack.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2012-2013, AT&T Labs, Yun Mao -# 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. - -# Use lintstack.py to compare pylint errors. -# We run pylint twice, once on HEAD, once on the code before the latest -# commit for review. -set -e -TOOLS_DIR=$(cd $(dirname "$0") && pwd) -# Get the current branch name. -GITHEAD=`git rev-parse --abbrev-ref HEAD` -if [[ "$GITHEAD" == "HEAD" ]]; then - # In detached head mode, get revision number instead - GITHEAD=`git rev-parse HEAD` - echo "Currently we are at commit $GITHEAD" -else - echo "Currently we are at branch $GITHEAD" -fi - -cp -f $TOOLS_DIR/lintstack.py $TOOLS_DIR/lintstack.head.py - -if git rev-parse HEAD^2 2>/dev/null; then - # The HEAD is a Merge commit. Here, the patch to review is - # HEAD^2, the master branch is at HEAD^1, and the patch was - # written based on HEAD^2~1. - PREV_COMMIT=`git rev-parse HEAD^2~1` - git checkout HEAD~1 - # The git merge is necessary for reviews with a series of patches. - # If not, this is a no-op so won't hurt either. - git merge $PREV_COMMIT -else - # The HEAD is not a merge commit. This won't happen on gerrit. - # Most likely you are running against your own patch locally. - # We assume the patch to examine is HEAD, and we compare it against - # HEAD~1 - git checkout HEAD~1 -fi - -# First generate tools/pylint_exceptions from HEAD~1 -$TOOLS_DIR/lintstack.head.py generate -# Then use that as a reference to compare against HEAD -git checkout $GITHEAD -$TOOLS_DIR/lintstack.head.py -echo "Check passed. FYI: the pylint exceptions are:" -cat $TOOLS_DIR/pylint_exceptions - diff --git a/nova-manage.bash_completion b/nova-manage.bash_completion deleted file mode 100644 index 053d419..0000000 --- a/nova-manage.bash_completion +++ /dev/null @@ -1,37 +0,0 @@ -# bash completion for openstack nova-manage - -_nova_manage_opts="" # lazy init -_nova_manage_opts_exp="" # lazy init - -# dict hack for bash 3 -_set_nova_manage_subopts () { - eval _nova_manage_subopts_"$1"='$2' -} -_get_nova_manage_subopts () { - eval echo '${_nova_manage_subopts_'"$1"'#_nova_manage_subopts_}' -} - -_nova_manage() -{ - local cur prev subopts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - if [ "x$_nova_manage_opts" == "x" ] ; then - _nova_manage_opts="`nova-manage bash-completion 2>/dev/null`" - _nova_manage_opts_exp="`echo $_nova_manage_opts | sed -e "s/\s/|/g"`" - fi - - if [[ " `echo $_nova_manage_opts` " =~ " $prev " ]] ; then - if [ "x$(_get_nova_manage_subopts "$prev")" == "x" ] ; then - subopts="`nova-manage bash-completion $prev 2>/dev/null`" - _set_nova_manage_subopts "$prev" "$subopts" - fi - COMPREPLY=($(compgen -W "$(_get_nova_manage_subopts "$prev")" -- ${cur})) - elif [[ ! " ${COMP_WORDS[@]} " =~ " "($_nova_manage_opts_exp)" " ]] ; then - COMPREPLY=($(compgen -W "${_nova_manage_opts}" -- ${cur})) - fi - return 0 -} -complete -F _nova_manage nova-manage diff --git a/patch_tox_venv.py b/patch_tox_venv.py deleted file mode 100644 index 399acbc..0000000 --- a/patch_tox_venv.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 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, 'tools', 'pip-requires') - test_requires = os.path.join(root, 'tools', 'test-requires') - 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/pip-requires b/pip-requires deleted file mode 100644 index 092f549..0000000 --- a/pip-requires +++ /dev/null @@ -1,30 +0,0 @@ -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 -suds>=0.4 -paramiko -pyasn1 -Babel>=0.9.6 -iso8601>=0.1.4 -httplib2 -setuptools_git>=0.4 -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/regression_tester.py b/regression_tester.py deleted file mode 100755 index f21af61..0000000 --- a/regression_tester.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python - -"""Tool for checking if patch contains a regression test. - -Pass in gerrit review number as parameter, tool will download branch and run -modified tests without bug fix. -""" - -import string -import subprocess -import sys - -gerrit_number = None - -#TODO(jogo) use proper optParser -if len(sys.argv) == 2: - gerrit_number = sys.argv[1] -else: - gerrit_number = None - print ("no gerrit review number specified, running on latest commit" - "on current branch.") - - -def run(cmd, fail_ok=False): - print "running: %s" % cmd - try: - rval = subprocess.check_output(cmd, shell=True) - except subprocess.CalledProcessError: - if not fail_ok: - print "The command above terminated with an error." - sys.exit(1) - pass - return rval - - -test_works = False - -if gerrit_number: - original_branch = run("git rev-parse --abbrev-ref HEAD") - run("git review -d %s" % gerrit_number) - -# run new tests with old code -run("git checkout HEAD^ nova") -run("git checkout HEAD nova/tests") - -# identify which tests have changed -tests = run("git whatchanged --format=oneline -1 | grep \"nova/tests\" " - "| cut -f2").split() -test_list = [] -for test in tests: - test_list.append(string.replace(test[0:-3], '/', '.')) - -if test_list == []: - test_works = False - expect_failure = "" -else: - # run new tests, expect them to fail - expect_failure = run(("tox -epy27 %s 2>&1" % string.join(test_list)), - fail_ok=True) - if "FAILED (id=" in expect_failure: - test_works = True - -# cleanup -run("git checkout HEAD nova") -if gerrit_number: - new_branch = run("git status | head -1 | cut -d ' ' -f 4") - run("git checkout %s" % original_branch) - run("git branch -D %s" % new_branch) - - -if test_works: - print expect_failure - print "" - print "*******************************" - print "FOUND a regression test" -else: - print expect_failure - print "" - print "*******************************" - print "NO regression test" - sys.exit(1) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..81e68b4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +d2to1 +flake8 +pbr diff --git a/run_pep8.sh b/run_pep8.sh deleted file mode 100755 index a2a982c..0000000 --- a/run_pep8.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -set -e -# This is used by run_tests.sh and tox.ini -python tools/hacking.py --doctest - -# Until all these issues get fixed, ignore. -PEP8='python tools/hacking.py --ignore=E12,E711,E721,E712,N303,N403,N404' - -EXCLUDE='--exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*' -EXCLUDE+=',*egg,build,./plugins/xenserver/networking/etc/xensource/scripts' -EXCLUDE+=',./plugins/xenserver/xenapi/etc/xapi.d/plugins' -${PEP8} ${EXCLUDE} . - -${PEP8} --filename=nova* bin - -SCRIPT_ROOT=$(echo $(cd "$(dirname $0)"; pwd) | sed s/\\/tools//) - -SCRIPTS_PATH=${SCRIPT_ROOT}/plugins/xenserver/networking/etc/xensource/scripts -PYTHONPATH=${SCRIPTS_PATH} ${PEP8} ./plugins/xenserver/networking - -# NOTE(sirp): Also check Dom0 plugins w/o .py extension -PLUGINS_PATH=${SCRIPT_ROOT}/plugins/xenserver/xenapi/etc/xapi.d/plugins -PYTHONPATH=${PLUGINS_PATH} ${PEP8} ./plugins/xenserver/xenapi \ - `find plugins/xenserver/xenapi/etc/xapi.d/plugins -type f -perm +111` - -! pyflakes nova/ | grep "imported but unused\|redefinition of function" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..561c5c3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,48 @@ +[metadata] +name = hacking +author = OpenStack +author-email = openstack-dev@lists.openstack.org +summary = OpenStack Hacking Guidline Enforcement +description-file = + README.rst +home-page = http://pypi.python.org/pypi/hacking +classifier = + Development Status :: 4 - Beta + Environment :: Console + Environment :: OpenStack + Intended Audience :: Developers + Intended Audience :: Information Technology + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + +[files] +packages = + hacking + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[entry_points] +flake8.extension = + H101 = hacking:hacking_todo_format + H201 = hacking:hacking_except_format + H202 = hacking:hacking_except_format_assert + H301 = hacking:hacking_import_rules + H306 = hacking:hacking_import_alphabetical + H307 = hacking:hacking_import_no_db_in_virt + H401 = hacking:hacking_docstring_start_space + H402 = hacking:hacking_docstring_one_line + H403 = hacking:hacking_docstring_multiline_end + H404 = hacking:hacking_docstring_multiline_start + H601 = hacking:hacking_no_cr + H700 = hacking:hacking_localization_strings + H901 = hacking:hacking_is_not + H902 = hacking:hacking_not_in + +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..59a0090 --- /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', 'pbr'], + d2to1=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..fd36375 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,7 @@ +coverage>=3.6 +discover +fixtures>=0.3.12 +python-subunit +sphinx>=1.1.2 +testrepository>=0.0.13 +testtools>=0.9.27 diff --git a/test-requires b/test-requires deleted file mode 100644 index f4a905f..0000000 --- a/test-requires +++ /dev/null @@ -1,16 +0,0 @@ -# Packages needed for dev testing -distribute>=0.6.24 - -coverage>=3.6 -discover -feedparser -fixtures>=0.3.12 -flake8 -hacking -mox==0.5.3 -MySQL-python -psycopg2 -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 0000000..3c1492e --- /dev/null +++ b/tox.ini @@ -0,0 +1,31 @@ +[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 setup.py testr --slowest --testr-args='{posargs}' + +[tox:jenkins] +sitepackages = True +downloadcache = ~/cache/pip + +[testenv:pep8] +commands = flake8 + +[testenv:cover] +setenv = VIRTUAL_ENV={envdir} +commands = + python setup.py testr --coverage + +[testenv:venv] +commands = {posargs} + +[flake8] +exclude = .venv,.tox,dist,doc,*.egg +show-source = true diff --git a/with_venv.sh b/with_venv.sh deleted file mode 100755 index 94e05c1..0000000 --- a/with_venv.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -tools_path=${tools_path:-$(dirname $0)} -venv_path=${venv_path:-${tools_path}} -venv_dir=${venv_name:-/../.venv} -TOOLS=${tools_path} -VENV=${venv:-${venv_path}/${venv_dir}} -source ${VENV}/bin/activate && "$@" diff --git a/xenserver/cleanup_sm_locks.py b/xenserver/cleanup_sm_locks.py deleted file mode 100755 index 5ccb6e0..0000000 --- a/xenserver/cleanup_sm_locks.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2013 OpenStack Foundation -# -# 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. -""" -Script to cleanup old XenServer /var/lock/sm locks. - -XenServer 5.6 and 6.0 do not appear to always cleanup locks when using a -FileSR. ext3 has a limit of 32K inode links, so when we have 32K-2 (31998) -locks laying around, builds will begin to fail because we can't create any -additional locks. This cleanup script is something we can run periodically as -a stop-gap measure until this is fixed upstream. - -This script should be run on the dom0 of the affected machine. -""" -import errno -import optparse -import os -import sys -import time - -BASE = '/var/lock/sm' - - -def _get_age_days(secs): - return float(time.time() - secs) / 86400 - - -def _parse_args(): - parser = optparse.OptionParser() - parser.add_option("-d", "--dry-run", - action="store_true", dest="dry_run", default=False, - help="don't actually remove locks") - parser.add_option("-l", "--limit", - action="store", type='int', dest="limit", - default=sys.maxint, - help="max number of locks to delete (default: no limit)") - parser.add_option("-v", "--verbose", - action="store_true", dest="verbose", default=False, - help="don't print status messages to stdout") - - options, args = parser.parse_args() - - try: - days_old = int(args[0]) - except (IndexError, ValueError): - parser.print_help() - sys.exit(1) - - return options, days_old - - -def main(): - options, days_old = _parse_args() - - if not os.path.exists(BASE): - print >> sys.stderr, "error: '%s' doesn't exist. Make sure you're"\ - " running this on the dom0." % BASE - sys.exit(1) - - lockpaths_removed = 0 - nspaths_removed = 0 - - for nsname in os.listdir(BASE)[:options.limit]: - nspath = os.path.join(BASE, nsname) - - if not os.path.isdir(nspath): - continue - - # Remove old lockfiles - removed = 0 - locknames = os.listdir(nspath) - for lockname in locknames: - lockpath = os.path.join(nspath, lockname) - lock_age_days = _get_age_days(os.path.getmtime(lockpath)) - if lock_age_days > days_old: - lockpaths_removed += 1 - removed += 1 - - if options.verbose: - print 'Removing old lock: %03d %s' % (lock_age_days, - lockpath) - - if not options.dry_run: - os.unlink(lockpath) - - # Remove empty namespace paths - if len(locknames) == removed: - nspaths_removed += 1 - - if options.verbose: - print 'Removing empty namespace: %s' % nspath - - if not options.dry_run: - try: - os.rmdir(nspath) - except OSError, e: - if e.errno == errno.ENOTEMPTY: - print >> sys.stderr, "warning: directory '%s'"\ - " not empty" % nspath - else: - raise - - if options.dry_run: - print "** Dry Run **" - - print "Total locks removed: ", lockpaths_removed - print "Total namespaces removed: ", nspaths_removed - - -if __name__ == '__main__': - main() diff --git a/xenserver/destroy_cached_images.py b/xenserver/destroy_cached_images.py deleted file mode 100644 index 625c765..0000000 --- a/xenserver/destroy_cached_images.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -destroy_cached_images.py - -This script is used to clean up Glance images that are cached in the SR. By -default, this script will only cleanup unused cached images. - -Options: - - --dry_run - Don't actually destroy the VDIs - --all_cached - Destroy all cached images instead of just unused cached - images. -""" -import eventlet -eventlet.monkey_patch() - -import os -import sys - -from oslo.config import cfg - -# If ../nova/__init__.py exists, add ../ to Python search path, so that -# it will override what happens to be installed in /usr/(local/)lib/python... -POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir, - os.pardir)) -if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')): - sys.path.insert(0, POSSIBLE_TOPDIR) - -from nova import config -from nova.openstack.common import log as logging -from nova import utils -from nova.virt.xenapi import driver as xenapi_driver -from nova.virt.xenapi import vm_utils - -destroy_opts = [ - cfg.BoolOpt('all_cached', - default=False, - help='Destroy all cached images instead of just unused cached' - ' images.'), - cfg.BoolOpt('dry_run', - default=False, - help='Don\'t actually delete the VDIs.') -] - -CONF = cfg.CONF -CONF.register_cli_opts(destroy_opts) - - -def main(): - config.parse_args(sys.argv) - utils.monkey_patch() - - xenapi = xenapi_driver.XenAPIDriver() - session = xenapi._session - - sr_ref = vm_utils.safe_find_sr(session) - destroyed = vm_utils.destroy_cached_images( - session, sr_ref, all_cached=CONF.all_cached, - dry_run=CONF.dry_run) - - if '--verbose' in sys.argv: - print '\n'.join(destroyed) - - print "Destroyed %d cached VDIs" % len(destroyed) - - -if __name__ == "__main__": - main() diff --git a/xenserver/stress_test.py b/xenserver/stress_test.py deleted file mode 100644 index d20652b..0000000 --- a/xenserver/stress_test.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -This script concurrently builds and migrates instances. This can be useful when -troubleshooting race-conditions in virt-layer code. - -Expects: - - novarc to be sourced in the environment - -Helper Script for Xen Dom0: - - # cat /tmp/destroy_cache_vdis - #!/bin/bash - xe vdi-list | grep "Glance Image" -C1 | grep "^uuid" | awk '{print $5}' | - xargs -n1 -I{} xe vdi-destroy uuid={} -""" -import argparse -import contextlib -import multiprocessing -import subprocess -import sys -import time - -DOM0_CLEANUP_SCRIPT = "/tmp/destroy_cache_vdis" - - -def run(cmd): - ret = subprocess.call(cmd, shell=True) - if ret != 0: - print >> sys.stderr, "Command exited non-zero: %s" % cmd - - -@contextlib.contextmanager -def server_built(server_name, image_name, flavor=1, cleanup=True): - run("nova boot --image=%(image_name)s --flavor=%(flavor)s" - " --poll %(server_name)s" % locals()) - try: - yield - finally: - if cleanup: - run("nova delete %(server_name)s" % locals()) - - -@contextlib.contextmanager -def snapshot_taken(server_name, snapshot_name, cleanup=True): - run("nova image-create %(server_name)s %(snapshot_name)s" - " --poll" % locals()) - try: - yield - finally: - if cleanup: - run("nova image-delete %(snapshot_name)s" % locals()) - - -def migrate_server(server_name): - run("nova migrate %(server_name)s --poll" % locals()) - - cmd = "nova list | grep %(server_name)s | awk '{print $6}'" % locals() - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - stdout, stderr = proc.communicate() - status = stdout.strip() - if status.upper() != 'VERIFY_RESIZE': - print >> sys.stderr, "Server %(server_name)s failed to rebuild"\ - % locals() - return False - - # Confirm the resize - run("nova resize-confirm %(server_name)s" % locals()) - return True - - -def test_migrate(context): - count, args = context - server_name = "server%d" % count - cleanup = args.cleanup - with server_built(server_name, args.image, cleanup=cleanup): - # Migrate A -> B - result = migrate_server(server_name) - if not result: - return False - - # Migrate B -> A - return migrate_server(server_name) - - -def rebuild_server(server_name, snapshot_name): - run("nova rebuild %(server_name)s %(snapshot_name)s --poll" % locals()) - - cmd = "nova list | grep %(server_name)s | awk '{print $6}'" % locals() - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - stdout, stderr = proc.communicate() - status = stdout.strip() - if status != 'ACTIVE': - print >> sys.stderr, "Server %(server_name)s failed to rebuild"\ - % locals() - return False - - return True - - -def test_rebuild(context): - count, args = context - server_name = "server%d" % count - snapshot_name = "snap%d" % count - cleanup = args.cleanup - with server_built(server_name, args.image, cleanup=cleanup): - with snapshot_taken(server_name, snapshot_name, cleanup=cleanup): - return rebuild_server(server_name, snapshot_name) - - -def _parse_args(): - parser = argparse.ArgumentParser( - description='Test Nova for Race Conditions.') - - parser.add_argument('tests', metavar='TESTS', type=str, nargs='*', - default=['rebuild', 'migrate'], - help='tests to run: [rebuilt|migrate]') - - parser.add_argument('-i', '--image', help="image to build from", - required=True) - parser.add_argument('-n', '--num-runs', type=int, help="number of runs", - default=1) - parser.add_argument('-c', '--concurrency', type=int, default=5, - help="number of concurrent processes") - parser.add_argument('--no-cleanup', action='store_false', dest="cleanup", - default=True) - parser.add_argument('-d', '--dom0-ips', - help="IP of dom0's to run cleanup script") - - return parser.parse_args() - - -def main(): - dom0_cleanup_script = DOM0_CLEANUP_SCRIPT - args = _parse_args() - - if args.dom0_ips: - dom0_ips = args.dom0_ips.split(',') - else: - dom0_ips = [] - - start_time = time.time() - batch_size = min(args.num_runs, args.concurrency) - pool = multiprocessing.Pool(processes=args.concurrency) - - results = [] - for test in args.tests: - test_func = globals().get("test_%s" % test) - if not test_func: - print >> sys.stderr, "test '%s' not found" % test - sys.exit(1) - - contexts = [(x, args) for x in range(args.num_runs)] - - try: - results += pool.map(test_func, contexts) - finally: - if args.cleanup: - for dom0_ip in dom0_ips: - run("ssh root@%(dom0_ip)s %(dom0_cleanup_script)s" - % locals()) - - success = all(results) - result = "SUCCESS" if success else "FAILED" - - duration = time.time() - start_time - print "%s, finished in %.2f secs" % (result, duration) - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/xenserver/vdi_chain_cleanup.py b/xenserver/vdi_chain_cleanup.py deleted file mode 100644 index b2baca0..0000000 --- a/xenserver/vdi_chain_cleanup.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2012 OpenStack Foundation -# -# 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. - -""" -This script is designed to cleanup any VHDs (and their descendents) which have -a bad parent pointer. - -The script needs to be run in the dom0 of the affected host. - -The available actions are: - - - print: display the filenames of the affected VHDs - - delete: remove the affected VHDs - - move: move the affected VHDs out of the SR into another directory -""" -import glob -import os -import subprocess -import sys - - -class ExecutionFailed(Exception): - def __init__(self, returncode, stdout, stderr, max_stream_length=32): - self.returncode = returncode - self.stdout = stdout[:max_stream_length] - self.stderr = stderr[:max_stream_length] - self.max_stream_length = max_stream_length - - def __repr__(self): - return "" % ( - self.returncode, self.stdout, self.stderr) - - __str__ = __repr__ - - -def execute(cmd, ok_exit_codes=None): - if ok_exit_codes is None: - ok_exit_codes = [0] - - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - (stdout, stderr) = proc.communicate() - - if proc.returncode not in ok_exit_codes: - raise ExecutionFailed(proc.returncode, stdout, stderr) - - return proc.returncode, stdout, stderr - - -def usage(): - print "usage: %s " % sys.argv[0] - sys.exit(1) - - -def main(): - if len(sys.argv) < 3: - usage() - - sr_path = sys.argv[1] - action = sys.argv[2] - - if action not in ('print', 'delete', 'move'): - usage() - - if action == 'move': - if len(sys.argv) < 4: - print "error: must specify where to move bad VHDs" - sys.exit(1) - - bad_vhd_path = sys.argv[3] - if not os.path.exists(bad_vhd_path): - os.makedirs(bad_vhd_path) - - bad_leaves = [] - descendents = {} - - for fname in glob.glob(os.path.join(sr_path, "*.vhd")): - (returncode, stdout, stderr) = execute( - ['vhd-util', 'query', '-n', fname, '-p'], ok_exit_codes=[0, 22]) - - stdout = stdout.strip() - - if stdout.endswith('.vhd'): - try: - descendents[stdout].append(fname) - except KeyError: - descendents[stdout] = [fname] - elif 'query failed' in stdout: - bad_leaves.append(fname) - - def walk_vhds(root): - yield root - if root in descendents: - for child in descendents[root]: - for vhd in walk_vhds(child): - yield vhd - - for bad_leaf in bad_leaves: - for bad_vhd in walk_vhds(bad_leaf): - print bad_vhd - if action == "print": - pass - elif action == "delete": - os.unlink(bad_vhd) - elif action == "move": - new_path = os.path.join(bad_vhd_path, - os.path.basename(bad_vhd)) - os.rename(bad_vhd, new_path) - else: - raise Exception("invalid action %s" % action) - - -if __name__ == '__main__': - main() diff --git a/xenserver/vm_vdi_cleaner.py b/xenserver/vm_vdi_cleaner.py deleted file mode 100755 index d2137b8..0000000 --- a/xenserver/vm_vdi_cleaner.py +++ /dev/null @@ -1,329 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2011 OpenStack Foundation -# -# 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. - -"""vm_vdi_cleaner.py - List or clean orphaned VDIs/instances on XenServer.""" - -import doctest -import os -import sys - -from oslo.config import cfg -import XenAPI - - -possible_topdir = os.getcwd() -if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): - sys.path.insert(0, possible_topdir) - - -from nova import context -from nova import db -from nova import exception -from nova.openstack.common import timeutils -from nova.virt import virtapi -from nova.virt.xenapi import driver as xenapi_driver - -cleaner_opts = [ - cfg.IntOpt('zombie_instance_updated_at_window', - default=172800, - help='Number of seconds zombie instances are cleaned up.'), -] - -cli_opt = cfg.StrOpt('command', - default=None, - help='Cleaner command') - -CONF = cfg.CONF -CONF.register_opts(cleaner_opts) -CONF.register_cli_opt(cli_opt) -CONF.import_opt('verbose', 'nova.openstack.common.log') -CONF.import_opt("resize_confirm_window", "nova.compute.manager") - - -ALLOWED_COMMANDS = ["list-vdis", "clean-vdis", "list-instances", - "clean-instances", "test"] - - -def call_xenapi(xenapi, method, *args): - """Make a call to xapi.""" - return xenapi._session.call_xenapi(method, *args) - - -def find_orphaned_instances(xenapi): - """Find and return a list of orphaned instances.""" - ctxt = context.get_admin_context(read_deleted="only") - - orphaned_instances = [] - - for vm_ref, vm_rec in _get_applicable_vm_recs(xenapi): - try: - uuid = vm_rec['other_config']['nova_uuid'] - instance = db.api.instance_get_by_uuid(ctxt, uuid) - except (KeyError, exception.InstanceNotFound): - # NOTE(jk0): Err on the side of caution here. If we don't know - # anything about the particular instance, ignore it. - print_xen_object("INFO: Ignoring VM", vm_rec, indent_level=0) - continue - - # NOTE(jk0): This would be triggered if a VM was deleted but the - # actual deletion process failed somewhere along the line. - is_active_and_deleting = (instance.vm_state == "active" and - instance.task_state == "deleting") - - # NOTE(jk0): A zombie VM is an instance that is not active and hasn't - # been updated in over the specified period. - is_zombie_vm = (instance.vm_state != "active" - and timeutils.is_older_than(instance.updated_at, - CONF.zombie_instance_updated_at_window)) - - if is_active_and_deleting or is_zombie_vm: - orphaned_instances.append((vm_ref, vm_rec, instance)) - - return orphaned_instances - - -def cleanup_instance(xenapi, instance, vm_ref, vm_rec): - """Delete orphaned instances.""" - xenapi._vmops._destroy(instance, vm_ref) - - -def _get_applicable_vm_recs(xenapi): - """An 'applicable' VM is one that is not a template and not the control - domain. - """ - for vm_ref in call_xenapi(xenapi, 'VM.get_all'): - try: - vm_rec = call_xenapi(xenapi, 'VM.get_record', vm_ref) - except XenAPI.Failure, e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - - if vm_rec["is_a_template"] or vm_rec["is_control_domain"]: - continue - yield vm_ref, vm_rec - - -def print_xen_object(obj_type, obj, indent_level=0, spaces_per_indent=4): - """Pretty-print a Xen object. - - Looks like: - - VM (abcd-abcd-abcd): 'name label here' - """ - if not CONF.verbose: - return - uuid = obj["uuid"] - try: - name_label = obj["name_label"] - except KeyError: - name_label = "" - msg = "%(obj_type)s (%(uuid)s) '%(name_label)s'" % locals() - indent = " " * spaces_per_indent * indent_level - print "".join([indent, msg]) - - -def _find_vdis_connected_to_vm(xenapi, connected_vdi_uuids): - """Find VDIs which are connected to VBDs which are connected to VMs.""" - def _is_null_ref(ref): - return ref == "OpaqueRef:NULL" - - def _add_vdi_and_parents_to_connected(vdi_rec, indent_level): - indent_level += 1 - - vdi_and_parent_uuids = [] - cur_vdi_rec = vdi_rec - while True: - cur_vdi_uuid = cur_vdi_rec["uuid"] - print_xen_object("VDI", vdi_rec, indent_level=indent_level) - connected_vdi_uuids.add(cur_vdi_uuid) - vdi_and_parent_uuids.append(cur_vdi_uuid) - - try: - parent_vdi_uuid = vdi_rec["sm_config"]["vhd-parent"] - except KeyError: - parent_vdi_uuid = None - - # NOTE(sirp): VDI's can have themselves as a parent?! - if parent_vdi_uuid and parent_vdi_uuid != cur_vdi_uuid: - indent_level += 1 - cur_vdi_ref = call_xenapi(xenapi, 'VDI.get_by_uuid', - parent_vdi_uuid) - try: - cur_vdi_rec = call_xenapi(xenapi, 'VDI.get_record', - cur_vdi_ref) - except XenAPI.Failure, e: - if e.details[0] != 'HANDLE_INVALID': - raise - break - else: - break - - for vm_ref, vm_rec in _get_applicable_vm_recs(xenapi): - indent_level = 0 - print_xen_object("VM", vm_rec, indent_level=indent_level) - - vbd_refs = vm_rec["VBDs"] - for vbd_ref in vbd_refs: - try: - vbd_rec = call_xenapi(xenapi, 'VBD.get_record', vbd_ref) - except XenAPI.Failure, e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - - indent_level = 1 - print_xen_object("VBD", vbd_rec, indent_level=indent_level) - - vbd_vdi_ref = vbd_rec["VDI"] - - if _is_null_ref(vbd_vdi_ref): - continue - - try: - vdi_rec = call_xenapi(xenapi, 'VDI.get_record', vbd_vdi_ref) - except XenAPI.Failure, e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - - _add_vdi_and_parents_to_connected(vdi_rec, indent_level) - - -def _find_all_vdis_and_system_vdis(xenapi, all_vdi_uuids, connected_vdi_uuids): - """Collects all VDIs and adds system VDIs to the connected set.""" - def _system_owned(vdi_rec): - vdi_name = vdi_rec["name_label"] - return (vdi_name.startswith("USB") or - vdi_name.endswith(".iso") or - vdi_rec["type"] == "system") - - for vdi_ref in call_xenapi(xenapi, 'VDI.get_all'): - try: - vdi_rec = call_xenapi(xenapi, 'VDI.get_record', vdi_ref) - except XenAPI.Failure, e: - if e.details[0] != 'HANDLE_INVALID': - raise - continue - vdi_uuid = vdi_rec["uuid"] - all_vdi_uuids.add(vdi_uuid) - - # System owned and non-managed VDIs should be considered 'connected' - # for our purposes. - if _system_owned(vdi_rec): - print_xen_object("SYSTEM VDI", vdi_rec, indent_level=0) - connected_vdi_uuids.add(vdi_uuid) - elif not vdi_rec["managed"]: - print_xen_object("UNMANAGED VDI", vdi_rec, indent_level=0) - connected_vdi_uuids.add(vdi_uuid) - - -def find_orphaned_vdi_uuids(xenapi): - """Walk VM -> VBD -> VDI change and accumulate connected VDIs.""" - connected_vdi_uuids = set() - - _find_vdis_connected_to_vm(xenapi, connected_vdi_uuids) - - all_vdi_uuids = set() - _find_all_vdis_and_system_vdis(xenapi, all_vdi_uuids, connected_vdi_uuids) - - orphaned_vdi_uuids = all_vdi_uuids - connected_vdi_uuids - return orphaned_vdi_uuids - - -def list_orphaned_vdis(vdi_uuids): - """List orphaned VDIs.""" - for vdi_uuid in vdi_uuids: - if CONF.verbose: - print "ORPHANED VDI (%s)" % vdi_uuid - else: - print vdi_uuid - - -def clean_orphaned_vdis(xenapi, vdi_uuids): - """Clean orphaned VDIs.""" - for vdi_uuid in vdi_uuids: - if CONF.verbose: - print "CLEANING VDI (%s)" % vdi_uuid - - vdi_ref = call_xenapi(xenapi, 'VDI.get_by_uuid', vdi_uuid) - try: - call_xenapi(xenapi, 'VDI.destroy', vdi_ref) - except XenAPI.Failure, exc: - print >> sys.stderr, "Skipping %s: %s" % (vdi_uuid, exc) - - -def list_orphaned_instances(orphaned_instances): - """List orphaned instances.""" - for vm_ref, vm_rec, orphaned_instance in orphaned_instances: - if CONF.verbose: - print "ORPHANED INSTANCE (%s)" % orphaned_instance.name - else: - print orphaned_instance.name - - -def clean_orphaned_instances(xenapi, orphaned_instances): - """Clean orphaned instances.""" - for vm_ref, vm_rec, instance in orphaned_instances: - if CONF.verbose: - print "CLEANING INSTANCE (%s)" % instance.name - - cleanup_instance(xenapi, instance, vm_ref, vm_rec) - - -def main(): - """Main loop.""" - args = CONF(args=sys.argv[1:], usage='%(prog)s [options] --command={' + - '|'.join(ALLOWED_COMMANDS) + '}') - - command = CONF.command - if not command or command not in ALLOWED_COMMANDS: - CONF.print_usage() - sys.exit(1) - - if CONF.zombie_instance_updated_at_window < CONF.resize_confirm_window: - raise Exception("`zombie_instance_updated_at_window` has to be longer" - " than `resize_confirm_window`.") - - # NOTE(blamar) This tool does not require DB access, so passing in the - # 'abstract' VirtAPI class is acceptable - xenapi = xenapi_driver.XenAPIDriver(virtapi.VirtAPI()) - - if command == "list-vdis": - if CONF.verbose: - print "Connected VDIs:\n" - orphaned_vdi_uuids = find_orphaned_vdi_uuids(xenapi) - if CONF.verbose: - print "\nOrphaned VDIs:\n" - list_orphaned_vdis(orphaned_vdi_uuids) - elif command == "clean-vdis": - orphaned_vdi_uuids = find_orphaned_vdi_uuids(xenapi) - clean_orphaned_vdis(xenapi, orphaned_vdi_uuids) - elif command == "list-instances": - orphaned_instances = find_orphaned_instances(xenapi) - list_orphaned_instances(orphaned_instances) - elif command == "clean-instances": - orphaned_instances = find_orphaned_instances(xenapi) - clean_orphaned_instances(xenapi, orphaned_instances) - elif command == "test": - doctest.testmod() - else: - print "Unknown command '%s'" % command - sys.exit(1) - - -if __name__ == "__main__": - main()