commit cf6a5a2e1534c90222dd5de4f71e8ad122036358 Author: James Page Date: Mon Jun 20 11:58:44 2016 +0100 Initial baseline of OpenStack Charm documentation diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a34a803b --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.gitreview b/.gitreview new file mode 100644 index 00000000..1ae51d4c --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.openstack.org +port=29418 +project=openstack/openstack-charm-guide.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..41b814cd --- /dev/null +++ b/README.rst @@ -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. diff --git a/doc/source/backport-policy.rst b/doc/source/backport-policy.rst new file mode 100644 index 00000000..b2ae527d --- /dev/null +++ b/doc/source/backport-policy.rst @@ -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 diff --git a/doc/source/coding-guidelines.rst b/doc/source/coding-guidelines.rst new file mode 100644 index 00000000..7c661bb2 --- /dev/null +++ b/doc/source/coding-guidelines.rst @@ -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 `__. +* 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. diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..6cb87188 --- /dev/null +++ b/doc/source/conf.py @@ -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 openstack-charms-guide git repo ' + 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 +# " v 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 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 diff --git a/doc/source/find-us.rst b/doc/source/find-us.rst new file mode 100644 index 00000000..ba29adb4 --- /dev/null +++ b/doc/source/find-us.rst @@ -0,0 +1,15 @@ +========== +Talk to us +========== + +* Talk to us on IRC channel ``#openstack-charms`` on Freenode. +* Join the conversation on our `Mailing list `_. +* Participate to our `Online Meetings `_. + + +.. toctree:: + :maxdepth: 1 + :hidden: + + mailing-list + meetings diff --git a/doc/source/getting-started.rst b/doc/source/getting-started.rst new file mode 100644 index 00000000..8c09cf7c --- /dev/null +++ b/doc/source/getting-started.rst @@ -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 `__ + 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 `__ from + the Juju charm store. + +.. _Juju: https://jujucharms.com/docs/devel/getting-started +.. _MAAS: http://maas.io/get-started + +.. toctree:: + :maxdepth: 1 + :hidden: diff --git a/doc/source/how-to-contribute.rst b/doc/source/how-to-contribute.rst new file mode 100644 index 00000000..c09ba76e --- /dev/null +++ b/doc/source/how-to-contribute.rst @@ -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 `__. +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 diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..939a6e57 --- /dev/null +++ b/doc/source/index.rst @@ -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` diff --git a/doc/source/mailing-list.rst b/doc/source/mailing-list.rst new file mode 100644 index 00000000..2fec34ea --- /dev/null +++ b/doc/source/mailing-list.rst @@ -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 `__ + with ``[charms]`` tag. +- Usage discussions: + `openstack-operators@lists.openstack.org `__ + with ``[charms]`` tag. diff --git a/doc/source/making-a-change.rst b/doc/source/making-a-change.rst new file mode 100644 index 00000000..f7d6eeb1 --- /dev/null +++ b/doc/source/making-a-change.rst @@ -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 + 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. diff --git a/doc/source/meetings.rst b/doc/source/meetings.rst new file mode 100644 index 00000000..8b43df12 --- /dev/null +++ b/doc/source/meetings.rst @@ -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 + `_ on ``#ubuntu-meeting`` on freenode + +Agenda +====== + +https://etherpad.openstack.org/p/openstack-charms-weekly-meeting-20160622 + +Previous meetings +================= + +Meeting organizers +================== diff --git a/doc/source/new-charm.rst b/doc/source/new-charm.rst new file mode 100644 index 00000000..4baf5a2c --- /dev/null +++ b/doc/source/new-charm.rst @@ -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 `__ service. + +The charm will use prewritten Openstack `layers and interfaces `__. + +Once the charm is written it will be composed using `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 `__ +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 /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. diff --git a/doc/source/testing.rst b/doc/source/testing.rst new file mode 100644 index 00000000..3560d340 --- /dev/null +++ b/doc/source/testing.rst @@ -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 `__; +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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c35a7dce --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..78d0770b --- /dev/null +++ b/setup.cfg @@ -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 diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..c0a24eab --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import setuptools + +setuptools.setup( + setup_requires=['pbr'], + pbr=True) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..c3add8dc --- /dev/null +++ b/tox.ini @@ -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