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