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.