Bookmark this page

Lab: Implementing Task Control

Create a new workflow job template and modify the playbooks used to resolve issues, improve efficiency, and handle errors.

Outcomes

  • Create a workflow job template with "On Success" and "On Failure" nodes.

  • Use loops to write efficient tasks.

  • Use conditions to control where tasks run.

  • Use blocks with rescue and always sections to handle playbook errors.

As the student user on the workstation machine, use the lab command to prepare your system for this exercise.

This command creates Git repositories, creates some automation controller resources, and enables the Network Configuration Protocol (NETCONF) system service on the Juniper Junos managed node.

[student@workstation ~]$ lab start task-review

Instructions

This exercise provides you with playbooks to configure interface settings on a Cisco IOS XE and a Juniper Junos managed node in your environment. The playbooks have associated job templates created in automation controller for you to use to create a workflow job template. The playbooks do not work and you must fix them. After updating the playbooks, verify that the workflow job template runs successfully.

  1. Navigate to https://controller.lab.example.com to access the automation controller web UI. Log in as admin using redhat as the password.

  2. Create a workflow job template called Configure Interfaces with the following information. In the following steps, you create nodes for the workflow job template.

    Table 5.4. Workflow Template

    FieldValue
    Name Configure Interfaces
    Description Configure interfaces on network devices
    Organization Default
    Inventory Common

    1. Navigate to ResourcesTemplates and then click AddAdd workflow template.

    2. Create a workflow template using information from the "Workflow Template" table. When finished, click Save.

  3. Start the workflow for the Configure Interfaces workflow job template by synchronizing the Interfaces project. Create the first node using the following information:

    Table 5.5. Workflow Node #1

    FieldValue
    Node Type Project Sync
    Name Interfaces
    Node Alias Sync Project

    1. Using the workflow visualizer for the Configure Interfaces workflow job template, click Start. Create the first node using information from the "Workflow Node #1" table. Select the Interfaces project and specify the node alias. When finished, click Save.

      Synchronizing a project as the first step in the workflow

      Note

      The P symbol under the Sync Project node in the workflow visualizer indicates it is a project sync node.

      The blue line connecting Start and the Sync Project node indicates that this step is always performed.

  4. Create a node that runs if the Sync Project node succeeds. This new node must launch the Add Interfaces job template. Create the second node using the following information:

    Table 5.6. Workflow Node #2

    FieldValue
    Node Type Job Template
    Name Add Interfaces

    1. Using the workflow visualizer for the Configure Interfaces workflow job template, hover over the Sync Project node and then click Add a new node.

    2. Select On Success and then click Next.

    3. Create the second node using information from the "Workflow Node #2" table. Select the Add Interfaces job template and then click Save.

      Adding a job template node to the workflow

      Note

      The JT symbol under the Add Interfaces node in the workflow visualizer indicates it is a job template node.

      The green line connecting the Sync Project node and the Add Interfaces node indicates that this step is performed only on success.

  5. Create a node that runs if the Add Interfaces node succeeds. This new node must launch the Ping Interfaces job template. Create the third node using the following information:

    Table 5.7. Workflow Node #3

    FieldValue
    Node Type Job Template
    Name Ping Interfaces

    1. Using the workflow visualizer for the Configure Interfaces workflow job template, hover over the Add Interfaces node and then click Add a new node.

    2. Select On Success and then click Next.

    3. Create the third node using information from the "Workflow Node #3" table. Select the Ping Interfaces job template and then click Save.

  6. Create a node that runs if the Add Interfaces node fails. This new node must launch the Revert Changes job template. Create the fourth node using the following information:

    Table 5.8. Workflow Node #4

    FieldValue
    Node Type Job Template
    Name Revert Changes

    After this step, the completed workflow job template should appear as follows:

    Completed workflow job template
    1. Using the workflow visualizer for the Configure Interfaces workflow job template, hover over the Add Interfaces node and then click Add a new node.

    2. Select On Failure and then click Next.

    3. Create the fourth node using information from the "Workflow Node #4" table. Select the Revert Changes job template and then click Save.

      The red line connecting the Add Interfaces node and the Revert Changes node indicates that this step is performed only on failure.

    4. Click Save to exit the workflow visualizer.

  7. Run the Configure Interfaces workflow job template. Notice that the Add Interfaces node fails, which causes the Revert Changes node to run.

    1. Navigate to ResourcesTemplates and then click the Launch Template icon for the Configure Interfaces workflow job template.

    2. Monitor the workflow and notice that the Add Interfaces node fails, which causes the Revert Changes node to run successfully.

      On Failure node runs successfully

      Note

      The Add Interfaces node fails but the Revert Changes node succeeds, which results in a successful run of the Configure Interfaces workflow.

  8. Repair the issues in the add_interfaces.yml playbook that caused the Add Interfaces node in your workflow job to fail. Clone the git@git.lab.example.com:student/interfaces Git repository into the /home/student/git-repos directory.

    Change to the exercise branch and then edit the tasks in the add_interfaces.yml playbook. Any task using a module from the cisco.ios collection should only run on IOS devices, and any task using a module from the junipernetworks.junos collection should only run on Junos devices.

    Note

    You can view connection variables for the ios group in the ~/git-repos/inventories/group_vars/ios/connection.yml file and connection variables for the junos group in the ~/git-repos/inventories/group_vars/junos/connection.yml file.

    1. Use either VS Code or the git clone command to clone the git@git.lab.example.com:student/interfaces Git repository into the /home/student/git-repos directory:

      [student@workstation ~]$ cd ~/git-repos/
      [student@workstation git-repos]$ git clone \
      git@git.lab.example.com:student/interfaces
    2. Use either VS Code or the git checkout exercise command to switch to the exercise branch:

      [student@workstation git-repos]$ cd interfaces
      [student@workstation interfaces]$ git checkout exercise
      Switched to branch 'exercise'
      Your branch is up to date with 'origin/exercise'.
    3. Edit the add_interfaces.yml playbook to add the when: ansible_network_os == "cisco.ios.ios" case statement to the tasks that use the cisco.ios modules:

      ...output omitted...
          - name: Create loopback interface
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_interfaces:
      ...output omitted...
          - name: Assign IP to loopback interface
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_l3_interfaces:
    4. Edit the add_interfaces.yml playbook to add the when: ansible_network_os == "junipernetworks.junos.junos" case statement to the task that uses the junipernetworks.junos module:

      ...output omitted...
          - name: Add IP addresses
            when: ansible_network_os == 'junipernetworks.junos.junos'
            junipernetworks.junos.junos_l3_interfaces:
      ...output omitted...
  9. The existing add_interfaces.yml playbook only adds one interface to each managed node. Extend the functionality of the add_interfaces.yml playbook by using loops.

    Add a loop to the Create loopback interface task that iterates over the interface names in the ~/git-repos/inventories/group_vars/ios/interfaces.yml file.

    Add a second loop to the Assign IP to loopback interface task to iterate over the addresses in the ~/git-repos/inventories/group_vars/ios/interfaces.yml file.

    Add a third loop to the Add IP addresses task to iterate over the addresses in the ~/git-repos/inventories/group_vars/junos/interfaces.yml file.

    Note

    Each task in the playbook correctly references the interfaces variable in the ~/git-repos/inventories/group_vars/junos/interfaces.yml file. However, the existing tasks only reference the first list item for each managed node.

    Stage and commit the modified files and use a descriptive commit message. Push your changes to the remote Git repository.

    1. Inspect the ~/git-repos/inventories/group_vars/ios/interfaces.yml file:

      ---
      interfaces:
        iosxe1.lab.example.com:
          - name: Loopback100
            address: 10.10.10.100/32
            enabled: true
          - name: Loopback101
            address: 10.10.10.101/32
            enabled: true
          - name: Loopback102
            address: 10.10.10.102/32
            enabled: true
          - name: Loopback103
            address: 10.10.10.103/32
            enabled: true
    2. Update the ~/git-repos/interfaces/add_interfaces.yml playbook to add a loop to the Create loopback interfaces task. Update the interface name, enabled state, and description parameters. The updated task contains the following content:

          - name: Create loopback interfaces
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_interfaces:
              config:
                - name: "{{ item['name'] }}"
                  enabled: "{{ item['enabled'] }}"
                  description: >-
                    {{ item['description'] |
                    default(omit) }}
              state: replaced
            loop: "{{ interfaces[inventory_hostname] }}"
    3. Add a loop to the Assign IP to loopback interface task and then update the interface name and address parameters. The updated task contains the following content:

          - name: Assign IP to loopback interface
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_l3_interfaces:
              config:
                - name: "{{ item['name'] }}"
                  ipv4:
                    - address: "{{ item['address'] }}"
              state: replaced
            loop: "{{ interfaces[inventory_hostname] }}"
    4. Inspect the ~/git-repos/inventories/group_vars/junos/interfaces.yml file:

      ---
      interfaces:
        junos1.lab.example.com:
          - name: fxp0
            address: 172.25.250.205/24
          - name: fxp0
            address: 172.25.250.206/24
          - name: fxp0
            address: 172.25.250.207/24
          - name: fxp0
            address: 172.25.250.208/24
    5. Add a loop to the Add IP addresses task and then update the interface name and address parameters. The updated task contains the following content:

          - name: Add IP addresses
            when: ansible_network_os == 'junipernetworks.junos.junos'
            junipernetworks.junos.junos_l3_interfaces:
              config:
                - name: "{{ item['name'] }}"
                  ipv4:
                    - address: "{{ item['address'] }}"
            loop: "{{ interfaces[inventory_hostname] }}"
    6. The final playbook should contain the following content:

      ---
      - name: Configure loopback interfaces
        hosts:
          - iosxe1.lab.example.com
          - junos1.lab.example.com
        gather_facts: false
        tasks:
          - name: Create loopback interface
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_interfaces:
              config:
                - name: "{{ item['name'] }}"
                  enabled: "{{ item['enabled'] }}"
                  description: >-
                    {{ item['description'] |
                    default(omit) }}
              state: replaced
            loop: "{{ interfaces[inventory_hostname] }}"
      
          - name: Assign IP to loopback interface
            when: ansible_network_os == 'cisco.ios.ios'
            cisco.ios.ios_l3_interfaces:
              config:
                - name: "{{ item['name'] }}"
                  ipv4:
                    - address: "{{ item['address'] }}"
              state: replaced
            loop: "{{ interfaces[inventory_hostname] }}"
      
          - name: Add IP addresses
            when: ansible_network_os == 'junipernetworks.junos.junos'
            junipernetworks.junos.junos_l3_interfaces:
              config:
                - name: "{{ item['name'] }}"
                  ipv4:
                    - address: "{{ item['address'] }}"
            loop: "{{ interfaces[inventory_hostname] }}"
    7. Use either VS Code or the CLI to add, commit, and push the changes to the remote repository. Use a descriptive commit message, such as Improving add_interfaces.yml playbook. If you choose to use the CLI, then use the git add, git commit, and git push commands:

      [student@workstation interfaces]$ git add add_interfaces.yml
      [student@workstation interfaces]$ git commit -m \
      "Improving add_interfaces.yml playbook"
      [exercise 96b349d] Improving add_interfaces.yml playbook
      ...output omitted...
      [student@workstation interfaces]$ git push -u origin exercise
      ...output omitted...
      To git.lab.example.com:student/interfaces.git
         6563170..d4a96cb  exercise -> exercise
      Branch 'exercise' set up to track remote branch 'exercise' from 'origin'.
  10. Run the Configure Interfaces workflow job template. Notice that the Add Interfaces node succeeds but the Ping Interfaces node fails.

    1. Navigate to ResourcesTemplates and then click the Launch Template icon for the Configure Interfaces workflow job template.

    2. Monitor the workflow and notice that the Add Interfaces node succeeds and the Ping Interfaces node fails, which results in a failed run of the Configure Interfaces workflow.

      Ping Interfaces node fails
  11. Repair the issue in the ping_check.yml playbook that caused the Ping Interfaces node in your workflow job to fail.

    Edit the tasks in the ping_check.yml playbook in the exercise branch of the repository.

    Tasks in the IOS tasks block should only run on IOS devices, the Show ios_ping task should only run if the Ping IOS hosts task fails, and the Report IOS host status task should always run. Similarly, the tasks in the Junos tasks block should only run on Junos devices, the Show junos_ping task should only run if the Ping Junos hosts task fails, and the Report Junos host status task should always run.

    Stage and commit the modified files and use a descriptive commit message. Push your changes to the remote Git repository. When finished, either close the opened directory in VS Code or return to the /home/student directory from the CLI.

    1. Add a when condition statement to the IOS tasks block so that the tasks only run on hosts where the ansible_network_os variable is set to the cisco.ios.ios value. The updated block contains the following content:

      ...output omitted...
        tasks:
          - name: IOS tasks
            when: ansible_network_os == 'cisco.ios.ios'
            block:
              - name: Ping IOS hosts
                cisco.ios.ios_ping:
                  dest: "{{ item }}"
      ...output omitted...
    2. Move the Show ios_ping task into a rescue block for the IOS tasks block:

      ...output omitted...
          - name: IOS tasks
            when: ansible_network_os == 'cisco.ios.ios'
            block:
              - name: Ping IOS hosts
                cisco.ios.ios_ping:
                  dest: "{{ item }}"
                loop: >-
                  {{ interfaces[inventory_hostname] |
                  map(attribute='address') |
                  ansible.utils.ipaddr('address') }}
                register: ios_ping
      
              - name: Report IOS host status
                ansible.builtin.debug:
                  msg: >-
                    "The host {{ item['item'] }} had
                    {{ item['packet_loss'] }} packet loss."
                loop: "{{ ios_ping['results'] | flatten(1) }}"
      
            rescue:
              - name: Show ios_ping
                ansible.builtin.debug:
                  var: ios_ping['results'] | selectattr('failed', '==', true)
      ...output omitted...
    3. Move the Report IOS host status task into an always block for the IOS tasks block:

      ...output omitted...
          - name: IOS tasks
            when: ansible_network_os == 'cisco.ios.ios'
            block:
              - name: Ping IOS hosts
                cisco.ios.ios_ping:
                  dest: "{{ item }}"
                loop: >-
                  {{ interfaces[inventory_hostname] |
                  map(attribute='address') |
                  ansible.utils.ipaddr('address') }}
                register: ios_ping
      
            rescue:
              - name: Show ios_ping
                ansible.builtin.debug:
                  var: ios_ping['results'] | selectattr('failed', '==', true)
      
            always:
              - name: Report IOS host status
                ansible.builtin.debug:
                  msg: >-
                    "The host {{ item['item'] }} had
                    {{ item['packet_loss'] }} packet loss."
                loop: "{{ ios_ping['results'] | flatten(1) }}"
      ...output omitted...
    4. Add a when condition statement to the Junos tasks block so the tasks only run on hosts where the ansible_network_os variable is set to the junipernetworks.junos.junos value. The updated block contains the following content:

      ...output omitted...
        tasks:
          - name: Junos tasks
            when: ansible_network_os == 'junipernetworks.junos.junos'
            block:
              - name: Ping Junos hosts
                junipernetworks.junos.junos_ping:
                  dest: "{{ item }}"
      ...output omitted...
    5. Move the Show junos_ping task into a rescue block for the Junos tasks block:

      ...output omitted...
          - name: Junos tasks
            when: ansible_network_os == 'junipernetworks.junos.junos'
            block:
              - name: Ping Junos hosts
                vars:
                  ansible_connection: ansible.netcommon.network_cli
                junipernetworks.junos.junos_ping:
                  dest: "{{ item }}"
                loop: >-
                  {{ interfaces[inventory_hostname] |
                  map(attribute='address') |
                  ansible.utils.ipaddr('address') }}
                register: junos_ping
      
              - name: Report Junos host status
                ansible.builtin.debug:
                  msg: >-
                    "The host {{ item['item'] }} had
                    {{ item['packet_loss'] }} packet loss."
                loop: "{{ junos_ping['results'] | flatten(1) }}"
      
            rescue:
              - name: Show junos_ping
                ansible.builtin.debug:
                  var: junos_ping['results'] | selectattr('failed', '==', true)
      ...output omitted...
    6. Move the Report Junos host status task into an always block for the Junos tasks block:

      ...output omitted...
          - name: Junos tasks
            when: ansible_network_os == 'junipernetworks.junos.junos'
            block:
              - name: Ping Junos hosts
                vars:
                  ansible_connection: ansible.netcommon.network_cli
                junipernetworks.junos.junos_ping:
                  dest: "{{ item }}"
                loop: >-
                  {{ interfaces[inventory_hostname] |
                  map(attribute='address') |
                  ansible.utils.ipaddr('address') }}
                register: junos_ping
      
            rescue:
              - name: Show junos_ping
                ansible.builtin.debug:
                  var: junos_ping['results'] | selectattr('failed', '==', true)
      
            always:
              - name: Report Junos host status
                ansible.builtin.debug:
                  msg: >-
                    "The host {{ item['item'] }} had
                    {{ item['packet_loss'] }} packet loss."
                loop: "{{ junos_ping['results'] | flatten(1) }}"
    7. The final playbook should contain the following content:

      ---
      - name: Ping new IP addresses
        hosts:
          - iosxe1.lab.example.com
          - junos1.lab.example.com
        gather_facts: false
        tasks:
          - name: IOS tasks
            when: ansible_network_os == 'cisco.ios.ios'
            block:
              - name: Ping IOS hosts
                cisco.ios.ios_ping:
                  dest: "{{ item }}"
                loop: >-
                  {{ interfaces[inventory_hostname] |
                  map(attribute='address') |
                  ansible.utils.ipaddr('address') }}
                register: ios_ping
      
            rescue:
              - name: Show ios_ping
                ansible.builtin.debug:
                  var: ios_ping['results'] | selectattr('failed', '==', true)
      
            always:
              - name: Report IOS host status
                ansible.builtin.debug:
                  msg: >-
                    "The host {{ item['item'] }} had
                    {{ item['packet_loss'] }} packet loss."
                loop: "{{ ios_ping['results'] | flatten(1) }}"
      
          - name: Junos tasks
            when: ansible_network_os == 'junipernetworks.junos.junos'
            block:
              - name: Ping Junos hosts
                vars:
                  ansible_connection: ansible.netcommon.network_cli
                junipernetworks.junos.junos_ping:
                  dest: "{{ item }}"
                loop: >-
                  {{ interfaces[inventory_hostname] |
                  map(attribute='address') |
                  ansible.utils.ipaddr('address') }}
                register: junos_ping
      
            rescue:
              - name: Show junos_ping
                ansible.builtin.debug:
                  var: junos_ping['results'] | selectattr('failed', '==', true)
      
            always:
              - name: Report Junos host status
                ansible.builtin.debug:
                  msg: >-
                    "The host {{ item['item'] }} had
                    {{ item['packet_loss'] }} packet loss."
                loop: "{{ junos_ping['results'] | flatten(1) }}"
    8. Use either VS Code or the CLI to add, commit, and push the changes to the remote repository. Use a descriptive commit message, such as Improving ping_check.yml playbook. If you choose to use the CLI, then use the git add, git commit, and git push commands:

      [student@workstation interfaces]$ git add ping_check.yml
      [student@workstation interfaces]$ git commit -m \
      "Improving ping_check.yml playbook"
      [exercise 96b349d] Improving ping_check.yml playbook
      ...output omitted...
      [student@workstation interfaces]$ git push -u origin exercise
      ...output omitted...
      Branch 'exercise' set up to track remote branch 'exercise' from 'origin'.
    9. If you completed this step and the previous steps using VS Code, then click FileClose Folder to close the /home/student/git-repos/interfaces directory. If you completed this step and the previous steps using the CLI, then run the cd command to return to the /home/student directory:

      [student@workstation interfaces]$ cd
  12. Run the Configure Interfaces workflow job template. Notice that the Add Interfaces node and the Ping Interfaces node both succeed.

    1. Navigate to ResourcesTemplates and then click the Launch Template icon for the Configure Interfaces workflow job template.

    2. Monitor the workflow and notice that the Add Interfaces node and the Ping Interfaces node both succeed.

      Workflow template runs successfully

Evaluation

As the student user on the workstation machine, use the lab command to grade your work. Correct any reported failures and rerun the command until successful.

[student@workstation ~]$ lab grade task-review

Finish

As the student user on the workstation machine, use the lab command to complete this exercise. This step is important to ensure that resources from previous exercises do not impact upcoming exercises.

[student@workstation ~]$ lab finish task-review

Revision: do457-2.3-7cfa22a