Most of the time in Ansible, the default strategy and forks will work well. However, there are some race conditions or flow control issues. The strategy
and serial
play keywords can provide controls to avoid such issues. The following example will demonstrate specifically how serializing a task will help avoid a race condition.
Oftentimes getting the next resource, such as an IP address, VLAN, or subnet is a two-step process. The first step is to get the list of next available resources and the second step will actually assign that resource. The below example is somewhat contrived, as the netbox_ip_address
can already complete this in a single step, but will work for the purposes of this demonstration.
Given the following playbook, you can see the two-step process between the first 2 tasks.
---
- name: "GET VERSION INFORMATION"
hosts: "all"
gather_facts: "no"
connection: "local"
vars:
prefix_id: "15"
tasks:
- name: "GET NEXT AVAILABLE IP ADDRESSES"
uri:
url: "http://127.0.0.1:8000/api/ipam/prefixes/{{ prefix_id }}/available-ips/"
method: "GET"
headers:
authorization: "Token {{ token }}"
status_code: 200
register: "available_ip"
- name: "ASSIGN A SPECIFIC IP FROM NETBOX"
netbox.netbox.netbox_ip_address:
netbox_url: 'http://netbox:8000'
netbox_token: "{{ token }}"
data:
address: "{{ available_ip['json'][0]['address'] }}"
interface:
name: "GigabitEthernet2"
device: "{{ inventory_hostname }}"
state: "present"
validate_certs: false
register: "msg"
- name:
debug:
var: msg['msg']
When the playbook runs, Ansible flow control performs the same query at nearly the same moment, and then assigns the IP address based on that returned value. In this case, it is now going to set the same value multiple times.
root@4625fa37f36f:/ntc# ansible-playbook -i inventory pb_get_next_ip.yml
PLAY [GET VERSION INFORMATION] *****************************************************************************************************************************************************************************
TASK [GET NEXT AVAILABLE IP ADDRESSES] **********************************************************************************************************************************************************************
ok: [csr2]
ok: [csr1]
TASK [ASSIGN A SPECIFIC IP FROM NETBOX] ********************************************************************************************************************************************************************
changed: [csr2]
changed: [csr1]
TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [csr1] => {
"msg['msg']": "ip_address 10.40.0.1/24 created"
}
ok: [csr2] => {
---
"msg['msg']": "ip_address 10.40.0.1/24 created"
}
PLAY RECAP *************************************************************************************************************************************************************************************************
csr1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
csr2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
root@4625fa37f36f:/ntc#
As noted in the debug statement, the same IP address was set. Checking the NetBox GUI, we can confirm this to be the case
Without using the serial
keyword in the play definition, the ordering is roughly the following:
By using the serial
keyword within the play definition, the order can be changed in which the tasks are executed.
---
- name: "GET VERSION INFORMATION"
hosts: "all"
gather_facts: "no"
connection: "local"
serial: 1
vars:
prefix_id: "15"
tasks:
With serial, the ordering is now:
Re-running the playbook, we can now get the desired effect.
root@4625fa37f36f:/ntc# ansible-playbook -i inventory pb_get_next_ip.yml
PLAY [GET VERSION INFORMATION] *****************************************************************************************************************************************************************************
TASK [GET NEXT AVAILABLE IP ADDRESSES] *********************************************************************************************************************************************************************
ok: [csr1]
TASK [ASSIGN A SPECIFIC IP FROM NETBOX] ********************************************************************************************************************************************************************
changed: [csr1]
TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [csr1] => {
"msg['msg']": "ip_address 10.40.0.1/24 created"
}
PLAY [GET VERSION INFORMATION] *****************************************************************************************************************************************************************************
TASK [GET NEXT AVAILABLE IP ADDRESSES] *********************************************************************************************************************************************************************
ok: [csr2]
TASK [ASSIGN A SPECIFIC IP FROM NETBOX] ********************************************************************************************************************************************************************
changed: [csr2]
TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [csr2] => {
"msg['msg']": "ip_address 10.40.0.2/24 created"
}
PLAY RECAP *************************************************************************************************************************************************************************************************
csr1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
csr2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
root@4625fa37f36f:/ntc#
As indicated by the message, CSR2
did in fact correctly get the second IP address.
The flow control for out of the box Ansible doesn’t work for every use case. However, the constructs can be modified based on the actual use case and desired outcome.
-Ken
Share details about yourself & someone from our team will reach out to you ASAP!