f507465c9e
Change-Id: Iebcd45d0eae79ab3911fd97c63d17b42d238f875
14 KiB
14 KiB
Development Guide for Ansible OpenStack Collection
Ansible OpenStack collection is a set of Ansible modules for interacting with the OpenStack API as either an admin or an end user.
We, and the OpenStack community in general, use OpenDev for its development. Patches are submitted to OpenDev Gerrit. Pull requests submitted through GitHub will be ignored. Please read OpenStack's Developer Workflow for details.
For hacking on the Ansible OpenStack collection it helps to prepare a DevStack environment first.
Hosting
- Bug tracker
- Mailing list
openstack-discuss@lists.openstack.org
. Prefix subjects with[aoc]
or[aco]
for faster responses. - Code Hosting
- Code Review
Branches
For rationale behind our master
and stable/1.0.0
branches and details on our relation to openstacksdk, please read our branching docs.
Examples
- For an example on how to write a
*_info
module, have a look at modulesopenstack.cloud.identity_role_info
oropenstack.cloud.neutron_rbac_policies_info
. - For an example on how to write a regular non-
*_info
module, have a look at moduleopenstack.cloud.federation_idp
or any other module which usesclass StateMachine
. - Do NOT use modules which define a
_system_state_change
function as examples, because they often do not properly define Ansible's check mode, idempotency and/or updates. Refer to modules which useclass StateMachine
. In cases where usingclass StateMachine
would cause code bloat, it might help to look at modules which define a_will_change
function instead.
Naming
- This collection is named
openstack.cloud
. There is no need for further namespace prefixing. - Name any module that a cloud consumer would expect from openstackclient (OSC), for example
server
instead ofnova
. This naming convention acknowledges that the end user does not care which service manages the resource - that is a deployment detail. For example, cloud consumers may not know whether their floating ip address are managed by Nova or Neutron.
Interface
- If the resource being managed has an
id
, it should be returned. - If the resource being managed has an associated object more complex than an
id
, that should be returned instead of theid
. - Modules should return a value of type
dict
,list
or other primitive data types. For example,floating_ips
inself.exit_json(changed=False, floating_ips=floating_ips)
should to be a list ofdict
s. Useto_dict()
on openstacksdk objects to convert resources to dictionaries. Setting its parameters such ascomputed
toFalse
will drop computed attributes from the resulting dict. Readto_dict
's docstring for more parameters. - Module results have to be documented in
RETURN
docstring. - We should document which attribute cannot be updated in
DOCUMENTATION
variable. For example, insert'This attribute cannot be updated.'
toDOCUMENTATION
like we did for theserver
module and others. - Sorting module options in
DOCUMENTATION
, attributes inRETURN
, entries inargument_spec
and expected fields in integration tests will make reviewing easier and faster.
Interoperability
- It should be assumed that the cloud consumer does not know details about the deployment choices their cloud provider made. A best effort should be made to present one sane interface to the Ansible user regardless of deployer choices.
- It should be assumed that a user may have more than one cloud account that they wish to combine as part of a single Ansible-managed infrastructure.
- All modules should work appropriately against all existing versions of OpenStack regardless of upstream EOL status. The reason for this is that the Ansible modules are for consumers of cloud APIs who are not in a position to impact what version of OpenStack their cloud provider is running. It is known that there are OpenStack Public Clouds running rather old versions of OpenStack, but from a user point of view the Ansible modules can still support these users without impacting use of more modern versions.
Coding Guidelines
- Modules should
- be idempotent (not being idempotent requires a solid reason),
- return whether something has
changed
, - support
check mode
, - be based on (be subclasses of)
OpenStackModule
inansible_collections.openstack.cloud.plugins.module_utils.openstack
, - should include
extends_documentation_fragment: openstack
in theirDOCUMENTATION
docstring, - be registered in
meta/action_groups.yml
for enabling the variables to be set in group level.
- Complex functionality, cloud interaction or interoperability code should be moved to openstacksdk.
- OpenStack API interactions should happen via openstacksdk and not via OpenStack component libraries. The OpenStack component libraries do no have end users as a primary audience, they are for intra-server communication.
- When a resource exist and should be deleted (absent), then pass the resource to the
delete_*
function, not its name. Passing a name requires openstacksdk to find that resource again, doing a unnecessary api call, because we queried the resource before. *_info
modules never raise exceptions when resources cannot be found. When resources cannot be found, then a*_info
module returns an empty list instead. For example, moduleopenstack.cloud.neutron_rbac_policies_info
will return an empty list when no project with name given in module parameterproject
can be found.- When a id is given in
*_info
modules, then we do not need nor want extra code to handle that. Instead most openstacksdk resources allow to pass ids as query arguments to OpenStack API. For example,identity.identity_providers()
can be used for both cases: Where an id is given and where no id is given. No need to callget_identity_provider()
. EXAMPLES
docstring in modules (and Ansible's own modules) consist of a list of tasks. They do not contain YAML directives end marker line (---) and do not define playbooks (e.g. hosts keyword). They shall be simple, e.g. do not do fancy loops, heavy use of variables or use Ansible directives for no apparent reason such as ignore_errors or register.self.params.get('...')
can be replaced withself.params['...']
because parameters fromargument_spec
will always be inself.params
. If not defined differently, they have a default value ofNone
.- Writing code to check that some options cannot be updated and to fail if user still tries to update that value is most
often not worth it. It would require much more code to catch all cases where updates are impossible and we would have
to implement it consistently across modules. Atm we are fine with documenting which attribute cannot be updated in
DOCUMENTATION
variable. We could simply drop these checks and insert'This attribute cannot be updated.'
toDOCUMENTATION
like we did for the server module and others. - openstacksdk functions often accept IDs but no names, e.g.
find_address_scope()
andcreate_address_scope()
accept aproject_id
parameter. Most modules in our collection use names for finding resources, so we want to support the same for resources attributes such asproject_id
inAddressScope
. - Constraints for module parameters and error handling can often be implemented in
argument_spec
ormodule_kwargs
module_kwargs
allows to define dependencies between module options such asmutually_exclusive
,required_together
,required_if
etc.. - When using openstacksdk's
find_*
functions (self.conn.*.find_*
), then passignore_missing=False
instead of checking its return value and failing withself.fail_json()
if it isNone
. - Use module option names which match attribute names used in openstacksdk, e.g. use
is_shared
instead ofshared
. When refactoring modules, keep old option names as aliases to keep backward compatibility. Using openstacksdk names provides two benefits:- The module inputs and outputs do match, are consistent and thus the module is easier to use.
- Most code for filters and query arguments can be replaced with loops. This patch for floating_ip_info has some ideas for how to write loops.
- Use functions from openstacksdk's proxy layer instead of its cloud layer, if possible. For example,
use
self.conn.network.find_network()
, notself.conn.get_network()
. As a guideline use this decision tree:- If a functionality requires a single api call (to the OpenStack API), then use functions from openstacksdk's proxy layer.
- If a functionality requires several api calls (to the OpenStack API), e.g. when creating and attaching a floating ip to a server, then use functions from openstacksdk's cloud layer.
- When unsure which of openstacksdk's layers to use, then first go to proxy layer, then to its cloud layer and if this is not sufficient, then use its resource layer. Mainly, this applies to functions retrieving information, i.e. all calls where we get info about cloud resources should be changed to openstacksdk functions which return proxy resources.
- It is perfectly fine to use openstacksdk's cloud layer for functionality which is not provided by openstacksdk's
proxy layer. SDK's cloud layer is not going away.
For example,
list_*
functions from openstacksdk's cloud layer such assearch_users()
allow to filter retrieved results with function parameterfilters
. openstacksdk's proxy layer does not provide an equivalent and thus the use ofsearch_users()
is perfectly fine.
Testing
- Modules have to be tested with CI integration tests (if possible).
- Each module has a corresponding Ansible role containing integration tests in
ci/roles
directory. - Ensure role names of integration tests in
ci/roles
match the module names. Only exception are*_info
modules: Their integration tests are located in the same Ansible roles as their non-*_info
equivalents (to reduce redundant code). For example, tests for both modulesfederation_mapping
andfederation_mapping_info
can be found in rolefederation_mapping
. - Zuul CI jobs are defined in
.zuul.yaml
. - Add assertions on return values from Ansible modules in integration tests. For an example, refer to
ci/roles/floating_ip/tasks/main.yml
. We need those checks to validate return values from openstacksdk, which might change across releases. Adding those assertions will be done in minutes, while checking the output manually during code reviews takes much more time. - Our Zuul CI jobs will run
ansible-test
for sanity checking. - Use
tox -elinters_latest
to run various linters against your code.
Upload
- Study our Review Guidelines before submitting a patch.
- Use Gerrit's work-in-progress feature to mark the status of the patch. A minus workflow (-w) will be reset when a new patchset is uploaded and hence easy to miss.
- When you edit a patch, first rebase your patch on top of the current branch. Sometimes we replace code in all modules
which might cause merge conflicts for you otherwise. For example, we dropped all options with default values from
argument_spec
such asrequired=False
.
Release
Read Release Guide on how to publish new releases.
Permissions
- Only members of group
ansible-collections-openstack-core
are allowed to merge patches. - Only members of group
ansible-collections-openstack-release
are allowed to push tags and trigger our release jobansible-collections-openstack-release
in galaxy.yml. - Only members of
openstack
namespace in Ansible Galaxy are allowed to apply changes to meta properties of Ansible collectionopenstack.cloud
on Ansible Galaxy.