The ModuleManager¶
The next class that we will look at is the ModuleManager
class.
The previous section stated that the ModuleManager
is considered the
traffic cop of the module. It is also the main ingress point into all future work that
the module can take.
This class can take several forms. The most typical form though is a rather simple form
of the Command pattern. This is the type of ModuleManager
that is used in this
module.
Other forms that the manager can take vary with the needs of the module. A common alternative
of the Command pattern in the ModuleManager
is the use of the Factory pattern.
The Factory pattern, for example, is used in a number of GTM modules. It is a go-to pattern for cases where the F5 module developers need to create one module to support a variety of versions of an F5 product. For example, the major API changes that occurred in GTM between versions 11.x and 12.x.
The ModuleManager
class is usually where the specifics of your code will be. The f5ansible
command will create a generic version of this for you. It is your responsibility to change
the API calls as needed.
Versions of the coding standards¶
This would be a good point in time to discuss how the ModuleManager
implementation (and
a number of other classes) have changed in design over time. This gives some history to the
story, and insight into what has been done in the past.
Below are examples of the different versions of the design standards that have existed at one point or another:
Note
The ModuleManager
class will change over time as design standards change. The above
examples are for historical reference and training.
Most modules these days will “look” like version 3.1.1 (as of the time of this writing). The F5 module developers expect this to change as their needs change.
When a change in the coding standards happens, work must be undertaken to ensure that all modules are brought up to spec. This is an arduous task that will get more difficult as time goes on and the total number of F5 modules for Ansible increases.
Nevertheless, it is more difficult to maintain the F5 Modules for Ansible code base if the modules are each in varying states of convention.
ModuleManager for bigip_policy_rule¶
The ModuleManager
for this module is shown in its entirety at this link. For this tutorial, you should
reproduce this class in your own module file.
The remainder of this content covers several things.
- The changes to make to this module to reflect the required functionality.
- Explanations of the different methods in the class and how they relate to the
ModuleManager
’s responsibilities. - Conventions that are allowed when you want to extend the functionality of the manager.
- An example of the Factory
ModuleManager
and how the flow of a module changes to accommodate the pattern.
The common methods¶
The following methods are part of the boilerplate that is generated by the f5ansible
command.
The __init__ method¶
This method initializes the ModuleManager
class. In this method, a couple of
important things are initialized.
A number of variables are set. These variables are used throughout the module.
Variable | Instance Of | Purpose |
---|---|---|
self.module |
AnsibleModule |
The self.module variable contains the AnsibleModule
object. This allows the ModuleManager class to have
access to the parameters that were sent to the module. |
self.client |
F5Client |
The self.client variable contains your copy of the
connection to the F5 product. This variable is used
extensively in the *_on_device and *_from_device
methods that are discussed later. |
self.want |
ModuleParameters |
The self.want variable is a common theme across a
number of areas of the module, the Difference and
ModuleManager classes in particular. This object
exposes all of the parameters sent to the module, as
properties of the self.want object. You will refer to
this module for all future operations that require you to
get the data that was sent to you by the user. It is the
configuration that the user “wants.” |
self.have |
ApiParameters |
The self.have variable is similar to the self.want
variable. It, too, is a collection of parameters that are
available as properties. The difference is that, while the
self.want variable is what the user wants, the
self.have variable contains the configuration that the
system already has. With these two variables, you are
able to do all of the comparison needed to support updating
resources on the F5 products. |
self.changes |
UsableChanges |
The self.changes variable contains a copy of the
usable changes that will be discovered by the
Difference class. When the Difference class is
used, it generates a number of properties. These properties
usually match the properties that you’ve been working with
in the self.want and self.have objects. With
self.changes , the module gives you one last opportunity
to make sure that your API values are formatted as you want
them to be. It is this object that is then usually
delivered to the API. |
The _update_changed_options method¶
This method invokes the Difference
class to check for updates.
Use this method when requesting an update to an existing resource. When this method runs,
the self.changes
object is updated with the changes needed when updating the API.
A key point about this method is the way the results of diff’ing parameters are interpreted. The
return value of a compare
operation of the Difference
class is
either a scalar value or a dictionary of values.
If a scalar value is returned, it is just associated with the key that matches the parameter being compared. For example:
changed[k] = change
Where changed
is a dictionary of results that will become the UsableChanges
.
If a dictionary value is returned, it is merged into the changed
dictionary. Each key
and value of the returned dictionary becomes a key and value of the changed
dictionary.
For example:
changed.update(change)
The should_update method¶
This method is very similar to the _update_changed_options
, except this method is used when
creating a new resource only. In this case, there is no Difference
class that
you need to invoke.
The exec_module method¶
This method is the only ingress point into the execution of the module. All modules run this command to begin execution.
The exact implementation of this method can vary from module to module, however, what usually
changes is only the if...else
statement in the body of it. Other implementations may
have more states, or even no states.
Just remember that this is the ingress point to all module execution.
The _announce_deprecations method¶
The purpose of this method is to notify the user of the module (via the Playbook) that a feature they are using is deprecated.
The present method¶
This is a simple method that directs the execution of the module based on whether the
requested resource needs to be created or updated. This method is usually called from
the exec_module
method.
The exists method¶
This is the first method you will change.
The purpose of this method is to determine if a resource currently exists. You must change this method to reflect the APIs of the module that you are writing. During unit testing, you will be stubbing out this module because you will be driving code paths and have no need to communicate with a real device.
The update method¶
This method is responsible for dealing with update-specific logic. It is the last chance you have before you drop the API data on the wire to send to the remote F5 device.
The content of this module usually changes for each module (and is expected to change). Therefore, this is another method that you will change from the default boilerplate implementation.
This method wraps the update_on_device
method.
The remove method¶
This method is responsible for dealing with resource removal-specific logic. It is the last chance that you have before the request to delete the resource is dropped on the wire.
The content of this module usually changes for each module (and is expected to change). Therefore, this is another method you will want to change from the default boilerplate implementation.
This method wraps the remove_from_device
method.
The create method¶
This method is responsible for dealing with resource creation specific logic. It is the last chance that a module developer has before the request to create the resource is dropped on the wire.
The content of this module usually changes for each module (and is expected to change). Therefore, this will be another method that you will want to change from the default boilerplate implementation.
This method wraps the create_on_device
method.
The absent method¶
This is a simple method that directs the execution of the module based on whether the
requested resource needs to be deleted or not. This method is usually called from
the exec_module
method.
The create_on_device method¶
This method is one of the major override points in a module. This method must be customized to reflect the APIs required by your module for resource creation.
The update_on_device method¶
This method is one of the major override points in a module. This method must be customized to reflect the APIs required by your module for resource updating.
The remove_from_device method¶
This method is one of the major override points in a module. This method must be customized to reflect the APIs required by your module for resource removal.
The read_current_from_device method¶
This method is one of the major override points in a module. This method must be customized to reflect the APIs required by your module for fetching resource details from the remote device.
This method returns a copy of the ApiParameters
, or similar, class.
The other methods¶
The remaining methods in the ModuleManager
class are specific to this module. They are
supporting methods, whose purpose is to make the developer’s task easier in implementing a
particular piece of functionality.
Usually, these custom methods are prefixed with an underscore (_
) character, such as in the
following methods.
_create_existing_policy_draft_on_device
Note
While this method seems to be named after the special *_on_device
methods mentioned earlier,
it is in fact a module-specific method. The other *_on_device
methods mentioned earlier are part of the common ModuleManager
class and are stubbed out for
you. The above method is not stubbed out for you and you would need to add it.
Other times, these methods have no underscore, such as in the following methods.
draft_exists
publish_on_device
Again, just like the underscored methods, the non-underscored methods (while they have
a similar *_on_device
naming scheme) are not considered core methods. The important
point is to use the *_on_device
pattern when you need to communicate with the
remote F5 device.
Conclusion¶
By now, the ModuleManager
class you have been working with should be fleshed out. This
class is the core point of control in the module. It contains the only ingress point in
the execution of the module. It also contains all the other integration points with all the
other classes in the module.
The next section explores the classes related to parameters.