Improvements to structure
This commit is contained in:
parent
78d7769bd9
commit
e038338e9f
2 changed files with 122 additions and 66 deletions
|
@ -1,10 +1,51 @@
|
|||
Developing ACI modules
|
||||
----------------------
|
||||
This is a brief walk-through of how to create new ACI modules for Ansible.
|
||||
.. _aci_dev_guide:
|
||||
|
||||
Imports
|
||||
.......
|
||||
The following imports are standard across modules:
|
||||
****************************
|
||||
Developing Cisco ACI modules
|
||||
****************************
|
||||
This is a brief walk-through of how to create new Cisco ACI modules for Ansible.
|
||||
|
||||
For more information about Cisco ACI, look at the :ref:`Cisco ACI user guide <aci_guide>`.
|
||||
|
||||
What's covered in this section:
|
||||
|
||||
.. contents::
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
|
||||
.. _aci_dev_guide_intro:
|
||||
|
||||
Introduction
|
||||
============
|
||||
Ansible already ships with a large collection of Cisco ACI modules, however the ACI object model is huge and covering all possible functionality would easily cover more than 1500 individual modules.
|
||||
|
||||
If you are in need of specific functionality, you have 2 options:
|
||||
|
||||
- Learn the ACI object model and use the low-level APIC REST API using the :ref:`aci_rest <aci_rest_module>` module
|
||||
- Write your own dedicated modules, which is actually quite easy
|
||||
|
||||
.. seealso::
|
||||
|
||||
`ACI Fundamentals: ACI Policy Model <https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/1-x/aci-fundamentals/b_ACI-Fundamentals/b_ACI-Fundamentals_chapter_010001.html>`_
|
||||
A good introduction to the ACI object model.
|
||||
`APIC Management Information Model reference <https://developer.cisco.com/docs/apic-mim-ref/>`_
|
||||
Complete reference of the APIC object model.
|
||||
`APIC REST API Configuration Guide <https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/2-x/rest_cfg/2_1_x/b_Cisco_APIC_REST_API_Configuration_Guide.html>`_
|
||||
Detailed guide on how the APIC REST API is designed and used, incl. many examples.
|
||||
|
||||
|
||||
So let's look at how a typical ACI module is built up.
|
||||
|
||||
|
||||
.. _aci_dev_guide_module_structure:
|
||||
|
||||
ACI module structure
|
||||
====================
|
||||
|
||||
Importing objects from Python libraries
|
||||
---------------------------------------
|
||||
The following imports are standard across ACI modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -12,21 +53,19 @@ The following imports are standard across modules:
|
|||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
Argument Spec
|
||||
.............
|
||||
The first line adds the standard connection parameters to the module. After that, the next section will update the ``argument_spec`` dictionary with module specific parameters. The module specific parameters should include:
|
||||
Defining the argument spec
|
||||
--------------------------
|
||||
The first line adds the standard connection parameters to the module. After that, the next section will update the ``argument_spec`` dictionary with module-specific parameters. The module-specific parameters should include:
|
||||
|
||||
* the object_id (usually the name)
|
||||
* configurable properties of the object
|
||||
* the configurable properties of the object
|
||||
* the parent object IDs (all parents up to the root)
|
||||
* only child classes that are a 1-to-1 relationship (1-to-many/many-to-many require their own module to properly manage)
|
||||
* state
|
||||
* the state
|
||||
|
||||
+ `present` to ensure the object and configs exist; this is also the default
|
||||
|
||||
+ `absent` to ensure object does not exist
|
||||
|
||||
+ `query` to retrieve information about objects in the class
|
||||
+ ``state: absent`` to ensure object does not exist
|
||||
+ ``state: present`` to ensure the object and configs exist; this is also the default
|
||||
+ ``state: query`` to retrieve information about objects in the class
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -40,15 +79,15 @@ The first line adds the standard connection parameters to the module. After that
|
|||
parent_id=dict(type='str'),
|
||||
child_object_id=dict(type='str'),
|
||||
child_object_prop=dict(type='str'),
|
||||
state=dict(type='str', choices=['absent', 'present', 'query'], default='present'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
|
||||
.. hint:: It is important to point out that all configuration arguments should not have a default value, as that could cause unintended changes to the object.
|
||||
.. hint:: Do not provide default values for configuration arguments. Default values could cause unintended changes to the object.
|
||||
|
||||
AnsibleModule
|
||||
.............
|
||||
The following section creates an AnsibleModule instance. The module should support check_mode, so we pass the ``argument_spec`` and ``supports_check_mode`` arguments. Since these modules support querying the APIC for all objects of the module's class, the object/parent IDs should only be required if `state` is present or absent.
|
||||
Using the AnsibleModule object
|
||||
------------------------------
|
||||
The following section creates an AnsibleModule instance. The module should support check-mode, so we pass the ``argument_spec`` and ``supports_check_mode`` arguments. Since these modules support querying the APIC for all objects of the module's class, the object/parent IDs should only be required if ``state: absent`` or ``state: present``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -62,9 +101,9 @@ The following section creates an AnsibleModule instance. The module should suppo
|
|||
)
|
||||
|
||||
|
||||
Variable Definition
|
||||
...................
|
||||
Once the AnsibleModule object has been initiated, the necessary parameter values should be extracted from ``params`` and any data validation should be done. Usually the only params that need to be extracted are those related to the ACI object configuration and it's child configuration. If you have integer objects that you would like to validate, then the validation should be done here, and the ``ACIModule.payload()`` method will handle the str conversion.
|
||||
Mapping variable definition
|
||||
---------------------------
|
||||
Once the AnsibleModule object has been initiated, the necessary parameter values should be extracted from ``params`` and any data validation should be done. Usually the only params that need to be extracted are those related to the ACI object configuration and its child configuration. If you have integer objects that you would like to validate, then the validation should be done here, and the ``ACIModule.payload()`` method will handle the string conversion.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -79,8 +118,8 @@ Once the AnsibleModule object has been initiated, the necessary parameter values
|
|||
state = module.params['state']
|
||||
|
||||
|
||||
ACIModule
|
||||
.........
|
||||
Using the ACIModule object
|
||||
--------------------------
|
||||
The ACIModule class handles most of the logic for the ACI modules. The ACIModule extends functionality to the AnsibleModule object, so the module instance must be passed into the class instantiation.
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -98,14 +137,14 @@ The ACIModule has six main methods that are used by the modules:
|
|||
|
||||
The first two methods are used regardless of what value is passed to the ``state`` parameter.
|
||||
|
||||
Construct URL
|
||||
;;;;;;;;;;;;;
|
||||
Constructing URLs
|
||||
^^^^^^^^^^^^^^^^^
|
||||
The ``construct_url()`` method is used to dynamically build the appropriate URL to interact with the object, and the appropriate filter string that should be appended to the URL to filter the results.
|
||||
|
||||
* When the ``state`` is not ``query``, the URL is the base URL to access the APIC plus the distinguished name to access the object. The filter string will restrict the returned data to just the configuration data.
|
||||
* When ``state`` is ``query``, the URL and filter string used depends on what parameters are passed to the object. This method handles the complexity so that it is easier to add new modules and so that all modules are consistent in what type of data is returned.
|
||||
|
||||
.. note:: Our design goal is to take all ID parameters that have values, and return the most specific data possible. If no ID parameters are supplied by the task, then all objects of the class will be returned. If all ID parameters are passed, then the data for the specific object is returned. If a partial set of ID parameters are passed, then the module will use the IDs that are passed to build the URL and filter strings appropriately.
|
||||
.. note:: Our design goal is to take all ID parameters that have values, and return the most specific data possible. If you do not supply any ID parameters to the task, then all objects of the class will be returned. If your task does consist of ID parameters sed, then the data for the specific object is returned. If a partial set of ID parameters are passed, then the module will use the IDs that are passed to build the URL and filter strings appropriately.
|
||||
|
||||
The ``construct_url()`` method takes 2 required arguments:
|
||||
|
||||
|
@ -152,51 +191,45 @@ The ``construct_url()`` method takes 4 optional arguments, the first three imita
|
|||
* child_classes - The list of APIC names for the child classes supported by the modules.
|
||||
|
||||
+ This is a list, even if it is a list of one
|
||||
|
||||
+ These are the unfriendly names used by the APIC
|
||||
|
||||
+ These are used to limit the returned child_classes when possible
|
||||
|
||||
+ Example: ``child_classes=['fvRsBDSubnetToProfile', 'fvRsNdPfxPol']``
|
||||
|
||||
.. note:: Sometimes the APIC will require special characters ([, ], and -) or will use object metadata in the name ("vlanns" for VLAN pools); the module should handle adding special characters or joining of multiple parameters in order to keep expected inputs simple.
|
||||
|
||||
Get Existing
|
||||
............
|
||||
Getting the existing configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Once the URL and filter string have been built, the module is ready to retrieve the existing configuration for the object:
|
||||
|
||||
* ``present`` retrieves the configuration to use as a comparison against what was entered in the task. All values that are different than the existing values will be updated.
|
||||
* ``absent`` uses the existing configuration to see if the item exists and needs to be deleted.
|
||||
* ``query`` uses this to perform the query for the task and report back the existing data.
|
||||
* ``state: present`` retrieves the configuration to use as a comparison against what was entered in the task. All values that are different than the existing values will be updated.
|
||||
* ``state: absent`` uses the existing configuration to see if the item exists and needs to be deleted.
|
||||
* ``state: query`` uses this to perform the query for the task and report back the existing data.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
aci.get_existing()
|
||||
|
||||
|
||||
State is Present
|
||||
................
|
||||
When the state is present, the module needs to perform a diff against the existing configuration and the task entries. If any value needs to be updated, then the module will make a POST request with only the items that need to be updated. Some modules have children that are in a 1-to-1 relationship with another object; for these cases, the module can be used to manage the child objects.
|
||||
When state is present
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
When ``state: present``, the module needs to perform a diff against the existing configuration and the task entries. If any value needs to be updated, then the module will make a POST request with only the items that need to be updated. Some modules have children that are in a 1-to-1 relationship with another object; for these cases, the module can be used to manage the child objects.
|
||||
|
||||
ACI Payload Method
|
||||
;;;;;;;;;;;;;;;;;;
|
||||
Building the ACI payload
|
||||
""""""""""""""""""""""""
|
||||
The ``aci.payload()`` method is used to build a dictionary of the proposed object configuration. All parameters that were not provided a value in the task will be removed from the dictionary (both for the object and its children). Any parameter that does have a value will be converted to a string and added to the final dictionary object that will be used for comparison against the existing configuration.
|
||||
|
||||
The ``aci.payload()`` method takes two required arguments and 1 optional argument, depending on if the module manages child objects.
|
||||
|
||||
* ``aci_class`` is the APIC name for the object's class, e.g. ``aci_class='fvBD'``
|
||||
|
||||
* ``class_config`` is the appropriate dictionary to be used as the payload for the POST request
|
||||
|
||||
+ The keys should match the names used by the APIC.
|
||||
|
||||
+ The values should be the corresponding value in ``module.params``; these are the variables defined above
|
||||
|
||||
* ``child_configs`` is optional, and is a list of child config dictionaries.
|
||||
|
||||
+ The child configs include the full child object dictionary, not just the attributes configuration portion.
|
||||
|
||||
+ The configuration portion is built the same way that the object is done.
|
||||
+ The configuration portion is built the same way as the object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -208,23 +241,29 @@ The ``aci.payload()`` method takes two required arguments and 1 optional argumen
|
|||
type=bd_type,
|
||||
),
|
||||
child_configs=[
|
||||
dict(fvRsCtx=dict(attributes=dict(tnFvCtxName=vrf))),
|
||||
dict(
|
||||
fvRsCtx=dict(
|
||||
attributes=dict(
|
||||
tnFvCtxName=vrf
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
Get the Config Diff and Make the POST Request
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
Performing the request
|
||||
""""""""""""""""""""""
|
||||
The ``get_diff()`` method is used to perform the diff, and takes only one required argument, ``aci_class``.
|
||||
Example: ``aci.get_diff(aci_class='fvBD')``
|
||||
|
||||
The ``post_config()`` method is used to make the POST request to the APIC if needed. This method doesn't take any arguments and handles check mode.
|
||||
Example: ``aci.post_config()``
|
||||
|
||||
Full Example
|
||||
;;;;;;;;;;;;
|
||||
|
||||
.. code-block:: python
|
||||
Example code
|
||||
""""""""""""
|
||||
.. code-block:: guess
|
||||
|
||||
if state == 'present':
|
||||
aci.payload(
|
||||
|
@ -252,21 +291,21 @@ Full Example
|
|||
aci.post_config()
|
||||
|
||||
|
||||
State is Absent
|
||||
...............
|
||||
When state is absent
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
If the task sets the state to absent, then the ``delete_config()`` method is all that is needed. This method does not take any arguments, and handles check mode.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: guess
|
||||
|
||||
elif state == 'absent':
|
||||
aci.delete_config()
|
||||
elif state == 'absent':
|
||||
aci.delete_config()
|
||||
|
||||
|
||||
Module Exit
|
||||
...........
|
||||
Exiting the module
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
To have the module exit, call the ACIModule method ``exit_json()``. This method automatically takes care of returning the common return values for you.
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: guess
|
||||
|
||||
aci.exit_json()
|
||||
|
||||
|
@ -274,11 +313,13 @@ To have the module exit, call the ACIModule method ``exit_json()``. This method
|
|||
main()
|
||||
|
||||
|
||||
.. _aci_dev_guide_testing:
|
||||
|
||||
Testing ACI library functions
|
||||
.............................
|
||||
=============================
|
||||
You can test your ``construct_url()`` and ``payload()`` arguments without accessing APIC hardware by using the following python script:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: guess
|
||||
|
||||
#!/usr/bin/python
|
||||
import json
|
||||
|
@ -349,16 +390,21 @@ This will result in:
|
|||
}
|
||||
|
||||
Testing for sanity checks
|
||||
.........................
|
||||
-------------------------
|
||||
You can run from your fork something like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ./test/runner/ansible-test sanity --python 2.7 lib/ansible/modules/network/aci/aci_tenant.py
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`testing_sanity`
|
||||
Information on how to build sanity tests.
|
||||
|
||||
|
||||
Testing ACI integration tests
|
||||
.............................
|
||||
-----------------------------
|
||||
You can run this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -381,9 +427,14 @@ You may want to edit the used inventory at *./test/integration/inventory.network
|
|||
[aci]
|
||||
localhost ansible_ssh_host=127.0.0.1 ansible_connection=local
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`testing_integration`
|
||||
Information on how to build integration tests.
|
||||
|
||||
|
||||
Testing for test coverage
|
||||
.........................
|
||||
-------------------------
|
||||
You can run this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
|
|
@ -67,6 +67,8 @@ For instance ensuring that a specific tenant exists, is done using the following
|
|||
|
||||
A complete list of existing ACI modules is available for the latest stable release on the :ref:`list of network modules <network_modules>`. You can also view the `current development version <https://docs.ansible.com/ansible/devel/modules/list_of_network_modules.html#aci>`_.
|
||||
|
||||
If you want to learn how to write your own ACI modules to contribute, look at the :ref:`Developing Cisco ACI modules <aci_dev_guide>` section.
|
||||
|
||||
Querying ACI configuration
|
||||
..........................
|
||||
|
||||
|
@ -261,6 +263,7 @@ More information
|
|||
................
|
||||
Various resources exist to start learn more about ACI programmability, we recommend the following links:
|
||||
|
||||
- :ref:`Developing Cisco ACI modules <aci_dev_guide>`
|
||||
- `Jacob McGill: Automating Cisco ACI with Ansible <https://blogs.cisco.com/developer/automating-cisco-aci-with-ansible-eliminates-repetitive-day-to-day-tasks>`_
|
||||
- `Cisco DevNet Learning Labs about ACI and Ansible <https://learninglabs.cisco.com/labs/tags/ACI,Ansible>`_
|
||||
|
||||
|
@ -330,7 +333,7 @@ You can automate this by using the following Ansible task:
|
|||
|
||||
aaa_user: admin
|
||||
certificate_name: admin
|
||||
certificate: "{{ lookup('file', 'pki/admin.crt') }}" # This wil read the certificate data from a local file
|
||||
certificate: "{{ lookup('file', 'pki/admin.crt') }}" # This will read the certificate data from a local file
|
||||
|
||||
.. note:: Signature-based authentication only works with local users.
|
||||
|
||||
|
@ -563,6 +566,8 @@ You will find our roadmap, an overview of open ACI issues and pull-requests, and
|
|||
|
||||
:ref:`List of ACI modules <aci_network_modules>`
|
||||
A complete list of supported ACI modules.
|
||||
:ref:`Developing Cisco ACI modules <aci_dev_guide>`
|
||||
A walkthough on how to develop new Cisco ACI modules to contribute back.
|
||||
`ACI community <https://github.com/ansible/community/wiki/Network:-ACI>`_
|
||||
The Ansible ACI community wiki page, includes roadmap, ideas and development documentation.
|
||||
:ref:`network_guide`
|
||||
|
|
Loading…
Reference in a new issue