Make hacking a flake8 plugin.

This commit is contained in:
Monty Taylor 2013-03-18 12:18:09 -07:00
parent db1d1959af
commit 4e90f08824
51 changed files with 1961 additions and 2764 deletions

30
.gitignore vendored Normal file
View File

@ -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

4
.gitreview Normal file
View File

@ -0,0 +1,4 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack-dev/hacking.git

4
.testr.conf Normal file
View File

@ -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

17
CONTRIBUTING.rst Normal file
View File

@ -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

176
LICENSE Normal file
View File

@ -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.

7
MANIFEST.in Normal file
View File

@ -0,0 +1,7 @@
include AUTHORS
include ChangeLog
include README.rst
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

304
README.rst Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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=<None>' % 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:])

View File

@ -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 <lzyeval@gmail.com>
#
# 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

View File

@ -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 = ["<mysql|postgres>", "<orig-branch:orig-version>",
"<new-branch:new-version>"]
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()

View File

View File

@ -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 %}
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
{%- block sidebarlogo %}
{%- if logo %}
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
</a></p>
{%- endif %}
{%- endblock %}
{%- block sidebartoc %}
{%- if display_toc %}
<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
{{ toc }}
{%- endif %}
{%- endblock %}
{%- block sidebarrel %}
{%- if prev %}
<h4>{{ _('Previous topic') }}</h4>
<p class="topless"><a href="{{ prev.link|e }}"
title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
{%- endif %}
{%- if next %}
<h4>{{ _('Next topic') }}</h4>
<p class="topless"><a href="{{ next.link|e }}"
title="{{ _('next chapter') }}">{{ next.title }}</a></p>
{%- endif %}
{%- endblock %}
{%- block sidebarsourcelink %}
{%- if show_source and has_source and sourcename %}
<h3>{{ _('This Page') }}</h3>
<ul class="this-page-menu">
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
rel="nofollow">{{ _('Show Source') }}</a></li>
</ul>
{%- endif %}
{%- endblock %}
{%- if customsidebar %}
{% include customsidebar %}
{%- endif %}
{%- block sidebarsearch %}
{%- if pagename != "search" %}
<div id="searchbox" style="display: none">
<h3>{{ _('Quick search') }}</h3>
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" size="18" />
<input type="submit" value="{{ _('Go') }}" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
{{ _('Enter search terms or a module, class or function name.') }}
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
{%- endif %}
{%- endblock %}
</div>
</div>
{%- endif %}{% endif %}
{%- endmacro %}
{% block relbar1 %}{% endblock relbar1 %}
{% block header %}
<div id="header">
<h1 id="logo"><a href="http://www.openstack.org/">OpenStack</a></h1>
<ul id="navigation">
<li><a href="http://www.openstack.org/" title="Go to the Home page" class="link">Home</a></li>
<li><a href="http://www.openstack.org/projects/" title="Go to the OpenStack Projects page">Projects</a></li>
<li><a href="http://www.openstack.org/user-stories/" title="Go to the User Stories page" class="link">User Stories</a></li>
<li><a href="http://www.openstack.org/community/" title="Go to the Community page" class="link">Community</a></li>
<li><a href="http://www.openstack.org/blog/" title="Go to the OpenStack Blog">Blog</a></li>
<li><a href="http://wiki.openstack.org/" title="Go to the OpenStack Wiki">Wiki</a></li>
<li><a href="http://docs.openstack.org/" title="Go to OpenStack Documentation" class="current">Documentation</a></li>
</ul>
</div>
{% endblock %}

View File

@ -0,0 +1,4 @@
[theme]
inherit = basic
stylesheet = nature.css
pygments_style = tango

62
doc/source/conf.py Normal file
View File

@ -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}

21
doc/source/index.rst Normal file
View File

