Parts two and three of this series looked at passing input parameters into playbooks, and how to work with the Automate workspace. This article discusses how an Ansible playbook can be effectively used in a state machine.

Working with State Machines

A state machine is a specially constructed Automate class that comprises a sequence of states. Each state typically runs a Ruby or Ansible playbook method, and progression through the state machine is only achieved as each state completes successfully. Individual states can be periodically retried, which gives a state machine the ability to initiate a long-running asynchronous task, followed by a retrying check-completed stage to monitor for task completion.

A state machine is the best way to model a workflow in ManageIQ Automate.

State Variables

The Ruby and Ansible playbook methods in a state machine can share values by using state variables (often referred to as state_vars). State variables persist throughout the life of an active state machine — even between state retries — whereas attributes saved to $evm.root are more ephemeral and do not persist if a state schedules a retry of itself.

As mentioned in part one of this series there are four functions in the manageiq-automate role that can be used to access state variables when an Ansible playbook method is running as part of a state machine. These are:

get_state_var
set_state_var
state_var_exists
get_state_var_names

For illustration of the use of these functions, a prior Ruby method in the state machine is assumed to have defined two state variables using the following lines:

$evm.set_state_var(:request_id, '8088')
$evm.set_state_var(:task_id, '8086')


get_state_var

The get_state_var function can be used to get the value of a previously saved state variable, for example:

- name: Get the value of the "request_id" state var (get_state_var)
  manageiq_automate:
    workspace: "{{ workspace }}"
    get_state_var:
      attribute: "request_id"
  register: state_var
- debug:
    msg: Result = {{ state_var.value }}

The output is as follows:

TASK [Get the value of the "request_id" state var (get_state_var)] *************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "Result = 8088"
}


set_state_var

The set_state_var function can be used to set a state variable, for example:

- name: Set a "job_id" state var (set_state_var)
  manageiq_automate:
    workspace: "{{ workspace }}"
    set_state_var:
      attribute: "job_id"
      value: "68008"

A subsequent Ruby method in the state machine can retrieve the saved state_var in the following way:

$evm.log(:info, "Saved job_id from playbook = #{$evm.get_state_var(:job_id)}")

...<AEMethod step3> Saved job_id from playbook = 68008


state_var_exists

The state_var_exists function tests whether a state variable has been defined, and returns a boolean result accordingly, as follows:

- name: Check whether a "task_id" state var exists (state_var_exists)
  manageiq_automate:
    workspace: "{{ workspace }}"
    state_var_exists:
      attribute: "task_id"
  register: state_var_exists
- debug: msg="Result:"

The output is as follows:

TASK [Check whether a "task_id" state var exists (state_var_exists)] ***********
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "Result:True"
}


get_state_var_names

The get_state_var_names function can be used to get the list of state variables that have been defined so far in the state machine’s workflow, for example:

- name: Get the list of state vars (get_state_var_names)
  manageiq_automate:
    workspace: "{{ workspace }}"
    get_state_var_names: yes
  register: get_state_var_names
- debug: msg="Result:"

The output is as follows:

TASK [Get the list of state vars (get_state_var_names)] ************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "Result:['job_id', 'task_id', 'request_id']"
}


State Retries

A state retry is triggered by the Ruby or Ansible playbook method running in that state. An Ansible playbook method can force a state retry using the set_retry function, for example:

 tasks:
   - name: Get the value of ae_state_retries
     manageiq_automate:
       workspace: "{{ workspace }}"
       get_attribute:
         object: root
         attribute: "ae_state_retries"
       register: ae_state_retries
   - debug:
       msg: State retry number 

   - name: Set state status to retry
     manageiq_automate:
       workspace: "{{ workspace }}"
       set_retry:
         interval: 60
     when: ae_state_retries.value|int < 3


Passing Values Between Successive Playbook or Ruby Methods in ManageIQ Ivanchuk (5.11.0.28)

Ivanchuk has added the capability for values to be saved from a playbook using the set_stats module so that any follow-on Ruby or Ansible methods in a state machine can also access them. The following code sample shows a playbook that has retrieved IP details which are in a hash called ipam_return. The example shows how the playbook can use set_stats to copy the hash keys and their values back to the state machine workspace.

- name: Save IPAM detail back to the workspace
  set_stats:
    data:
      ip_addr: "{{ ipam_return.ipaddress }}"
      netmask: "{{ ipam_return.netmask }}"
      gateway: "{{ ipam_return.gateway }}"

A follow-on Ruby method in the workflow can access these variables as state_vars prefixed by the string “ansible_stats_”, for example:

$evm.log(:info, "Value of ip_addr from Ansible = #{$evm.get_state_var('ansible_stats_ip_addr')}")
$evm.log(:info, "Value of netmask from Ansible = #{$evm.get_state_var('ansible_stats_netmask')}")
$evm.log(:info, "Value of gateway from Ansible = #{$evm.get_state_var('ansible_stats_gateway')}")

...<AEMethod state_2> Value of ip_addr from Ansible = 192.168.1.44
...<AEMethod state_2> Value of netmask from Ansible = 255.255.255.0
...<AEMethod state_2> Value of gateway from Ansible = 192.168.1.254


Any state machine state_vars that are prefixed by the string “ansible_stats_” will be automatically injected into any follow-on playbook method as extra_vars (without the “ansible_stats_” prefix). For example, a follow-on Ansible playbook can access these example IPAM-related variables as normal “{{ var }}” style variables as follows:

- name: print ip_addr
  debug:
    msg: "ip_addr is {{ ip_addr }}"
- name: print netmask
  debug:
    msg: "netmask is {{ netmask }}"
- name: print gateway
   debug:
    msg: "gateway is {{ gateway }}"


TASK [print ip_addr] ***********************************************************
ok: [localhost] => {
    "msg": "ip_addr is 192.168.1.44"
}

TASK [print netmask] ***********************************************************
ok: [localhost] => {
    "msg": "netmask is 255.255.255.0"
}

TASK [print gateway] ***********************************************************
ok: [localhost] => {
    "msg": "gateway is 192.168.1.254"
}

Summary

This article has shown how embedded Ansible playbooks can form an integral part of a ManageIQ automation workflow that uses a state machine.