oVirt Ansible Modules

This is a set of modules for interacting with oVirt/RHV. This documentserves as developer coding guidelines for creating oVirt/RHV modules.

Naming

  • All modules should start with an ovirt_ prefix.
  • All modules should be named after the resource it manages in singularform.
  • All modules that gather information should have a _infosuffix.

Interface

  • Every module should return the ID of the resource it manages.
  • Every module should return the dictionary of the resource it manages.
  • Never change the name of the parameter, as we guarantee backwardcompatibility. Use aliases instead.
  • If a parameter can’t achieve idempotency for any reason, pleasedocument it.

Interoperability

  • All modules should work against all minor versions ofversion 4 of the API. Version 3 of the API is not supported.

Libraries

  • All modules should use ovirt_full_argument_spec orovirt_info_full_argument_spec to pick up the standard input (suchas auth and fetch_nested).
  • All modules should use extends_documentation_fragment: ovirt to goalong with ovirt_full_argument_spec.
  • All info modules should use extends_documentation_fragment:ovirt_info to go along with ovirt_info_full_argument_spec.
  • Functions that are common to all modules should be implemented in themodule_utils/ovirt.py file, so they can be reused.
  • Python SDK version 4 must be used.

New module development

Please read Should you develop a module?,first to know what common properties, functions and features every module musthave.

In order to achieve idempotency of oVirt entity attributes, a helper classwas created. The first thing you need to do is to extend this class and override a fewmethods:

  1. try:
  2. import ovirtsdk4.types as otypes
  3. except ImportError:
  4. pass
  5.  
  6. from ansible.module_utils.ovirt import (
  7. BaseModule,
  8. equal
  9. )
  10.  
  11. class ClustersModule(BaseModule):
  12.  
  13. # The build method builds the entity we want to create.
  14. # Always be sure to build only the parameters the user specified
  15. # in their yaml file, so we don't change the values which we shouldn't
  16. # change. If you set the parameter to None, nothing will be changed.
  17. def build_entity(self):
  18. return otypes.Cluster(
  19. name=self.param('name'),
  20. comment=self.param('comment'),
  21. description=self.param('description'),
  22. )
  23.  
  24. # The update_check method checks if the update is needed to be done on
  25. # the entity. The equal method doesn't check the values which are None,
  26. # which means it doesn't check the values which user didn't set in yaml.
  27. # All other values are checked and if there is found some mismatch,
  28. # the update method is run on the entity, the entity is build by
  29. # 'build_entity' method. You don't have to care about calling the update,
  30. # it's called behind the scene by the 'BaseModule' class.
  31. def update_check(self, entity):
  32. return (
  33. equal(self.param('comment'), entity.comment)
  34. and equal(self.param('description'), entity.description)
  35. )

The code above handle the check if the entity should be updated, so wedon’t update the entity if not needed and also it construct the neededentity of the SDK.

  1. from ansible.module_utils.basic import AnsibleModule
  2. from ansible.module_utils.ovirt import (
  3. check_sdk,
  4. create_connection,
  5. ovirt_full_argument_spec,
  6. )
  7.  
  8. # This module will support two states of the cluster,
  9. # either it will be present or absent. The user can
  10. # specify three parameters: name, comment and description,
  11. # The 'ovirt_full_argument_spec' function, will merge the
  12. # parameters created here with some common one like 'auth':
  13. argument_spec = ovirt_full_argument_spec(
  14. state=dict(
  15. choices=['present', 'absent'],
  16. default='present',
  17. ),
  18. name=dict(default=None, required=True),
  19. description=dict(default=None),
  20. comment=dict(default=None),
  21. )
  22.  
  23. # Create the Ansible module, please always implement the
  24. # feautre called 'check_mode', for 'create', 'update' and
  25. # 'delete' operations it's implemented by default in BaseModule:
  26. module = AnsibleModule(
  27. argument_spec=argument_spec,
  28. supports_check_mode=True,
  29. )
  30.  
  31. # Check if the user has Python SDK installed:
  32. check_sdk(module)
  33.  
  34. try:
  35. auth = module.params.pop('auth')
  36.  
  37. # Create the connection to the oVirt engine:
  38. connection = create_connection(auth)
  39.  
  40. # Create the service which manages the entity:
  41. clusters_service = connection.system_service().clusters_service()
  42.  
  43. # Create the module which will handle create, update and delete flow:
  44. clusters_module = ClustersModule(
  45. connection=connection,
  46. module=module,
  47. service=clusters_service,
  48. )
  49.  
  50. # Check the state and call the appropriate method:
  51. state = module.params['state']
  52. if state == 'present':
  53. ret = clusters_module.create()
  54. elif state == 'absent':
  55. ret = clusters_module.remove()
  56.  
  57. # The return value of the 'create' and 'remove' method is dictionary
  58. # with the 'id' of the entity we manage and the type of the entity
  59. # with filled in attributes of the entity. The 'change' status is
  60. # also returned by those methods:
  61. module.exit_json(**ret)
  62. except Exception as e:
  63. # Modules can't raises exception, it always must exit with
  64. # 'module.fail_json' in case of exception. Always use
  65. # 'exception=traceback.format_exc' for debugging purposes:
  66. module.fail_json(msg=str(e), exception=traceback.format_exc())
  67. finally:
  68. # Logout only in case the user passed the 'token' in 'auth'
  69. # parameter:
  70. connection.close(logout=auth.get('token') is None)

If your module must support action handling (for example,virtual machine start) you must ensure that you handle the states of thevirtual machine correctly, and document the behavior of themodule:

  1. if state == 'running':
  2. ret = vms_module.action(
  3. action='start',
  4. post_action=vms_module._post_start_action,
  5. action_condition=lambda vm: (
  6. vm.status not in [
  7. otypes.VmStatus.MIGRATING,
  8. otypes.VmStatus.POWERING_UP,
  9. otypes.VmStatus.REBOOT_IN_PROGRESS,
  10. otypes.VmStatus.WAIT_FOR_LAUNCH,
  11. otypes.VmStatus.UP,
  12. otypes.VmStatus.RESTORING_STATE,
  13. ]
  14. ),
  15. wait_condition=lambda vm: vm.status == otypes.VmStatus.UP,
  16. # Start action kwargs:
  17. use_cloud_init=use_cloud_init,
  18. use_sysprep=use_sysprep,
  19. # ...
  20. )

As you can see from the preceding example, the action method accepts the action_condition andwait_condition, which are methods which accept the virtual machineobject as a parameter, so you can check whether the virtualmachine is in a proper state before the action. The rest of theparameters are for the start action. You may also handle pre-or post- action tasks by defining pre_action and post_actionparameters.

Testing

  • Integration testing is currently done in oVirt’s CI systemon Jenkinsandon GitHub.
  • Please consider using these integration tests if you create a new module or add a new feature to an existingmodule.