@ -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`

416
doc/source/static/basic.css Normal file
View File

@ -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;
}
}

View File

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,154 @@
(function($) {
$.fn.tweet = function(o){
var s = {
username: ["seaofclouds"], // [string] required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"]
list: null, //[string] optional name of list belonging to username
avatar_size: null, // [integer] height and width of avatar if displayed (48px max)
count: 3, // [integer] how many tweets to display?
intro_text: null, // [string] do you want text BEFORE your your tweets?
outro_text: null, // [string] do you want text AFTER your tweets?
join_text: null, // [string] optional text in between date and tweet, try setting to "auto"
auto_join_text_default: "i said,", // [string] auto text for non verb: "i said" bullocks
auto_join_text_ed: "i", // [string] auto text for past tense: "i" surfed
auto_join_text_ing: "i am", // [string] auto tense for present tense: "i was" surfing
auto_join_text_reply: "i replied to", // [string] auto tense for replies: "i replied to" @someone "with"
auto_join_text_url: "i was looking at", // [string] auto tense for urls: "i was looking at" http:...
loading_text: null, // [string] optional loading text, displayed while tweets load
query: null // [string] optional search query
};
if(o) $.extend(s, o);
$.fn.extend({
linkUrl: function() {
var returning = [];
var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
this.each(function() {
returning.push(this.replace(regexp,"<a href=\"$1\">$1</a>"));
});
return $(returning);
},
linkUser: function() {
var returning = [];
var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
this.each(function() {
returning.push(this.replace(regexp,"<a href=\"http://twitter.com/$1\">@$1</a>"));
});
return $(returning);
},
linkHash: function() {
var returning = [];
var regexp = / [\#]+([A-Za-z0-9-_]+)/gi;
this.each(function() {
returning.push(this.replace(regexp, ' <a href="http://search.twitter.com/search?q=&tag=$1&lang=all&from='+s.username.join("%2BOR%2B")+'">#$1</a>'));
});
return $(returning);
},
capAwesome: function() {
var returning = [];
this.each(function() {
returning.push(this.replace(/\b(awesome)\b/gi, '<span class="awesome">$1</span>'));
});
return $(returning);
},
capEpic: function() {
var returning = [];
this.each(function() {
returning.push(this.replace(/\b(epic)\b/gi, '<span class="epic">$1</span>'));
});
return $(returning);
},
makeHeart: function() {
var returning = [];
this.each(function() {
returning.push(this.replace(/(&lt;)+[3]/gi, "<tt class='heart'>&#x2665;</tt>"));
});
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 = $('<ul class="tweet_list">').appendTo(this);
var intro = '<p class="tweet_intro">'+s.intro_text+'</p>';
var outro = '<p class="tweet_outro">'+s.outro_text+'</p>';
var loading = $('<p class="loading">'+s.loading_text+'</p>');
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 = '<span class="tweet_join"> '+join_text+' </span>';
var join = ((s.join_text) ? join_template : ' ');
var avatar_template = '<a class="tweet_avatar" href="http://twitter.com/'+from_user+'"><img src="'+profile_image_url+'" height="'+s.avatar_size+'" width="'+s.avatar_size+'" alt="'+from_user+'\'s avatar" title="'+from_user+'\'s avatar" border="0"/></a>';
var avatar = (s.avatar_size ? avatar_template : '');
var date = '<a href="http://twitter.com/'+from_user+'/statuses/'+item.id+'" title="view tweet on twitter">'+relative_time(item.created_at)+'</a>';
var text = '<span class="tweet_text">' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ '</span>';
// until we create a template option, arrange the items below to alter a tweet's display.
list.append('<li>' + avatar + date + join + text + '</li>');
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);

View File

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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())

View File

@ -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 <your command>
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)

View File

@ -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')

View File

@ -1,199 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012, AT&T Labs, Yun Mao <yunmao@gmail.com>
# 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()

View File

@ -1,59 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2012-2013, AT&T Labs, Yun Mao <yunmao@gmail.com>
# 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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
d2to1
flake8
pbr

View File

@ -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"

48
setup.cfg Normal file
View File

@ -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

21
setup.py Executable file
View File

@ -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)

7
test-requirements.txt Normal file
View File

@ -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

View File

@ -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

31
tox.ini Normal file
View File

@ -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

View File

@ -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 && "$@"

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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 "<ExecutionFailed returncode=%s out='%s' stderr='%s'>" % (
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 <SR PATH> <print|delete|move>" % 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()

View File

@ -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()