Initial baseline of OpenStack Charm documentation
This commit is contained in:
commit
cf6a5a2e15
|
@ -0,0 +1,20 @@
|
|||
# Testenvironment
|
||||
.tox/*
|
||||
|
||||
# Build directories
|
||||
doc/build/*
|
||||
|
||||
# generated during doc builds
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Packages
|
||||
*.egg-info/*
|
||||
|
||||
# Editors
|
||||
*~
|
||||
.*.swp
|
||||
.bak
|
||||
|
||||
# For Mac Users
|
||||
.DS_Store
|
|
@ -0,0 +1,4 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/openstack-charm-guide.git
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,10 @@
|
|||
=====================
|
||||
OpenStack Charm Guide
|
||||
=====================
|
||||
To build the guide, execute the following command::
|
||||
|
||||
$ tox
|
||||
|
||||
After running ``tox``, the documentation will be available for viewing
|
||||
in HTML format in the ``doc/build/`` directory. View these new documents
|
||||
in your favorite web browser.
|
|
@ -0,0 +1,17 @@
|
|||
.. _backporting:
|
||||
|
||||
Backporting Policy
|
||||
==================
|
||||
|
||||
This page documents the OpenStack Charms backport policy.
|
||||
|
||||
Backport candidates
|
||||
-------------------
|
||||
|
||||
- Critical and High bugs fixes, if reported in Launchpad.
|
||||
|
||||
Backport exclusions
|
||||
-------------------
|
||||
|
||||
- Medium and Low bug fixes
|
||||
- Features
|
|
@ -0,0 +1,676 @@
|
|||
.. _coding_guidelines:
|
||||
|
||||
=================
|
||||
Coding Guidelines
|
||||
=================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
We write code for OpenStack charms. Mostly in Python. They say that code is
|
||||
read roughly 20 times more than writing it, and that’s just the process of
|
||||
writing code. Reviewing code and modifying it means that it will be read many,
|
||||
many times. Let’s make it as easy as possible. We’re lucky(!) with Python as
|
||||
the syntax ensures that it roughly always looks the same.
|
||||
|
||||
As OpenStack charms are for OpenStack it’s a good idea to adhere to the
|
||||
OpenStack Python coding standard. So first things first:
|
||||
|
||||
* Read the `OpenStack Coding standard <http://docs.openstack.org/developer/hacking/>`__.
|
||||
* Read PEP8 (again).
|
||||
|
||||
Topics
|
||||
------
|
||||
|
||||
Multiple roots -- symlinks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Multiple roots with symlinks create issues in charms. This is where, for
|
||||
example, charmhelpers is symlinked into both a hooks and actions subdirectory.
|
||||
This creates a situation where the *same* modules are loaded into the Python
|
||||
interpreter's memory twice at two different module paths. This creates
|
||||
problems with testing as *depending on the load time ordering* it might not be
|
||||
clear which particular module path you're trying to mock out and which one is
|
||||
first in the module path map.
|
||||
|
||||
So only every have ONE root for your python code in a charm. e.g. put it in
|
||||
``/lib`` and add that to path by ``sys.path.append(‘lib’).``
|
||||
|
||||
Incidentally, if you **are** mocking out code in charmhelpers in you charms,
|
||||
**it's probably not a good idea**. Only mock code in the target object file,
|
||||
rather than an included module.
|
||||
|
||||
Install-time vs Load-time vs Runtime code
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The hooks in charms are effectively short-term running scripts. However,
|
||||
despite being short-lived, the code invoked is often complex with multiple
|
||||
modules being imported which also import other modules.
|
||||
|
||||
It’s important to be clear on what is *load time* code and _runtime_ code.
|
||||
Although there is no actual distinction in Python, it’s useful to think of
|
||||
*runtime* starting when the following code is reached:
|
||||
|
||||
.. code:: python
|
||||
|
||||
if __name__ == ‘__main__’:
|
||||
do_something()
|
||||
|
||||
I.e. the code execution of ``do_something()`` is runtime, with everything
|
||||
preceding being loadtime.
|
||||
|
||||
So why is the distinction useful? Put simply, *it’s much harder to test*
|
||||
load-time code in comparison to runtime code with respect to mocking. Consider
|
||||
these two fragments:
|
||||
|
||||
**Bad:**
|
||||
|
||||
.. code:: python
|
||||
|
||||
import a.something
|
||||
|
||||
OUR_CONFIG = {
|
||||
‘some_thing’: a.something.config(‘a-value’),
|
||||
}
|
||||
|
||||
**Good:**
|
||||
|
||||
.. code:: python
|
||||
|
||||
import a.something
|
||||
|
||||
|
||||
def get_our_config():
|
||||
return {
|
||||
‘some_thing’: a.something.config(‘a-value’),
|
||||
}
|
||||
|
||||
If performance is an issue (i.e. multiple calls to ``config()`` are expensive)
|
||||
then either use a ``@caching`` type decorator, or just doing it manually. e.g.
|
||||
|
||||
.. code:: python
|
||||
|
||||
_our_config = None
|
||||
|
||||
def get_our_config():
|
||||
if _our_config is None:
|
||||
_our_config = {
|
||||
'some_thing': a.something.config('a-value'),
|
||||
}
|
||||
return _our_config
|
||||
|
||||
In the bad example, in order to mock out the config module we have to do
|
||||
something like:
|
||||
|
||||
.. code:: python
|
||||
|
||||
with patch(‘a.something.config’) as mock_config:
|
||||
import a.something.config
|
||||
|
||||
This also relies on this being the _first_ time that module has been imported.
|
||||
Otherwise, the module is already cached and config can’t be mocked out.
|
||||
|
||||
Compare this with the good example.
|
||||
|
||||
.. code:: python
|
||||
|
||||
def test_out_config(self):
|
||||
with patch(‘module.a.something.config’) as mock_config:
|
||||
mock_config.return_value = ‘thing’
|
||||
x = model.get_out_config()
|
||||
|
||||
This brings us to:
|
||||
|
||||
CONSTANTS should be simple
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the bad example above, the constant ``OUR_CONFIG`` is defined as load-time by
|
||||
calling ``a.something.config()``. Thus, in reality, the constant is being
|
||||
defined at load-time using a runtime function that returns a value - it's
|
||||
dynamic.
|
||||
|
||||
Don’t:
|
||||
|
||||
.. code:: python
|
||||
|
||||
CONFIG = {
|
||||
‘some_key’: config(‘something’),
|
||||
}
|
||||
|
||||
This is actually a *function in disguise*.
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def get_config():
|
||||
return {
|
||||
‘some_key’: config(‘something’),
|
||||
}
|
||||
|
||||
Why?
|
||||
|
||||
So that you can mock out ``get_config()`` or ``config()`` at the test run time,
|
||||
rather than before the module loads. This makes testing easier, more
|
||||
predictable, and also makes it obvious that it’s not really a constant, but
|
||||
actually a function which returns a structure that is dynamically generated
|
||||
from configuration.
|
||||
|
||||
And **definitely** don’t do this at the top level in a file:
|
||||
|
||||
.. code:: python
|
||||
|
||||
CONFIGS = register_configs()
|
||||
|
||||
You’ve just created a load time test problem _and_ created a CONSTANT that
|
||||
isn’t really one. Just use ``register_configs()`` directly in the code and write
|
||||
``register_configs()`` to be ``@cached`` if performance is an issue.
|
||||
|
||||
|
||||
Decorators
|
||||
~~~~~~~~~~
|
||||
|
||||
There shouldn't be much need to write a decorator. They definitely **should
|
||||
not** be used instead of function application or instead of context managers.
|
||||
When they are used it's preferable that they are orthogonal to the function
|
||||
they are decorating, and don't change the nature of the function.
|
||||
|
||||
functools.wraps(f)
|
||||
++++++++++++++++++
|
||||
|
||||
If they are used, then they should definitely make use of ``functools.wraps`` to
|
||||
preserve the function name of the original function and it's docstring. This
|
||||
makes stacktraces more readable. e.g.:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def my_decorator(f):
|
||||
functools.wraps(f):
|
||||
def decoration(*args, **kwargs):
|
||||
# do soemthing before the function call?
|
||||
r = f(*args, **kwargs)
|
||||
# do soemthing after the function call?
|
||||
return r
|
||||
|
||||
return decoration
|
||||
|
||||
Mocking out decorators
|
||||
++++++++++++++++++++++
|
||||
|
||||
If the decorator's functionality is orthogonal to the function, then mocking
|
||||
out the decorator shouldn't be necessary. However, if it *isn't* then tweaking
|
||||
how the decorator is written can make it easier to mock out the decorator.
|
||||
|
||||
Consider the following code:
|
||||
|
||||
.. code:: python
|
||||
|
||||
@a_decorator("Hello")
|
||||
def some_function():
|
||||
pass
|
||||
|
||||
def a_decorator(name):
|
||||
def outer(f):
|
||||
@functools.wraps(f)
|
||||
def inner(*args, **kwargs):
|
||||
# do something before the function
|
||||
r = f(*args, **kwargs)
|
||||
# do something after the function
|
||||
return r
|
||||
|
||||
return inner
|
||||
|
||||
return outer
|
||||
|
||||
It's very difficult to test some_function without invoking the decorator, and
|
||||
equally, it's difficult to stop the decorator from being applied to the
|
||||
function without mocking out ``@a_decorator`` before importing the module under
|
||||
test.
|
||||
|
||||
However, with a little tweaking of the decorator we can mock out the decorator
|
||||
without having to jump through hoops:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def a_decorator(name):
|
||||
def outer(f):
|
||||
@functools.wraps(f)
|
||||
def inner(*args, **kwargs):
|
||||
return _inner(name, args, kwargs)
|
||||
return inner
|
||||
return outer
|
||||
|
||||
def _inner(name, args, kwargs):
|
||||
# do something before the function
|
||||
r = f(*args, **kwargs)
|
||||
# do something afterwards
|
||||
return r
|
||||
|
||||
Now, we can easily mock ``_inner()`` after the module has been loaded, thus
|
||||
changing the function of the decorator _after_ it has been applied.
|
||||
|
||||
|
||||
Import ordering and style
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's be consistent and ensure that we have the same import ordering and style
|
||||
across all of the charms (and other code) that we release.
|
||||
|
||||
Use absolute imports
|
||||
++++++++++++++++++++
|
||||
|
||||
Use absolute imports. In Python 2 code this means also that we should force
|
||||
absolute imports:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
We should use absolute imports so that we don't run into module name clashes
|
||||
across our own modules, nor with system and 3rd party packages. See
|
||||
https://www.python.org/dev/peps/pep-0328/#id8 for more details.
|
||||
|
||||
|
||||
Import ordering
|
||||
+++++++++++++++
|
||||
|
||||
* Core Python system packages
|
||||
* Third party modules
|
||||
* Local modules
|
||||
|
||||
They should be be alphabetical order, with a single space between them, and
|
||||
preferably in alphabetical order. If load order is important (and it shouldn’t
|
||||
be!) then that’s the only reason they shouldn’t be in alpha order.
|
||||
|
||||
Import Style
|
||||
++++++++++++
|
||||
|
||||
It's preferable to import a module rather than an object, class, function or
|
||||
instance from a module.
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import module
|
||||
|
||||
module.function()
|
||||
|
||||
over:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from module import function
|
||||
|
||||
function()
|
||||
|
||||
However, if there are good reasons to import from a module, and there is more
|
||||
than one item, then the style is:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from module import (
|
||||
one_import_per_line,
|
||||
)
|
||||
|
||||
Why?
|
||||
|
||||
Using ``import module; module.function()`` rather than ``from module import
|
||||
function`` is preferable because:
|
||||
|
||||
* with multiple imports, more symbols are being brought into the importing
|
||||
modules namespace.
|
||||
* It's clearer in the code when an external function is being used, as it is
|
||||
always prefixed by the external module name. This is useful as it makes it
|
||||
more obvious what is happening in the code.
|
||||
|
||||
Only patch mocks in the file/module under test
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A unit test often needs to mock out functions, classes or instances in the file
|
||||
under test. The mocks should _only_ be applied to the file that contains the
|
||||
item that is being tested.
|
||||
|
||||
Don't:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# object.py
|
||||
import something
|
||||
|
||||
def function_under_test(x):
|
||||
return something.doing(x)
|
||||
|
||||
In the unit test file: ``test_unit.py``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# test_unit.py
|
||||
def unit_test():
|
||||
with patch('something.doing') as y:
|
||||
y.return_value = 5
|
||||
assert function_under_test(3) == 5
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# object.py
|
||||
import something
|
||||
|
||||
def function_under_test(x):
|
||||
return something.doing(x)
|
||||
|
||||
In the unit test file: ``test_unit.py``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# test_unit.py
|
||||
def unit_test():
|
||||
with patch('object.something.doing') as y:
|
||||
y.return_value = 5
|
||||
assert function_under_test(3) == 5
|
||||
|
||||
i.e. the thing that is patched is in object.py **not** in the library file
|
||||
'something.py'
|
||||
|
||||
Don't use _underscore_methods outside of the class
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Underscore methods are supposed to be, by convention, private to the enclosing
|
||||
scope, be that a module or a class. They are used to signal that the method is
|
||||
_private_ even though the privacy can't be enforced.
|
||||
|
||||
Thus don't do this:
|
||||
|
||||
.. code:: python
|
||||
|
||||
class A():
|
||||
def _private_method():
|
||||
pass
|
||||
|
||||
x = A()
|
||||
x._private_method()
|
||||
|
||||
Simply rename the method without the underscore. Otherwise you break the
|
||||
convention and people will not understand how you are using *private methods*.
|
||||
|
||||
Equally, don't use them in derived classes _either_. A private method is
|
||||
supposed to be private to the class, and not used in derived classes.
|
||||
|
||||
Only use list comprehensions when you want the list
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Don’t:
|
||||
|
||||
.. code:: python
|
||||
|
||||
[do_something_with(thing) for thing in mylist]
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
for thing in mylist:
|
||||
do_something_with(thing)
|
||||
|
||||
Why?
|
||||
|
||||
You just created a list and then threw it away. And it’s actually less clear
|
||||
what you are doing. Do use list comprehensions when you actually want a list
|
||||
to do something with.
|
||||
|
||||
Avoid C-style dictionary access in loops
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Don’t:
|
||||
|
||||
.. code:: python
|
||||
|
||||
for key in dictionary:
|
||||
do_something_with(key, dictionary[key])
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
for key, value in dictionary.items():
|
||||
do_something_with(key, value)
|
||||
|
||||
Why?
|
||||
|
||||
Using a list of keys to access a dictionary is less efficient and less obvious
|
||||
as to what’s happening. ``key, value`` could actually be ``config_name`` and
|
||||
``config_item`` which means the code is more self-documenting.
|
||||
|
||||
Also remember that ``dictionary.keys()`` & ``dictionary.values()`` exist if you
|
||||
want to explicitly iterate just over the keys or values of a dictionary. Also,
|
||||
it’s preferable to iterate of ``dictionary.keys()`` rather than ``dictionary``
|
||||
because, whilst they do the same thing, it’s not as obvious what is happening.
|
||||
|
||||
If performance is an issue (Python2) then ``iterkeys()`` and ``itervalues()`` for
|
||||
generators, which is the default on Python3.
|
||||
|
||||
Prefer tuples to lists
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tuples are non malleable lists, and should be used where the list isn’t going
|
||||
to change. They have (slight) performance advantages, but come with a
|
||||
guarantee that the list won’t change - note the objects within the tuple could
|
||||
change, just not their position or reference.
|
||||
|
||||
Thus don’t:
|
||||
|
||||
.. code:: python
|
||||
|
||||
if x in [‘hello’, ‘there’]:
|
||||
do_something()
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
if x in (‘hello’, ‘there’):
|
||||
do_something()
|
||||
|
||||
However, remember the caveat. A single item tuple literal has to have a
|
||||
trailing comma:
|
||||
|
||||
.. code:: python
|
||||
|
||||
my_tuple = (‘item’, )
|
||||
|
||||
|
||||
Prefer CONSTANTS to string literals or numbers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is the “No magic numbers” rule. In a lot of the OS charms there is code
|
||||
like:
|
||||
|
||||
.. code:: python
|
||||
|
||||
db = kv()
|
||||
previous_thing = db.get('thing_key', thing)
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
THING_KEY = ‘thing_key’
|
||||
|
||||
db = kv()
|
||||
previous_thing = db.get(THING_KEY, thing)
|
||||
|
||||
Why?
|
||||
|
||||
String literals introduce a vector for mistakes. We can’t use the language to
|
||||
help prevent spelling mistakes, nor our tools to do autocompletion, nor use
|
||||
lint to find ‘undefined’ variables. This also means that if you use the same
|
||||
number or string literal more than once in code you should create a constant
|
||||
for that value and use that in code. This includes fixed array accesses,
|
||||
offsets, etc.
|
||||
|
||||
Don’t abuse __call__()
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``__call__()`` is a method that is invoked when ``()`` is invoked on an object --
|
||||
``()`` on a class invokes ``__call__`` on the metaclass for the class.
|
||||
|
||||
A good example of abuse of ``__call__`` is the class ``HookData()`` which, to
|
||||
access the context manager, is invoked as:
|
||||
|
||||
.. code:: python
|
||||
|
||||
with HookData()() as hd:
|
||||
hd.kv.set(...)
|
||||
|
||||
The sequence ``()()`` is almost certainly a *code smell*. There is hidden
|
||||
behaviour that requires you to go to the class to see what is actually
|
||||
happening. It would have been more obvious if that method was just called
|
||||
``cm()`` or ``context()``:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
with HookData().context() as hd:
|
||||
hd.kv.set(...)
|
||||
|
||||
|
||||
Don’t use old style string interpolation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: python
|
||||
|
||||
action_fail("Cannot remove service: %s" % service.host)
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
action_fail("Cannot remove service: {}".format(service.host))
|
||||
|
||||
Why?
|
||||
|
||||
It’s the new style, and the old style is deprecated; eventually it will be
|
||||
removed. Plus the new style is way more powerful: keywords, dictionary
|
||||
support, to name but a few.
|
||||
|
||||
Docstrings and comments
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Every function exported by a module should have a docstring. Generally, this
|
||||
means all functions mentioned in ``__ALL__`` or implicitly those that do not
|
||||
start with an ``_``.
|
||||
|
||||
The preferred format for documenting parameters and return values is
|
||||
ReStructuredText (reST) as described: http://docutils.sourceforge.net/rst.html
|
||||
|
||||
The field lists are described here:
|
||||
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
|
||||
|
||||
An example of an acceptable function docstring is:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def mult(a, b):
|
||||
"""Multiple a * b and return the result.
|
||||
|
||||
:param a: Number
|
||||
:param b: Number
|
||||
:returns Number: a * b
|
||||
:raises: ValueError, TypeError if the params are not numbers
|
||||
"""
|
||||
return a * b
|
||||
|
||||
Other comments should be used to support the code, but not just re-say what the
|
||||
code is doing.
|
||||
|
||||
Ensure there's a comma on the last item of a dictionary
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This helps when the developer adds an item to a dictionary literal, in that
|
||||
they don't have to edit the previous line to add a comma. It also means that
|
||||
the review doesn't indicate that the previous line has changed (due to the
|
||||
addition of a comma).
|
||||
|
||||
Prefer:
|
||||
|
||||
.. code:: python
|
||||
|
||||
a_dict = {
|
||||
'one': 1,
|
||||
'two': 2,
|
||||
}
|
||||
|
||||
over:
|
||||
|
||||
.. code:: python
|
||||
|
||||
a_dict = {
|
||||
'one': 1,
|
||||
'two': 2
|
||||
}
|
||||
|
||||
Avoid dynamic default arguments in functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Don't use a dynamic assignment to a default argument. e.g.
|
||||
|
||||
.. code:: python
|
||||
|
||||
def a(b=[]):
|
||||
b.append('hello')
|
||||
print b
|
||||
|
||||
In [2]: a()
|
||||
['hello']
|
||||
|
||||
In [3]: a()
|
||||
['hello', 'hello']
|
||||
|
||||
As you can see, the list is only assigned the first time, and thereafter it
|
||||
'remember' the previous values.
|
||||
|
||||
Also avoid other default, dynamic, assignments:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def f():
|
||||
return ['Hello']
|
||||
|
||||
|
||||
def a(b=f()):
|
||||
b.append('there')
|
||||
print b
|
||||
|
||||
|
||||
In [3]: a()
|
||||
['Hello', 'there']
|
||||
|
||||
In [4]: a()
|
||||
['Hello', 'there', 'there']
|
||||
|
||||
Instead, prefer:
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
def a(b=None):
|
||||
if b is None:
|
||||
b = f()
|
||||
b.append('there')
|
||||
print b
|
||||
|
||||
|
||||
In [6]: a()
|
||||
['Hello', 'there']
|
||||
|
||||
In [7]: a()
|
||||
['Hello', 'there']
|
||||
|
||||
Why?
|
||||
|
||||
Although it can be a handy side-effect for allowing a function to remember
|
||||
previous values, due to a quirk in the interpreter in only assigning the
|
||||
reference once, it may be changed in the future and it hides the intention of
|
||||
the code.
|
|
@ -0,0 +1,274 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Tempest documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue May 21 17:43:32 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
|
||||
from jinja2.utils import Markup
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# 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.todo',
|
||||
'sphinx.ext.viewcode',
|
||||
'oslosphinx'
|
||||
]
|
||||
|
||||
todo_include_todos = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'OpenStack Charms Guide'
|
||||
copyright = Markup(u'%s, OpenStack Contributors '
|
||||
u'- use the <a href="https://git.openstack.org/cgit/'
|
||||
u'openstack/openstack-charms-guide">openstack-charms-guide git repo</a> '
|
||||
u'to propose changes' % datetime.date.today().year)
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# 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 = False
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['puppet-openstack-guide.']
|
||||
|
||||
# -- Options for man page output ----------------------------------------------
|
||||
man_pages = []
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'nature'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
|
||||
html_last_updated_fmt = os.popen(git_cmd).read()
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
html_domain_indices = False
|
||||
|
||||
# If false, no index is generated.
|
||||
html_use_index = False
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'OpenStack-Charms-Guidedoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'OpenStack-Charms-guide.tex', u'OpenStack Charms Guide',
|
||||
u'OpenStack Contributors', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'OpenStack-Charms-guide', u'OpenStack Charms Guide',
|
||||
u'OpenStack Contributors', 'OpenStack-Charms-guide',
|
||||
'OpenStack Charms Guide.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
# -- Options for Epub output ---------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'OpenStack Charms Guide'
|
||||
epub_author = u'OpenStack Contributors'
|
||||
epub_publisher = u'OpenStack Contributors'
|
||||
epub_copyright = u'%s, OpenStack Contributors' % datetime.date.today().year
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
#epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
#epub_cover = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
#epub_exclude_files = []
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
#epub_tocdup = True
|
|
@ -0,0 +1,15 @@
|
|||
==========
|
||||
Talk to us
|
||||
==========
|
||||
|
||||
* Talk to us on IRC channel ``#openstack-charms`` on Freenode.
|
||||
* Join the conversation on our `Mailing list <http://docs.openstack.org/developer/openstack-charm-guide/mailing-list.html>`_.
|
||||
* Participate to our `Online Meetings <http://docs.openstack.org/developer/openstack-charm-guide/meetings.html>`_.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
|
||||
mailing-list
|
||||
meetings
|
|
@ -0,0 +1,23 @@
|
|||
===============
|
||||
Getting started
|
||||
===============
|
||||
|
||||
* The OpenStack Charms use Juju to deploy and manage OpenStack services, so
|
||||
getting to know Juju_ is a great first step.
|
||||
* You might want to tryout the charms on your laptop using the Juju LXD
|
||||
provider and the
|
||||
`OpenStack on LXD bundle <http://github.com/openstack-charmers/openstack-on-lxd>`__
|
||||
if you don't have physical servers to use or just want to contribute a fix
|
||||
or change.
|
||||
* For bare-metal deployment, Juju uses MAAS_ to provision and configure
|
||||
physical servers, so that's a good next step.
|
||||
* Once you have MAAS setup, you can try out the
|
||||
`OpenStack Base bundle <http://jujucharms.com/openstack-base>`__ from
|
||||
the Juju charm store.
|
||||
|
||||
.. _Juju: https://jujucharms.com/docs/devel/getting-started
|
||||
.. _MAAS: http://maas.io/get-started
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:hidden:
|
|
@ -0,0 +1,22 @@
|
|||
=================
|
||||
How to contribute
|
||||
=================
|
||||
|
||||
The OpenStack charms are part of the OpenStack project, and follow the
|
||||
same development process as other projects.
|
||||
|
||||
For details on how to submit changes to an OpenStack project please refer
|
||||
to the OpenStack `development documentation <http://docs.openstack.org/infra/manual/developers.html>`__.
|
||||
and then take a read through the rest of this section on how to contribute
|
||||
to the OpenStack Charms.
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
coding-guidelines
|
||||
testing
|
||||
making-a-change
|
||||
new-charm
|
||||
backport-policy
|
||||
reviews
|
|
@ -0,0 +1,21 @@
|
|||
====================================
|
||||
Welcome to the OpenStack Charm Guide
|
||||
====================================
|
||||
|
||||
The OpenStack charms deliver fast, repeatable OpenStack deployment
|
||||
with lose coupling between OpenStack Services.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:includehidden:
|
||||
|
||||
getting-started
|
||||
how-to-contribute
|
||||
find-us
|
||||
|
||||
====================
|
||||
Indices and tables
|
||||
====================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
|
@ -0,0 +1,16 @@
|
|||
.. _mailing_list:
|
||||
|
||||
=============
|
||||
Mailing lists
|
||||
=============
|
||||
|
||||
Please use the mailing list when possible as it makes the information
|
||||
discussed more readily available so that others who have the same
|
||||
question can search for (and find) those answers.
|
||||
|
||||
- Dev discussions:
|
||||
`openstack-dev@lists.openstack.org <http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev>`__
|
||||
with ``[charms]`` tag.
|
||||
- Usage discussions:
|
||||
`openstack-operators@lists.openstack.org <http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-operators>`__
|
||||
with ``[charms]`` tag.
|
|
@ -0,0 +1,79 @@
|
|||
Making a change
|
||||
===============
|
||||
|
||||
Development Workflow
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Broadly the workflow for making a change to a charm is:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git clone http://github.com/openstack/charm-cinder
|
||||
cd charm-cinder
|
||||
git checkout -b bug/XXXXXX master
|
||||
|
||||
Make the changes you need within the charm, and then run unit and pep8 tests using tox:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
tox
|
||||
|
||||
resolve any problems this throws up, and then commit your changes:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git add .
|
||||
git commit
|
||||
|
||||
Commit messages should have an appropriate title, and more detail in the body; they
|
||||
can also refer to bugs:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
Closes-Bug: #######
|
||||
Partial-Bug: #######
|
||||
Related-Bug: #######
|
||||
|
||||
Gerrit will automatically link your proposal to the bug reports on launchpad and
|
||||
mark them fix commited when changes are merged.
|
||||
|
||||
Execute pep8 and unit tests:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
tox
|
||||
|
||||
Finally, submit your change for review (if they pass pep8 and unit tests!):
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git review
|
||||
|
||||
This will push your proposed changes to Gerrit and provide you with a URL for the
|
||||
review board on http://review.openstack.org/.
|
||||
|
||||
To make amendments to your proposed change, update your local branch and then:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git commit --amend
|
||||
git review
|
||||
|
||||
Stable charm updates
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Any update to a stable charm must first be applied into the master branch; it should
|
||||
then be cherry-picked in a review for the stable branch corresponding to your target
|
||||
release (ensuring that any interim releases have the fix landed):
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git checkout -b stable/bug/XXXX stable/YYYY
|
||||
git cherry-pick -x <hash of master branch commit>
|
||||
git review
|
||||
|
||||
Where XXXX is the launchpad bug ID corresponding to the fix you want to backport and
|
||||
YYYY is the release name you are targeting e.g. 16.04
|
||||
|
||||
.. note:: when cherry-picking a commit and/or modifying the commit message, always ensure that
|
||||
the original Change-Id is left intact.
|
|
@ -0,0 +1,44 @@
|
|||
.. _meetings:
|
||||
|
||||
########
|
||||
Meetings
|
||||
########
|
||||
|
||||
1. `Weekly meeting`_
|
||||
2. `Next meeting`_
|
||||
3. `Previous meetings`_
|
||||
4. `Meeting organizers`_
|
||||
|
||||
Weekly meeting
|
||||
==============
|
||||
|
||||
If you're interested in the OpenStack Charms, we hold public meetings weekly on
|
||||
``#ubuntu-meeting`` on freenode.
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 75
|
||||
:header-rows: 1
|
||||
|
||||
* - Meeting Time
|
||||
- Local Time
|
||||
* - UTC 1700 Wednesdays
|
||||
- http://www.timeanddate.com/worldclock/fixedtime.html?msg=OpenStack+Charms&iso=20160622T17
|
||||
|
||||
|
||||
Next meeting
|
||||
============
|
||||
|
||||
- `Wednesday Jun 22, 2016 @ 1700 UTC
|
||||
<http://www.timeanddate.com/worldclock/fixedtime.html?msg=OpenStack+Charms&iso
|
||||
=20160622T17>`_ on ``#ubuntu-meeting`` on freenode
|
||||
|
||||
Agenda
|
||||
======
|
||||
|
||||
https://etherpad.openstack.org/p/openstack-charms-weekly-meeting-20160622
|
||||
|
||||
Previous meetings
|
||||
=================
|
||||
|
||||
Meeting organizers
|
||||
==================
|
|
@ -0,0 +1,353 @@
|
|||
.. _new_api_charm:
|
||||
|
||||
New API Charm
|
||||
=============
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
This guide will walk through the creation of a basic API charm for the Openstack
|
||||
`Congress <https://wiki.openstack.org/wiki/Congress>`__ service.
|
||||
|
||||
The charm will use prewritten Openstack `layers and interfaces <https://github.com/openstack-charmers>`__.
|
||||
|
||||
Once the charm is written it will be composed using `charm tools <https://github.com/juju/charm-tools/>`__.
|
||||
|
||||
The Congress service needs to register endpoints with Keystone. It needs
|
||||
a service username and password and it also needs a MySQL backend to
|
||||
store its schema.
|
||||
|
||||
Create the skeleton charm
|
||||
-------------------------
|
||||
|
||||
Firstly create a directory for the new charm and manage the charm with git.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
mkdir -p congress/src
|
||||
cd congress
|
||||
git init
|
||||
|
||||
The top layer of this charm is the Congress specific code this code will live in the charm subdirectory.
|
||||
|
||||
|
||||
.. code:: bash
|
||||
|
||||
mkdir -p src/{reactive,lib/charm/openstack}
|
||||
|
||||
Describe the Service and required layer(s)
|
||||
------------------------------------------
|
||||
|
||||
The new charm needs a basic src/metadata.yaml to describe what service the charm provides. Edit src/metadata.yaml
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
name: congress
|
||||
summary: Policy as a service
|
||||
description: |
|
||||
Congress is an open policy framework for the cloud. With Congress, a cloud
|
||||
operator can declare, monitor, enforce, and audit "policy" in a heterogeneous
|
||||
cloud environment.
|
||||
|
||||
The `openstack-api layer <https://github.com/openstack-charmers/charm-layer-openstack-api>`__
|
||||
defines a series of config options and interfaces which are mostly common across Openstack
|
||||
API services e.g. including the openstack-api-layer will pull in the Keystone and MySQL
|
||||
interfaces (among others) as well as the charm layers the new Congress charm can
|
||||
leverage.
|
||||
|
||||
To instruct "charm build" to pull in the openstack-api layer edit src/layer.yaml:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
includes: ['layer:openstack-api']
|
||||
|
||||
Add Congress configuration
|
||||
--------------------------
|
||||
|
||||
Define Congress attributes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There is a base OpenStackCharm class which provides the skeleton for the charm.
|
||||
Creating a child class from OpenStackCharm allows Congress specific attributes
|
||||
to be set, like which packages to install, which config files need rendering
|
||||
etc. This is all done in the src/lib/charm/openstack/congress.py file.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import charmhelpers.contrib.openstack.utils as ch_utils
|
||||
|
||||
import charms_openstack.charm
|
||||
import charms_openstack.adapters
|
||||
import charms_openstack.ip as os_ip
|
||||
|
||||
class CongressCharm(charms_openstack.charm.OpenStackCharm):
|
||||
|
||||
service_name = 'congress'
|
||||
release = 'mitaka'
|
||||
|
||||
# Packages the service needs installed
|
||||
packages = ['congress-server', 'congress-common', 'python-antlr3',
|
||||
'python-pymysql']
|
||||
|
||||
# Init services the charm manages
|
||||
services = ['congress-server']
|
||||
|
||||
# Standard interface adapters class to use.
|
||||
adapters_class = charms_openstack.adapters.OpenStackRelationAdapters
|
||||
|
||||
# Ports that need exposing.
|
||||
default_service = 'congress-api'
|
||||
api_ports = {
|
||||
'congress-api': {
|
||||
os_ip.PUBLIC: 1789,
|
||||
os_ip.ADMIN: 1789,
|
||||
os_ip.INTERNAL: 1789,
|
||||
}
|
||||
}
|
||||
|
||||
# Database sync command used to initalise the schema.
|
||||
sync_cmd = ['congress-db-manage', '--config-file',
|
||||
'/etc/congress/congress.conf', 'upgrade', 'head']
|
||||
|
||||
# The restart map defines which services should be restarted when a given
|
||||
# file changes
|
||||
restart_map = {
|
||||
'/etc/congress/congress.conf': ['congress-server'],
|
||||
'/etc/congress/api-paste.ini': ['congress-server'],
|
||||
'/etc/congress/policy.json': ['congress-server'],
|
||||
}
|
||||
|
||||
def __init__(self, release=None, **kwargs):
|
||||
"""Custom initialiser for class
|
||||
If no release is passed, then the charm determines the release from the
|
||||
ch_utils.os_release() function.
|
||||
"""
|
||||
if release is None:
|
||||
release = ch_utils.os_release('python-keystonemiddleware')
|
||||
super(CongressCharm, self).__init__(release=release, **kwargs)
|
||||
|
||||
def install(self):
|
||||
"""Customise the installation, configure the source and then call the
|
||||
parent install() method to install the packages
|
||||
"""
|
||||
self.configure_source()
|
||||
# and do the actual install
|
||||
super(CongressCharm, self).install()
|
||||
|
||||
For reasons methods are needed to wrap the calls to the Congress charms class
|
||||
methods. These can be appended to the bottom of the
|
||||
src/lib/charm/openstack/congress.py file.
|
||||
|
||||
.. code:: python
|
||||
|
||||
def install():
|
||||
"""Use the singleton from the CongressCharm to install the packages on the
|
||||
unit
|
||||
"""
|
||||
CongressCharm.singleton.install()
|
||||
|
||||
|
||||
def restart_all():
|
||||
"""Use the singleton from the CongressCharm to restart services on the
|
||||
unit
|
||||
"""
|
||||
CongressCharm.singleton.restart_all()
|
||||
|
||||
|
||||
def db_sync():
|
||||
"""Use the singleton from the CongressCharm to run db migration
|
||||
"""
|
||||
CongressCharm.singleton.db_sync()
|
||||
|
||||
|
||||
def setup_endpoint(keystone):
|
||||
"""When the keystone interface connects, register this unit in the keystone
|
||||
catalogue.
|
||||
"""
|
||||
charm = CongressCharm.singleton
|
||||
keystone.register_endpoints(charm.service_name,
|
||||
charm.region,
|
||||
charm.public_url,
|
||||
charm.internal_url,
|
||||
charm.admin_url)
|
||||
|
||||
|
||||
def render_configs(interfaces_list):
|
||||
"""Using a list of interfaces, render the configs and, if they have
|
||||
changes, restart the services on the unit.
|
||||
"""
|
||||
CongressCharm.singleton.render_with_interfaces(interfaces_list)
|
||||
|
||||
Add Congress code to react to events
|
||||
------------------------------------
|
||||
|
||||
Install Congress Packages
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The reactive framework is going to emit events that the Congress charm can react
|
||||
to. The charm needs to define how its going to react to these events and also
|
||||
raise new events as needed.
|
||||
|
||||
The first action a charm needs to do is to install the Congress code. This is
|
||||
by done running the install method from CongressCharm created earlier.
|
||||
|
||||
Edit src/reactive/handlers.py.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import charms.reactive as reactive
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
|
||||
# This charm's library contains all of the handler code associated with
|
||||
# congress
|
||||
import charm.openstack.congress as congress
|
||||
|
||||
|
||||
# use a synthetic state to ensure that it get it to be installed independent of
|
||||
# the install hook.
|
||||
@reactive.when_not('charm.installed')
|
||||
def install_packages():
|
||||
congress.install()
|
||||
reactive.set_state('charm.installed')
|
||||
|
||||
Configure Congress Relation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
At this point the charm could be built and deployed and it would deploy a unit,
|
||||
and install congress. However there is no code to specify how this charm should
|
||||
interact with the services it depend on. For example when joining the database
|
||||
the charm needs to specify the user and database it requires. The following code
|
||||
configures the relations with the dependant services.
|
||||
|
||||
Append to src/reactive/handlers.py:
|
||||
|
||||
.. code:: python
|
||||
|
||||
@reactive.when('amqp.connected')
|
||||
def setup_amqp_req(amqp):
|
||||
"""Use the amqp interface to request access to the amqp broker using our
|
||||
local configuration.
|
||||
"""
|
||||
amqp.request_access(username='congress',
|
||||
vhost='openstack')
|
||||
|
||||
|
||||
@reactive.when('shared-db.connected')
|
||||
def setup_database(database):
|
||||
"""On receiving database credentials, configure the database on the
|
||||
interface.
|
||||
"""
|
||||
database.configure('congress', 'congress', hookenv.unit_private_ip())
|
||||
|
||||
|
||||
@reactive.when('identity-service.connected')
|
||||
def setup_endpoint(keystone):
|
||||
congress.setup_endpoint(keystone)
|
||||
|
||||
Configure Congress
|
||||
------------------
|
||||
|
||||
Now that the charm has the relations defined that it needs the Congress charm
|
||||
is in a postion to generate its configuration files.
|
||||
|
||||
Create templates
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The charm code searches through the templates directories looking for a directory
|
||||
corresponding to the Openstack release being installed or earlier. Since Mitaka
|
||||
is the earliest release the charm is supporting a directory called mitaka will
|
||||
house the templates and files.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
( cd /tmp; apt-get source congress-server; )
|
||||
mkdir -p templates/mitaka
|
||||
cp /tmp/congress*/etc/{api-paste.ini,policy.json} templates/mitaka
|
||||
|
||||
A template for congress.conf is needed which will have have connection
|
||||
information for MySQL, RabbitMQ and Keystone as well as user controllable
|
||||
config options
|
||||
|
||||
.. code:: bash
|
||||
|
||||
[DEFAULT]
|
||||
auth_strategy = keystone
|
||||
drivers = congress.datasources.neutronv2_driver.NeutronV2Driver,congress.datasources.glancev2_driver.GlanceV2Driver,congress.datasources.nova_driver.NovaDriver,congress.datasources.keystone_driver.KeystoneDriver,congress.datasources.ceilometer_driver.CeilometerDriver,congress.datasources.cinder_driver.CinderDriver,congress.datasources.swift_driver.SwiftDriver,congress.datasources.plexxi_driver.PlexxiDriver,congress.datasources.vCenter_driver.VCenterDriver,congress.datasources.murano_driver.MuranoDriver,congress.datasources.ironic_driver.IronicDriver
|
||||
|
||||
[database]
|
||||
connection = {{ shared_db.uri }}
|
||||
|
||||
[keystone_authtoken]
|
||||
{% if identity_service.auth_host -%}
|
||||
auth_uri = {{ identity_service.service_protocol }}://{{
|
||||
identity_service.service_host }}:{{ identity_service.service_port }}
|
||||
auth_url = {{ identity_service.auth_protocol }}://{{ identity_service.auth_host
|
||||
}}:{{ identity_service.auth_port }}
|
||||
auth_plugin = password
|
||||
project_domain_id = default
|
||||
user_domain_id = default
|
||||
project_name = {{ identity_service.service_tenant }}
|
||||
username = {{ identity_service.service_username }}
|
||||
password = {{ identity_service.service_password }}
|
||||
{% endif -%}
|
||||
|
||||
Render the config
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now the templates and interfaces are in place the configs can be
|
||||
rendered. A side-effect of rendering the configs is that any associated
|
||||
services are restarted. Finally, set the config.complete state this
|
||||
will be used later to trigger other events.
|
||||
|
||||
Append to charm/reactive/handlers.py
|
||||
|
||||
.. code:: python
|
||||
|
||||
@reactive.when('shared-db.available')
|
||||
@reactive.when('identity-service.available')
|
||||
@reactive.when('amqp.available')
|
||||
def render_stuff(*args):
|
||||
congress.render_configs(args)
|
||||
reactive.set_state('config.complete')
|
||||
|
||||
Run DB Migration
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The DB migration can only be run once the config files are in place
|
||||
since as congress.conf will contain the DB connection information.
|
||||
|
||||
To achieve this the DB migration is gated on the config.complete
|
||||
being set. Finally set the db.synched event so that this is only
|
||||
run once.
|
||||
|
||||
Append to src/reactive/handlers.py
|
||||
|
||||
.. code:: python
|
||||
|
||||
@reactive.when('config.complete')
|
||||
@reactive.when_not('db.synched')
|
||||
def run_db_migration():
|
||||
congress.db_sync()
|
||||
congress.restart_all()
|
||||
reactive.set_state('db.synched')
|
||||
|
||||
Build and Deploy charm
|
||||
----------------------
|
||||
|
||||
Build the charm to pull down the interfaces and layers.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
mkdir build
|
||||
charm build -obuild src
|
||||
|
||||
The built charm can now be deployed with Juju.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
juju deploy <full path>/build/congress
|
||||
juju add-relation congress mysql
|
||||
juju add-relation congress keystone
|
||||
juju add-relation congress rabbitmq-server
|
||||
|
||||
Deploying an existing Openstack environment is not covered here.
|
|
@ -0,0 +1,76 @@
|
|||
.. _testing:
|
||||
|
||||
=======
|
||||
Testing
|
||||
=======
|
||||
|
||||
Every proposed change to a charm is run through testing during the review
|
||||
verification process. If you want to contribute a change or fix to a charm,
|
||||
please take time to review the `Unit Testing`_ and `Functional Testing`_
|
||||
sections of this document.
|
||||
|
||||
OpenStack Charm CI will verify your changes, but please execute at least
|
||||
unit tests locally before submitting patches to reduce load on the OpenStack
|
||||
CI infrastructure.
|
||||
|
||||
The OpenStack Charms are compliant with the OpenStack
|
||||
`Consistent Testing Interface <https://governance.openstack.org/reference/cti/python_cti.html>`__;
|
||||
take a read on how this works to understand in full.
|
||||
|
||||
|
||||
Lint
|
||||
====
|
||||
|
||||
You can verify the compliance of your code changes using flake8 and the charm
|
||||
proof tool using the pep8 tox environment:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
tox -e pep8
|
||||
|
||||
Ensure that any non-compliance is corrected prior to raising/updating a review.
|
||||
|
||||
Unit Testing
|
||||
============
|
||||
|
||||
Execute the unit tests for a charm using the tox py27 environment:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
tox -e py27
|
||||
|
||||
Unit tests are stored in the ``unit_tests`` folder; when adding features or
|
||||
changing existing code, please ensure that appropriate unit tests are added
|
||||
or updated to cover the changes you are making.
|
||||
|
||||
Unit tests are written in Python using standard mocking techniques to isolate
|
||||
the unit tests from the underlying host operating system.
|
||||
|
||||
Functional Testing
|
||||
==================
|
||||
|
||||
Amulet
|
||||
~~~~~~
|
||||
|
||||
Functional tests for a charm are written using the Amulet_ test framework and
|
||||
should exercise the target charm with a subset of a full OpenStack deployment
|
||||
to ensure that the charm is able to correctly deploy and configure the
|
||||
service that is encapsulates.
|
||||
|
||||
The OpenStack charm helpers provide some Amulet deployment helpers to ease
|
||||
testing of different OpenStack release combinations; typically each charm will
|
||||
test the OpenStack and Ubuntu release combinations currently supported by
|
||||
Ubuntu.
|
||||
|
||||
Execute of Amulet tests currently requires use of the Makefile:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
make functional_test
|
||||
|
||||
This will execute the full suite of Amulet tests under the ``tests/`` folder.
|
||||
|
||||
The ``tests/README`` file in each charm will contain more details on how to
|
||||
name tests and how to execute them individually.
|
||||
|
||||
.. _Amulet: https://jujucharms.com/docs/devel/tools-amulet
|
|
@ -0,0 +1,3 @@
|
|||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD
|
||||
oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0
|
||||
pbr>=1.6 # Apache-2.0
|
|
@ -0,0 +1,23 @@
|
|||
[metadata]
|
||||
name = openstack-charm-guide
|
||||
summary = OpenStack Charm Guide
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
classifier =
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
build-dir = doc/build
|
||||
source-dir = doc/source
|
||||
|
||||
[pbr]
|
||||
warnerrors = True
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
|
@ -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=['pbr'],
|
||||
pbr=True)
|
|
@ -0,0 +1,17 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = docs
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
basepython = python2.7
|
||||
usedevelop = True
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
install_command = pip install -U {opts} {packages}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
Loading…
Reference in New Issue