@ -0,0 +1,12 @@ | |||
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](http://wiki.openstack.org/HowToContribute#If_you.27re_a_developer.2C_start_here:) | |||
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](http://wiki.openstack.org/GerritWorkflow). | |||
Pull requests submitted through GitHub will be ignored. | |||
Bugs should be filed [on Launchpad](https://bugs.launchpad.net/cinder), | |||
not in GitHub's issue tracker. |
@ -0,0 +1,275 @@ | |||
Cinder 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) | |||
- 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 make relative imports | |||
- 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 ``cinder.db.sqlalchemy.session`` module | |||
Example:: | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
{{stdlib imports in human alphabetical order}} | |||
\n | |||
{{third-party lib imports in human alphabetical order}} | |||
\n | |||
{{cinder 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 cinder.api.ec2 | |||
from cinder.api import openstack | |||
from cinder.auth import users | |||
from cinder.endpoint import cloud | |||
import cinder.flags | |||
from cinder 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 Cinder, please read cinder/testing/README.rst. | |||
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 OpenStack, LLC, 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 (it is recommended to keep the commit title | |||
under 50 chars). | |||
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 |
@ -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. | |||
@ -0,0 +1,6 @@ | |||
include AUTHORS | |||
include ChangeLog | |||
exclude .gitignore | |||
exclude .gitreview | |||
global-exclude *.pyc |
@ -0,0 +1,21 @@ | |||
The Choose Your Own Adventure README for Cinder | |||
=============================================== | |||
You have come across a storage service for an open cloud computing service. | |||
It has identified itself as "Cinder." It was abstracted from the Nova project. | |||
To monitor it from a distance: follow `@openstack <http://twitter.com/openstack>`_ on twitter. | |||
To tame it for use in your own cloud: read http://docs.openstack.org | |||
To study its anatomy: read http://cinder.openstack.org | |||
To dissect it in detail: visit http://github.com/openstack/cinder | |||
To taunt it with its weaknesses: use http://bugs.launchpad.net/cinder | |||
To watch it: http://jenkins.openstack.org | |||
To hack at it: read HACKING | |||
To cry over its pylint problems: http://jenkins.openstack.org/job/cinder-pylint/violations |
@ -0,0 +1,2 @@ | |||
[python: **.py] | |||
@ -0,0 +1,70 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# Copyright 2011 OpenStack, LLC | |||
# 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. | |||
"""Starter script for All cinder services. | |||
This script attempts to start all the cinder services in one process. Each | |||
service is started in its own greenthread. Please note that exceptions and | |||
sys.exit() on the starting of a service are logged and the script will | |||
continue attempting to launch the rest of the services. | |||
""" | |||
import eventlet | |||
eventlet.monkey_patch() | |||
import os | |||
import sys | |||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath( | |||
sys.argv[0]), os.pardir, os.pardir)) | |||
if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder import service | |||
from cinder import utils | |||
if __name__ == '__main__': | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
LOG = logging.getLogger('cinder.all') | |||
utils.monkey_patch() | |||
servers = [] | |||
# cinder-api | |||
try: | |||
servers.append(service.WSGIService('osapi_volume')) | |||
except (Exception, SystemExit): | |||
LOG.exception(_('Failed to load osapi_volume')) | |||
for binary in ['cinder-volume', 'cinder-scheduler']: | |||
try: | |||
servers.append(service.Service.create(binary=binary)) | |||
except (Exception, SystemExit): | |||
LOG.exception(_('Failed to load %s'), binary) | |||
service.serve(*servers) | |||
service.wait() |
@ -0,0 +1,52 @@ | |||
#!/usr/bin/env python | |||
# 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. | |||
"""Starter script for Cinder OS API.""" | |||
# NOTE(jdg): If we port over multi worker code from Nova | |||
# we'll need to set monkey_patch(os=False), unless | |||
# eventlet is updated/released to fix the root issue | |||
import eventlet | |||
eventlet.monkey_patch() | |||
import os | |||
import sys | |||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath( | |||
sys.argv[0]), os.pardir, os.pardir)) | |||
if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder import service | |||
from cinder import utils | |||
if __name__ == '__main__': | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
utils.monkey_patch() | |||
server = service.WSGIService('osapi_volume') | |||
service.serve(server) | |||
service.wait() |
@ -0,0 +1,50 @@ | |||
#!/usr/bin/env python | |||
# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. | |||
# 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. | |||
"""Starter script for Cinder Volume Backup.""" | |||
import os | |||
import sys | |||
import eventlet | |||
eventlet.monkey_patch() | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(possible_topdir, 'cinder', '__init__.py')): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder import service | |||
from cinder import utils | |||
if __name__ == '__main__': | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
utils.monkey_patch() | |||
launcher = service.ProcessLauncher() | |||
server = service.Service.create(binary='cinder-backup') | |||
launcher.launch_server(server) | |||
launcher.wait() |
@ -0,0 +1,76 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# Copyright (c) 2011 OpenStack, LLC. | |||
# 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. | |||
"""Admin/debug script to wipe rabbitMQ (AMQP) queues cinder uses. | |||
This can be used if you need to change durable options on queues, | |||
or to wipe all messages in the queue system if things are in a | |||
serious bad way. | |||
""" | |||
import datetime | |||
import os | |||
import sys | |||
import time | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'cinder', '__init__.py')): | |||
sys.path.insert(0, POSSIBLE_TOPDIR) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from oslo.config import cfg | |||
from cinder import context | |||
from cinder import exception | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder.openstack.common import rpc | |||
delete_exchange_opt = \ | |||
cfg.BoolOpt('delete_exchange', | |||
default=False, | |||
help='delete cinder exchange too.') | |||
FLAGS = flags.FLAGS | |||
FLAGS.register_cli_opt(delete_exchange_opt) | |||
def delete_exchange(exch): | |||
conn = rpc.create_connection() | |||
x = conn.get_channel() | |||
x.exchange_delete(exch) | |||
def delete_queues(queues): | |||
conn = rpc.create_connection() | |||
x = conn.get_channel() | |||
for q in queues: | |||
x.queue_delete(q) | |||
if __name__ == '__main__': | |||
args = flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
delete_queues(args[1:]) | |||
if FLAGS.delete_exchange: | |||
delete_exchange(FLAGS.control_exchange) |
@ -0,0 +1,820 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. | |||
# 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. | |||
# Interactive shell based on Django: | |||
# | |||
# Copyright (c) 2005, the Lawrence Journal-World | |||
# All rights reserved. | |||
# | |||
# Redistribution and use in source and binary forms, with or without | |||
# modification, are permitted provided that the following conditions are met: | |||
# | |||
# 1. Redistributions of source code must retain the above copyright notice, | |||
# this list of conditions and the following disclaimer. | |||
# | |||
# 2. Redistributions in binary form must reproduce the above copyright | |||
# notice, this list of conditions and the following disclaimer in the | |||
# documentation and/or other materials provided with the distribution. | |||
# | |||
# 3. Neither the name of Django nor the names of its contributors may be | |||
# used to endorse or promote products derived from this software without | |||
# specific prior written permission. | |||
# | |||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
""" | |||
CLI interface for cinder management. | |||
""" | |||
import os | |||
import sys | |||
import uuid | |||
from sqlalchemy import create_engine, MetaData, Table | |||
from sqlalchemy.ext.declarative import declarative_base | |||
from sqlalchemy.orm import sessionmaker | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'cinder', '__init__.py')): | |||
sys.path.insert(0, POSSIBLE_TOPDIR) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from oslo.config import cfg | |||
from cinder import context | |||
from cinder import db | |||
from cinder.db import migration | |||
from cinder import exception | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder.openstack.common import rpc | |||
from cinder.openstack.common import uuidutils | |||
from cinder import utils | |||
from cinder import version | |||
FLAGS = flags.FLAGS | |||
# Decorators for actions | |||
def args(*args, **kwargs): | |||
def _decorator(func): | |||
func.__dict__.setdefault('args', []).insert(0, (args, kwargs)) | |||
return func | |||
return _decorator | |||
def param2id(object_id): | |||
"""Helper function to convert various id types to internal id. | |||
args: [object_id], e.g. 'vol-0000000a' or 'volume-0000000a' or '10' | |||
""" | |||
if uuidutils.is_uuid_like(object_id): | |||
return object_id | |||
elif '-' in object_id: | |||
# FIXME(ja): mapping occurs in nova? | |||
pass | |||
else: | |||
return int(object_id) | |||
class ShellCommands(object): | |||
def bpython(self): | |||
"""Runs a bpython shell. | |||
Falls back to Ipython/python shell if unavailable""" | |||
self.run('bpython') | |||
def ipython(self): | |||
"""Runs an Ipython shell. | |||
Falls back to Python shell if unavailable""" | |||
self.run('ipython') | |||
def python(self): | |||
"""Runs a python shell. | |||
Falls back to Python shell if unavailable""" | |||
self.run('python') | |||
@args('--shell', dest="shell", | |||
metavar='<bpython|ipython|python>', | |||
help='Python shell') | |||
def run(self, shell=None): | |||
"""Runs a Python interactive interpreter.""" | |||
if not shell: | |||
shell = 'bpython' | |||
if shell == 'bpython': | |||
try: | |||
import bpython | |||
bpython.embed() | |||
except ImportError: | |||
shell = 'ipython' | |||
if shell == 'ipython': | |||
try: | |||
import IPython | |||
# Explicitly pass an empty list as arguments, because | |||
# otherwise IPython would use sys.argv from this script. | |||
shell = IPython.Shell.IPShell(argv=[]) | |||
shell.mainloop() | |||
except ImportError: | |||
shell = 'python' | |||
if shell == 'python': | |||
import code | |||
try: | |||
# Try activating rlcompleter, because it's handy. | |||
import readline | |||
except ImportError: | |||
pass | |||
else: | |||
# We don't have to wrap the following import in a 'try', | |||
# because we already know 'readline' was imported successfully. | |||
import rlcompleter | |||
readline.parse_and_bind("tab:complete") | |||
code.interact() | |||
@args('--path', required=True, help='Script path') | |||
def script(self, path): | |||
"""Runs the script from the specifed path with flags set properly. | |||
arguments: path""" | |||
exec(compile(open(path).read(), path, 'exec'), locals(), globals()) | |||
def _db_error(caught_exception): | |||
print caught_exception | |||
print _("The above error may show that the database has not " | |||
"been created.\nPlease create a database using " | |||
"'cinder-manage db sync' before running this command.") | |||
exit(1) | |||
class HostCommands(object): | |||
"""List hosts.""" | |||
@args('zone', nargs='?', default=None, | |||
help='Availability Zone (default: %(default)s)') | |||
def list(self, zone=None): | |||
"""Show a list of all physical hosts. Filter by zone. | |||
args: [zone]""" | |||
print "%-25s\t%-15s" % (_('host'), | |||
_('zone')) | |||
ctxt = context.get_admin_context() | |||
services = db.service_get_all(ctxt) | |||
if zone: | |||
services = [s for s in services if s['availability_zone'] == zone] | |||
hosts = [] | |||
for srv in services: | |||
if not [h for h in hosts if h['host'] == srv['host']]: | |||
hosts.append(srv) | |||
for h in hosts: | |||
print "%-25s\t%-15s" % (h['host'], h['availability_zone']) | |||
class DbCommands(object): | |||
"""Class for managing the database.""" | |||
def __init__(self): | |||
pass | |||
@args('version', nargs='?', default=None, | |||
help='Database version') | |||
def sync(self, version=None): | |||
"""Sync the database up to the most recent version.""" | |||
return migration.db_sync(version) | |||
def version(self): | |||
"""Print the current database version.""" | |||
print migration.db_version() | |||
class VersionCommands(object): | |||
"""Class for exposing the codebase version.""" | |||
def __init__(self): | |||
pass | |||
def list(self): | |||
print(version.version_string()) | |||
def __call__(self): | |||
self.list() | |||
class ImportCommands(object): | |||
"""Methods for importing Nova volumes to Cinder. | |||
EXPECTATIONS: | |||
These methods will do two things: | |||
1. Import relevant Nova DB info in to Cinder | |||
2. Import persistent tgt files from Nova to Cinder (see copy_tgt_files) | |||
If you're using VG's (local storage) for your backend YOU MUST install | |||
Cinder on the same node that you're migrating from. | |||
""" | |||
def __init__(self): | |||
pass | |||
def _map_table(self, table): | |||
class Mapper(declarative_base()): | |||
__table__ = table | |||
return Mapper | |||
def _open_session(self, con_info): | |||
# Note(jdg): The echo option below sets whether to dispaly db command | |||
# debug info. | |||
engine = create_engine(con_info, | |||
convert_unicode=True, | |||
echo=False) | |||
session = sessionmaker(bind=engine) | |||
return (session(), engine) | |||
def _backup_cinder_db(self): | |||
#First, dump the dest_db as a backup incase this goes wrong | |||
cinder_dump = utils.execute('mysqldump', 'cinder') | |||
if 'Dump completed on' in cinder_dump[0]: | |||
with open('./cinder_db_bkup.sql', 'w+') as fo: | |||
for line in cinder_dump: | |||
fo.write(line) | |||
else: | |||
raise exception.InvalidResults() | |||
def _import_db(self, src_db, dest_db, backup_db): | |||
# Remember order matters due to FK's | |||
table_list = ['sm_flavors', | |||
'sm_backend_config', | |||
'snapshots', | |||
'volume_types', | |||
'volumes', | |||
'iscsi_targets', | |||
'sm_volume', | |||
'volume_metadata', | |||
'volume_type_extra_specs'] | |||
quota_table_list = ['quota_classes', | |||
'quota_usages', | |||
'quotas', | |||
'reservations'] | |||
if backup_db > 0: | |||
if 'mysql:' not in dest_db: | |||
print (_('Sorry, only mysql backups are supported!')) | |||
raise exception.InvalidRequest() | |||
else: | |||
self._backup_cinder_db() | |||
(src, src_engine) = self._open_session(src_db) | |||
src_meta = MetaData(bind=src_engine) | |||
(dest, dest_engine) = self._open_session(dest_db) | |||
# First make sure nova is at Folsom | |||
table = Table('migrate_version', src_meta, autoload=True) | |||
if src.query(table).first().version < 132: | |||
print (_('ERROR: Specified Nova DB is not at a compatible ' | |||
'migration version!\nNova must be at Folsom or newer ' | |||
'to import into Cinder database.')) | |||
sys.exit(2) | |||
for table_name in table_list: | |||
print (_('Importing table %s...') % table_name) | |||
table = Table(table_name, src_meta, autoload=True) | |||
new_row = self._map_table(table) | |||
columns = table.columns.keys() | |||
for row in src.query(table).all(): | |||
data = dict([(str(column), getattr(row, column)) | |||
for column in columns]) | |||
dest.add(new_row(**data)) | |||
dest.commit() | |||
for table_name in quota_table_list: | |||
print (_('Importing table %s...') % table_name) | |||
table = Table(table_name, src_meta, autoload=True) | |||
new_row = self._map_table(table) | |||
columns = table.columns.keys() | |||
for row in src.query(table).all(): | |||
if row.resource == 'gigabytes' or row.resource == 'volumes': | |||
data = dict([(str(column), getattr(row, column)) | |||
for column in columns]) | |||
dest.add(new_row(**data)) | |||
dest.commit() | |||
@args('src', metavar='<Nova DB>', | |||
help='db-engine://db_user[:passwd]@db_host[:port]\t\t' | |||
'example: mysql://root:secrete@192.168.137.1') | |||
@args('dest', metavar='<Cinder DB>', | |||
help='db-engine://db_user[:passwd]@db_host[:port]\t\t' | |||
'example: mysql://root:secrete@192.168.137.1') | |||
@args('--backup', metavar='<0|1>', choices=[0, 1], default=1, | |||
help='Perform mysqldump of cinder db before writing to it' | |||
' (default: %(default)d)') | |||
def import_db(self, src_db, dest_db, backup_db=1): | |||
"""Import relevant volume DB entries from Nova into Cinder. | |||
NOTE: | |||
Your Cinder DB should be clean WRT volume entries. | |||
NOTE: | |||
We take an sqldump of the cinder DB before mods | |||
If you're not using mysql, set backup_db=0 | |||
and create your own backup. | |||
""" | |||
src_db = '%s/nova' % src_db | |||
dest_db = '%s/cinder' % dest_db | |||
self._import_db(src_db, dest_db, backup_db) | |||
@args('src', | |||
help='e.g. (login@src_host:]/opt/stack/nova/volumes/)') | |||
@args('dest', nargs='?', default=None, | |||
help='e.g. (login@src_host:/opt/stack/cinder/volumes/) ' | |||
'optional, if emitted, \'volume_dir\' in config will be used') | |||
def copy_ptgt_files(self, src_tgts, dest_tgts=None): | |||
"""Copy persistent scsi tgt files from nova to cinder. | |||
Default destination is FLAGS.volume_dir or state_path/volumes/ | |||
PREREQUISITES: | |||
Persistent tgts were introduced in Folsom. If you're running | |||
Essex or other release, this script is unnecessary. | |||
NOTE: | |||
If you're using local VG's and LVM for your nova volume backend | |||
there's no point in copying these files over. Leave them on | |||
your Nova system as they won't do any good here. | |||
""" | |||
if dest_tgts is None: | |||
try: | |||
dest_tgts = FLAGS.volumes_dir | |||
except Exception: | |||
dest_tgts = '%s/volumes' % FLAGS.state_path | |||
utils.execute('rsync', '-avz', src_tgts, dest_tgts) | |||
class VolumeCommands(object): | |||
"""Methods for dealing with a cloud in an odd state.""" | |||
@args('volume_id', | |||
help='Volume ID to be deleted') | |||
def delete(self, volume_id): | |||
"""Delete a volume, bypassing the check that it | |||
must be available.""" | |||
ctxt = context.get_admin_context() | |||
volume = db.volume_get(ctxt, param2id(volume_id)) | |||
host = volume['host'] | |||
if not host: | |||
print "Volume not yet assigned to host." | |||
print "Deleting volume from database and skipping rpc." | |||
db.volume_destroy(ctxt, param2id(volume_id)) | |||
return | |||
if volume['status'] == 'in-use': | |||
print "Volume is in-use." | |||
print "Detach volume from instance and then try again." | |||
return | |||
rpc.cast(ctxt, | |||
rpc.queue_get_for(ctxt, FLAGS.volume_topic, host), | |||
{"method": "delete_volume", | |||
"args": {"volume_id": volume['id']}}) | |||
@args('volume_id', | |||
help='Volume ID to be reattached') | |||
def reattach(self, volume_id): | |||
"""Re-attach a volume that has previously been attached | |||
to an instance. Typically called after a compute host | |||
has been rebooted.""" | |||
ctxt = context.get_admin_context() | |||
volume = db.volume_get(ctxt, param2id(volume_id)) | |||
if not volume['instance_id']: | |||
print "volume is not attached to an instance" | |||
return | |||
instance = db.instance_get(ctxt, volume['instance_id']) | |||
host = instance['host'] | |||
rpc.cast(ctxt, | |||
rpc.queue_get_for(ctxt, FLAGS.compute_topic, host), | |||
{"method": "attach_volume", | |||
"args": {"instance_id": instance['id'], | |||
"volume_id": volume['id'], | |||
"mountpoint": volume['mountpoint']}}) | |||
class StorageManagerCommands(object): | |||
"""Class for mangaging Storage Backends and Flavors.""" | |||
@args('flavor', nargs='?', | |||
help='flavor to be listed') | |||
def flavor_list(self, flavor=None): | |||
ctxt = context.get_admin_context() | |||
try: | |||
if flavor is None: | |||
flavors = db.sm_flavor_get_all(ctxt) | |||
else: | |||
flavors = db.sm_flavor_get(ctxt, flavor) | |||
except exception.NotFound as ex: | |||
print "error: %s" % ex | |||
sys.exit(2) | |||
print "%-18s\t%-20s\t%s" % (_('id'), | |||
_('Label'), | |||
_('Description')) | |||
for flav in flavors: | |||
print "%-18s\t%-20s\t%s" % ( | |||
flav['id'], | |||
flav['label'], | |||
flav['description']) | |||
@args('label', help='flavor label') | |||
@args('desc', help='flavor description') | |||
def flavor_create(self, label, desc): | |||
# TODO(renukaapte) flavor name must be unique | |||
try: | |||
db.sm_flavor_create(context.get_admin_context(), | |||
dict(label=label, | |||
description=desc)) | |||
except exception.DBError, e: | |||
_db_error(e) | |||
@args('label', help='label of flavor to be deleted') | |||
def flavor_delete(self, label): | |||
try: | |||
db.sm_flavor_delete(context.get_admin_context(), label) | |||
except exception.DBError, e: | |||
_db_error(e) | |||
def _splitfun(self, item): | |||
i = item.split("=") | |||
return i[0:2] | |||
@args('backend_conf_id', nargs='?', default=None) | |||
def backend_list(self, backend_conf_id=None): | |||
ctxt = context.get_admin_context() | |||
try: | |||
if backend_conf_id is None: | |||
backends = db.sm_backend_conf_get_all(ctxt) | |||
else: | |||
backends = db.sm_backend_conf_get(ctxt, backend_conf_id) | |||
except exception.NotFound as ex: | |||
print "error: %s" % ex | |||
sys.exit(2) | |||
print "%-5s\t%-10s\t%-40s\t%-10s\t%s" % (_('id'), | |||
_('Flavor id'), | |||
_('SR UUID'), | |||
_('SR Type'), | |||
_('Config Parameters'),) | |||
for b in backends: | |||
print "%-5s\t%-10s\t%-40s\t%-10s\t%s" % (b['id'], | |||
b['flavor_id'], | |||
b['sr_uuid'], | |||
b['sr_type'], | |||
b['config_params'],) | |||
@args('flavor_label') | |||
@args('sr_type') | |||
@args('args', nargs='*') | |||
def backend_add(self, flavor_label, sr_type, *args): | |||
# TODO(renukaapte) Add backend_introduce. | |||
ctxt = context.get_admin_context() | |||
params = dict(map(self._splitfun, args)) | |||
sr_uuid = uuid.uuid4() | |||
if flavor_label is None: | |||
print "error: backend needs to be associated with flavor" | |||
sys.exit(2) | |||
try: | |||
flavors = db.sm_flavor_get(ctxt, flavor_label) | |||
except exception.NotFound as ex: | |||
print "error: %s" % ex | |||
sys.exit(2) | |||
config_params = " ".join( | |||
['%s=%s' % (key, params[key]) for key in params]) | |||
if 'sr_uuid' in params: | |||
sr_uuid = params['sr_uuid'] | |||
try: | |||
backend = db.sm_backend_conf_get_by_sr(ctxt, sr_uuid) | |||
except exception.DBError, e: | |||
_db_error(e) | |||
if backend: | |||
print 'Backend config found. Would you like to recreate this?' | |||
print '(WARNING:Recreating will destroy all VDIs on backend!!)' | |||
c = raw_input('Proceed? (y/n) ') | |||
if c == 'y' or c == 'Y': | |||
try: | |||
db.sm_backend_conf_update( | |||
ctxt, backend['id'], | |||
dict(created=False, | |||
flavor_id=flavors['id'], | |||
sr_type=sr_type, | |||
config_params=config_params)) | |||
except exception.DBError, e: | |||
_db_error(e) | |||
return | |||
else: | |||
print 'Backend config not found. Would you like to create it?' | |||
print '(WARNING: Creating will destroy all data on backend!!!)' | |||
c = raw_input('Proceed? (y/n) ') | |||
if c == 'y' or c == 'Y': | |||
try: | |||
db.sm_backend_conf_create(ctxt, | |||
dict(flavor_id=flavors['id'], | |||
sr_uuid=sr_uuid, | |||
sr_type=sr_type, | |||
config_params=config_params)) | |||
except exception.DBError, e: | |||
_db_error(e) | |||
@args('backend_conf_id') | |||
def backend_remove(self, backend_conf_id): | |||
try: | |||
db.sm_backend_conf_delete(context.get_admin_context(), | |||
backend_conf_id) | |||
except exception.DBError, e: | |||
_db_error(e) | |||
class ConfigCommands(object): | |||
"""Class for exposing the flags defined by flag_file(s).""" | |||
def __init__(self): | |||
pass | |||
def list(self): | |||
for key, value in FLAGS.iteritems(): | |||
if value is not None: | |||
print '%s = %s' % (key, value) | |||
class GetLogCommands(object): | |||
"""Get logging information.""" | |||
def errors(self): | |||
"""Get all of the errors from the log files.""" | |||
error_found = 0 | |||
if FLAGS.log_dir: | |||
logs = [x for x in os.listdir(FLAGS.log_dir) if x.endswith('.log')] | |||
for file in logs: | |||
log_file = os.path.join(FLAGS.log_dir, file) | |||
lines = [line.strip() for line in open(log_file, "r")] | |||
lines.reverse() | |||
print_name = 0 | |||
for index, line in enumerate(lines): | |||
if line.find(" ERROR ") > 0: | |||
error_found += 1 | |||
if print_name == 0: | |||
print log_file + ":-" | |||
print_name = 1 | |||
print "Line %d : %s" % (len(lines) - index, line) | |||
if error_found == 0: | |||
print "No errors in logfiles!" | |||
@args('num_entries', nargs='?', type=int, default=10, | |||
help='Number of entries to list (default: %(default)d)') | |||
def syslog(self, num_entries=10): | |||
"""Get <num_entries> of the cinder syslog events.""" | |||
entries = int(num_entries) | |||
count = 0 | |||
log_file = '' | |||
if os.path.exists('/var/log/syslog'): | |||
log_file = '/var/log/syslog' | |||
elif os.path.exists('/var/log/messages'): | |||
log_file = '/var/log/messages' | |||
else: | |||
print "Unable to find system log file!" | |||
sys.exit(1) | |||
lines = [line.strip() for line in open(log_file, "r")] | |||
lines.reverse() | |||
print "Last %s cinder syslog entries:-" % (entries) | |||
for line in lines: | |||
if line.find("cinder") > 0: | |||
count += 1 | |||
print "%s" % (line) | |||
if count == entries: | |||
break | |||
if count == 0: | |||
print "No cinder entries in syslog!" | |||
class BackupCommands(object): | |||
"""Methods for managing backups.""" | |||
def list(self): | |||
"""List all backups (including ones in progress) and the host | |||
on which the backup operation is running.""" | |||
ctxt = context.get_admin_context() | |||
backups = db.backup_get_all(ctxt) | |||
hdr = "%-32s\t%-32s\t%-32s\t%-24s\t%-24s\t%-12s\t%-12s\t%-12s\t%-12s" | |||
print hdr % (_('ID'), | |||
_('User ID'), | |||
_('Project ID'), | |||
_('Host'), | |||
_('Name'), | |||
_('Container'), | |||
_('Status'), | |||
_('Size'), | |||
_('Object Count')) | |||
res = "%-32s\t%-32s\t%-32s\t%-24s\t%-24s\t%-12s\t%-12s\t%-12d\t%-12d" | |||
for backup in backups: | |||
object_count = 0 | |||
if backup['object_count'] is not None: | |||
object_count = backup['object_count'] | |||
print res % (backup['id'], | |||
backup['user_id'], | |||
backup['project_id'], | |||
backup['host'], | |||
backup['display_name'], | |||
backup['container'], | |||
backup['status'], | |||
backup['size'], | |||
object_count) | |||
class ServiceCommands(object): | |||
"""Methods for managing services.""" | |||
def list(self): | |||
"""Show a list of all cinder services.""" | |||
ctxt = context.get_admin_context() | |||
services = db.service_get_all(ctxt) | |||
print_format = "%-16s %-36s %-16s %-10s %-5s %-10s" | |||
print print_format % ( | |||
_('Binary'), | |||
_('Host'), | |||
_('Zone'), | |||
_('Status'), | |||
_('State'), | |||
_('Updated At')) | |||
for svc in services: | |||
alive = utils.service_is_up(svc) | |||
art = ":-)" if alive else "XXX" | |||
status = 'enabled' | |||
if svc['disabled']: | |||
status = 'disabled' | |||
print print_format % (svc['binary'], svc['host'].partition('.')[0], | |||
svc['availability_zone'], status, art, | |||
svc['updated_at']) | |||
CATEGORIES = { | |||
'backup': BackupCommands, | |||
'config': ConfigCommands, | |||
'db': DbCommands, | |||
'host': HostCommands, | |||
'logs': GetLogCommands, | |||
'service': ServiceCommands, | |||
'shell': ShellCommands, | |||
'sm': StorageManagerCommands, | |||
'version': VersionCommands, | |||
'volume': VolumeCommands, | |||
'migrate': ImportCommands, | |||
} | |||
def methods_of(obj): | |||
"""Get all callable methods of an object that don't start with underscore | |||
returns a list of tuples of the form (method_name, method)""" | |||
result = [] | |||
for i in dir(obj): | |||
if callable(getattr(obj, i)) and not i.startswith('_'): | |||
result.append((i, getattr(obj, i))) | |||
return result | |||
def add_command_parsers(subparsers): | |||
for category in CATEGORIES: | |||
command_object = CATEGORIES[category]() | |||
parser = subparsers.add_parser(category) | |||
parser.set_defaults(command_object=command_object) | |||
category_subparsers = parser.add_subparsers(dest='action') | |||
for (action, action_fn) in methods_of(command_object): | |||
parser = category_subparsers.add_parser(action) | |||
action_kwargs = [] | |||
for args, kwargs in getattr(action_fn, 'args', []): | |||
parser.add_argument(*args, **kwargs) | |||
parser.set_defaults(action_fn=action_fn) | |||
parser.set_defaults(action_kwargs=action_kwargs) | |||
category_opt = cfg.SubCommandOpt('category', | |||
title='Command categories', | |||
handler=add_command_parsers) | |||
def get_arg_string(args): | |||
arg = None | |||
if args[0] == '-': | |||
# (Note)zhiteng: args starts with FLAGS.oparser.prefix_chars | |||
# is optional args. Notice that cfg module takes care of | |||
# actual ArgParser so prefix_chars is always '-'. | |||
if args[1] == '-': | |||
# This is long optional arg | |||
arg = args[2:] | |||
else: | |||
arg = args[3:] | |||
else: | |||
arg = args | |||
return arg | |||
def fetch_func_args(func): | |||
fn_args = [] | |||
for args, kwargs in getattr(func, 'args', []): | |||
arg = get_arg_string(args[0]) | |||
fn_args.append(getattr(FLAGS.category, arg)) | |||
return fn_args | |||
def main(): | |||
"""Parse options and call the appropriate class/method.""" | |||
FLAGS.register_cli_opt(category_opt) | |||
script_name = sys.argv[0] | |||
if len(sys.argv) < 2: | |||
print(_("\nOpenStack Cinder version: %(version)s\n") % | |||
{'version': version.version_string()}) | |||
print script_name + " category action [<args>]" | |||
print _("Available categories:") | |||
for category in CATEGORIES: | |||
print "\t%s" % category | |||
sys.exit(2) | |||
try: | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
except cfg.ConfigFilesNotFoundError: | |||
cfgfile = FLAGS.config_file[-1] if FLAGS.config_file else None | |||
if cfgfile and not os.access(cfgfile, os.R_OK): | |||
st = os.stat(cfgfile) | |||
print _("Could not read %s. Re-running with sudo") % cfgfile | |||
try: | |||
os.execvp('sudo', ['sudo', '-u', '#%s' % st.st_uid] + sys.argv) | |||
except Exception: | |||
print _('sudo failed, continuing as if nothing happened') | |||
print _('Please re-run cinder-manage as root.') | |||
sys.exit(2) | |||
fn = FLAGS.category.action_fn | |||
fn_args = fetch_func_args(fn) | |||
fn(*fn_args) | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,128 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# Copyright (c) 2011 OpenStack Foundation. | |||
# 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. | |||
"""Root wrapper for OpenStack services | |||
Filters which commands a service is allowed to run as another user. | |||
To use this with cinder, you should set the following in | |||
cinder.conf: | |||
rootwrap_config=/etc/cinder/rootwrap.conf | |||
You also need to let the cinder user run cinder-rootwrap | |||
as root in sudoers: | |||
cinder ALL = (root) NOPASSWD: /usr/bin/cinder-rootwrap | |||
/etc/cinder/rootwrap.conf * | |||
Service packaging should deploy .filters files only on nodes where | |||
they are needed, to avoid allowing more than is necessary. | |||
""" | |||
import ConfigParser | |||
import logging | |||
import os | |||
import pwd | |||
import signal | |||
import subprocess | |||
import sys | |||
RC_UNAUTHORIZED = 99 | |||
RC_NOCOMMAND = 98 | |||
RC_BADCONFIG = 97 | |||
RC_NOEXECFOUND = 96 | |||
def _subprocess_setup(): | |||
# Python installs a SIGPIPE handler by default. This is usually not what | |||
# non-Python subprocesses expect. | |||
signal.signal(signal.SIGPIPE, signal.SIG_DFL) | |||
def _exit_error(execname, message, errorcode, log=True): | |||
print "%s: %s" % (execname, message) | |||
if log: | |||
logging.error(message) | |||
sys.exit(errorcode) | |||
if __name__ == '__main__': | |||
# Split arguments, require at least a command | |||
execname = sys.argv.pop(0) | |||
if len(sys.argv) < 2: | |||
_exit_error(execname, "No command specified", RC_NOCOMMAND, log=False) | |||
configfile = sys.argv.pop(0) | |||
userargs = sys.argv[:] | |||
# Add ../ to sys.path to allow running from branch | |||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname), | |||
os.pardir, os.pardir)) | |||
if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common.rootwrap import wrapper | |||
# Load configuration | |||
try: | |||
rawconfig = ConfigParser.RawConfigParser() | |||
rawconfig.read(configfile) | |||
config = wrapper.RootwrapConfig(rawconfig) | |||
except ValueError as exc: | |||
msg = "Incorrect value in %s: %s" % (configfile, exc.message) | |||
_exit_error(execname, msg, RC_BADCONFIG, log=False) | |||
except ConfigParser.Error: | |||
_exit_error(execname, "Incorrect configuration file: %s" % configfile, | |||
RC_BADCONFIG, log=False) | |||
if config.use_syslog: | |||
wrapper.setup_syslog(execname, | |||
config.syslog_log_facility, | |||
config.syslog_log_level) | |||
# Execute command if it matches any of the loaded filters | |||
filters = wrapper.load_filters(config.filters_path) | |||
try: | |||
filtermatch = wrapper.match_filter(filters, userargs, | |||
exec_dirs=config.exec_dirs) | |||
if filtermatch: | |||
command = filtermatch.get_command(userargs, | |||
exec_dirs=config.exec_dirs) | |||
if config.use_syslog: | |||
logging.info("(%s > %s) Executing %s (filter match = %s)" % ( | |||
os.getlogin(), pwd.getpwuid(os.getuid())[0], | |||
command, filtermatch.name)) | |||
obj = subprocess.Popen(command, | |||
stdin=sys.stdin, | |||
stdout=sys.stdout, | |||
stderr=sys.stderr, | |||
preexec_fn=_subprocess_setup, | |||
env=filtermatch.get_environment(userargs)) | |||
obj.wait() | |||
sys.exit(obj.returncode) | |||
except wrapper.FilterMatchNotExecutable as exc: | |||
msg = ("Executable not found: %s (filter match = %s)" | |||
% (exc.match.exec_path, exc.match.name)) | |||
_exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog) | |||
except wrapper.NoFilterMatched: | |||
msg = ("Unauthorized command: %s (no filter matched)" | |||
% ' '.join(userargs)) | |||
_exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog) |
@ -0,0 +1,53 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# 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. | |||
import eventlet | |||
eventlet.monkey_patch() | |||
import contextlib | |||
import os | |||
import sys | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'cinder', '__init__.py')): | |||
sys.path.insert(0, POSSIBLE_TOPDIR) | |||
from oslo.config import cfg | |||
from cinder.openstack.common import log as logging | |||
from cinder.openstack.common import rpc | |||
from cinder.openstack.common.rpc import impl_zmq | |||
CONF = cfg.CONF | |||
CONF.register_opts(rpc.rpc_opts) | |||
CONF.register_opts(impl_zmq.zmq_opts) | |||
def main(): | |||
CONF(sys.argv[1:], project='cinder') | |||
logging.setup("cinder") | |||
with contextlib.closing(impl_zmq.ZmqProxy(CONF)) as reactor: | |||
reactor.consume_in_thread() | |||
reactor.wait() | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,50 @@ | |||
#!/usr/bin/env python | |||
# 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. | |||
"""Starter script for Cinder Scheduler.""" | |||
import eventlet | |||
eventlet.monkey_patch() | |||
import os | |||
import sys | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(possible_topdir, 'cinder', '__init__.py')): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder import service | |||
from cinder import utils | |||
if __name__ == '__main__': | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
utils.monkey_patch() | |||
server = service.Service.create(binary='cinder-scheduler') | |||
service.serve(server) | |||
service.wait() |
@ -0,0 +1,60 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# Copyright 2013 NetApp | |||
# 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. | |||
"""Starter script for Cinder Share.""" | |||
import eventlet | |||
eventlet.monkey_patch() | |||
import os | |||
import sys | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(possible_topdir, 'cinder', '__init__.py')): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder import service | |||
from cinder import utils | |||
FLAGS = flags.FLAGS | |||
if __name__ == '__main__': | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
utils.monkey_patch() | |||
launcher = service.ProcessLauncher() | |||
if FLAGS.enabled_share_backends: | |||
for backend in FLAGS.enabled_share_backends: | |||
host = "%s@%s" % (FLAGS.host, backend) | |||
server = service.Service.create( | |||
host=host, | |||
service_name=backend) | |||
launcher.launch_server(server) | |||
else: | |||
server = service.Service.create(binary='cinder-share') | |||
launcher.launch_server(server) | |||
launcher.wait() |
@ -0,0 +1,61 @@ | |||
#!/usr/bin/env python | |||
# 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. | |||
"""Starter script for Cinder Volume.""" | |||
import eventlet | |||
eventlet.monkey_patch() | |||
import os | |||
import sys | |||
# If ../cinder/__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)) | |||
if os.path.exists(os.path.join(possible_topdir, 'cinder', '__init__.py')): | |||
sys.path.insert(0, possible_topdir) | |||
from cinder.openstack.common import gettextutils | |||
gettextutils.install('cinder') | |||
from cinder import flags | |||
from cinder.openstack.common import log as logging | |||
from cinder import service | |||
from cinder import utils | |||
FLAGS = flags.FLAGS | |||
if __name__ == '__main__': | |||
flags.parse_args(sys.argv) | |||
logging.setup("cinder") | |||
utils.monkey_patch() | |||
launcher = service.ProcessLauncher() | |||
if FLAGS.enabled_backends: | |||
for backend in FLAGS.enabled_backends: | |||
host = "%s@%s" % (FLAGS.host, backend) | |||
server = service.Service.create( | |||
host=host, | |||
service_name=backend) | |||
launcher.launch_server(server) | |||
else: | |||
server = service.Service.create(binary='cinder-volume') | |||
launcher.launch_server(server) | |||
launcher.wait() |
@ -0,0 +1,101 @@ | |||
#!/usr/bin/env python | |||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | |||
# Copyright (c) 2011 OpenStack, LLC. | |||