Practice Questions

Test your knowledge on this topic in the ENAUTO Exam Trainer — 186 questions across 5 interactive modes.

Ansible for Controller-Based Network Automation – ENAUTO v2.0

Exam Relevance

Topic 3.0 Controller-Based Network Automation (30%) is the highest-weighted section. Exam topic 3.4 specifically tests your ability to construct automation solutions with Ansible that interact with Cisco controllers (Catalyst Center, Meraki, ISE, SD-WAN). This guide maps every playbook example to the corresponding exam objective.

Exam Topics Covered:
3.4 – Construct a controller-based network automation solution with Ansible
3.1 – Day-0 provisioning with controller-based solutions
3.2 – Python to manage and monitor configurations (comparison)
3.5 – Security automation (ISE with Ansible)
3.6 – Troubleshoot REST API automation solutions

Table of Contents


1 – Ansible Fundamentals for Network Automation

Ansible is an agentless automation tool that uses YAML playbooks to define desired state. For network automation against controllers, Ansible communicates over HTTPS using the httpapi connection plugin — no SSH to individual devices required.

┌─────────────────────────────────────────────┐
│           Ansible Control Node              │
│                                             │
│  ┌───────────┐  ┌──────────┐  ┌──────────┐ │
│  │ Inventory │  │Playbooks │  │Collections│ │
│  │ hosts.yml │  │ *.yml    │  │ cisco.*   │ │
│  └───────────┘  └──────────┘  └──────────┘ │
└──────────────────┬──────────────────────────┘
                   │ HTTPS (httpapi)
                   ▼
┌─────────────────────────────────────────────┐
│            Cisco Controllers                │
│                                             │
│  Catalyst Center  ·  Meraki Dashboard       │
│  ISE              ·  SD-WAN vManage         │
└──────────────────┬──────────────────────────┘
                   │ Internal protocols
                   ▼
┌─────────────────────────────────────────────┐
│          Managed Network Devices            │
│   Routers · Switches · APs · WLCs          │
└─────────────────────────────────────────────┘

Inventory File Structure

Ansible inventory defines the target hosts and connection variables. For controller automation, each controller is a single host entry.

INI format (inventory.ini):

[catalyst_center]
dnac1 ansible_host=10.10.20.85
 
[catalyst_center:vars]
ansible_connection=ansible.netcommon.httpapi
ansible_httpapi_use_ssl=true
ansible_httpapi_validate_certs=false
ansible_network_os=cisco.dnac.dnac
ansible_user=admin
ansible_password=Cisco123!
dnac_port=443
dnac_version=2.3.7.9
dnac_debug=false

YAML format (inventory.yml) — preferred for readability:

---
all:
  children:
    catalyst_center:
      hosts:
        dnac1:
          ansible_host: 10.10.20.85
      vars:
        ansible_connection: ansible.netcommon.httpapi
        ansible_httpapi_use_ssl: true
        ansible_httpapi_validate_certs: false
        ansible_network_os: cisco.dnac.dnac
        ansible_user: admin
        ansible_password: "{{ vault_dnac_password }}"
        dnac_port: 443
        dnac_version: "2.3.7.9"
        dnac_debug: false

Playbook Anatomy

Every Ansible playbook follows the same structure:

---
- name: Descriptive name for the play
  hosts: catalyst_center        # Target group from inventory
  gather_facts: false           # Always false for network controllers
  tasks:
    - name: Descriptive name for the task
      cisco.dnac.network_device_info:   # Module (collection.module)
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: false
      register: result          # Capture output
 
    - name: Display results
      ansible.builtin.debug:
        var: result.dnac_response

Key Playbook Elements

  • hosts: matches a group or host in inventory
  • gather_facts: false: always set this for API-based controllers (no SSH facts to gather)
  • register: saves module output into a variable for later use
  • debug: prints registered variables for troubleshooting

Variables: host_vars, group_vars, extra-vars

Ansible resolves variables in this precedence order (highest wins):

extra-vars (-e)  →  highest priority
task vars
play vars
host_vars/
group_vars/
inventory vars
role defaults     →  lowest priority

Directory structure for variable files:

project/
├── ansible.cfg
├── inventory.yml
├── group_vars/
│   ├── catalyst_center.yml    # Variables for all DNAC hosts
│   └── meraki.yml             # Variables for all Meraki hosts
├── host_vars/
│   └── dnac1.yml              # Variables for specific host
└── playbooks/
    └── get_devices.yml

Ansible Vault for Credentials

Never store passwords in plaintext. Use Ansible Vault:

# Create an encrypted variable file
ansible-vault create group_vars/catalyst_center/vault.yml
 
# Contents of the vault file
vault_dnac_password: Cisco123!
vault_dnac_username: admin
 
# Run playbook with vault
ansible-playbook playbooks/get_devices.yml --ask-vault-pass
 
# Or use a password file
ansible-playbook playbooks/get_devices.yml --vault-password-file ~/.vault_pass

ansible.cfg Key Settings

[defaults]
inventory = inventory.yml
host_key_checking = false
timeout = 60
retry_files_enabled = false
stdout_callback = yaml
collections_path = ./collections
 
[persistent_connection]
connect_timeout = 120
command_timeout = 120

host_key_checking

Set host_key_checking = false in lab environments. In production, use proper certificate validation. The exam often tests whether you know this setting exists and when to use it.

Connection Types

Connection TypeUse CaseProtocol
ansible.netcommon.httpapiController APIs (DNAC, Meraki, SD-WAN)HTTPS
ansible.netcommon.network_cliDirect device CLI (IOS, NX-OS)SSH
localLegacy; runs on control nodeVaries

For Exam Topic 3.4

Controller-based automation always uses httpapi (or sometimes local for older collections). Never use network_cli for controller APIs — that is for direct device management (Exam Topic 2.0).


2 – Ansible Collections for Cisco Controllers

Cisco provides dedicated Ansible collections for each controller platform. Each collection abstracts the REST API into declarative Ansible modules.

ControllerCollectionConnectionAuth MethodSync Model
Catalyst Centercisco.dnachttpapiToken-based (X-Auth-Token, auto-handled)Asynchronous (taskId)
Merakicisco.merakihttpapiAPI Key (X-Cisco-Meraki-API-Key)Synchronous
ISEcisco.isehttpapiBasic Auth (every request)Synchronous
SD-WAN (vManage)cisco.catalystwanhttpapiSession-based (JSESSIONID cookie)Asynchronous

Install all collections at once:

# Install individual collections
ansible-galaxy collection install cisco.dnac
ansible-galaxy collection install cisco.meraki
ansible-galaxy collection install cisco.ise
ansible-galaxy collection install cisco.catalystwan
 
# Or install from a requirements file
# requirements.yml
# ---
# collections:
#   - name: cisco.dnac
#     version: ">=6.0.0"
#   - name: cisco.meraki
#   - name: cisco.ise
#   - name: cisco.catalystwan
 
ansible-galaxy collection install -r requirements.yml

Collection Documentation

Every module has documentation accessible via:

ansible-doc cisco.dnac.network_device_info
ansible-doc cisco.meraki.organizations_info

3 – Catalyst Center with cisco.dnac

Installation

ansible-galaxy collection install cisco.dnac

Inventory (httpapi)

---
all:
  children:
    catalyst_center:
      hosts:
        dnac1:
          ansible_host: 10.10.20.85
      vars:
        ansible_connection: ansible.netcommon.httpapi
        ansible_httpapi_use_ssl: true
        ansible_httpapi_validate_certs: false
        ansible_network_os: cisco.dnac.dnac
        ansible_user: admin
        ansible_password: "{{ vault_dnac_password }}"
        dnac_port: 443
        dnac_version: "2.3.7.9"
        dnac_verify: false
        dnac_debug: false

Critical Inventory Variables

  • ansible_network_os must be cisco.dnac.dnac — not ios, not dnac alone
  • ansible_connection must be ansible.netcommon.httpapi — not network_cli
  • The httpapi plugin handles token auth automatically (POST to /dna/system/api/v1/auth/token)

a) Get All Network Devices

Ansible Playbook:

---
- name: Get all network devices from Catalyst Center
  hosts: catalyst_center
  gather_facts: false
  tasks:
    - name: Get device list
      cisco.dnac.network_device_info:
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: "{{ dnac_verify }}"
      register: device_list
 
    - name: Print device hostnames and IPs
      ansible.builtin.debug:
        msg: "{{ item.hostname }} — {{ item.managementIpAddress }}"
      loop: "{{ device_list.dnac_response.response }}"

Equivalent Python requests:

import requests
from requests.auth import HTTPBasicAuth
 
BASE_URL = "https://10.10.20.85"
AUTH_URL = f"{BASE_URL}/dna/system/api/v1/auth/token"
DEVICES_URL = f"{BASE_URL}/dna/intent/api/v1/network-device"
 
# Step 1: Get token (Ansible httpapi does this automatically)
auth_response = requests.post(
    AUTH_URL,
    auth=HTTPBasicAuth("admin", "Cisco123!"),
    verify=False
)
token = auth_response.json()["Token"]
 
# Step 2: Get devices
headers = {
    "X-Auth-Token": token,
    "Content-Type": "application/json"
}
response = requests.get(DEVICES_URL, headers=headers, verify=False)
devices = response.json()["response"]
 
for device in devices:
    print(f"{device['hostname']}{device['managementIpAddress']}")

Ansible Advantage

Notice that the Ansible playbook does NOT need to handle token authentication manually. The cisco.dnac httpapi plugin performs the POST /dna/system/api/v1/auth/token call behind the scenes and injects X-Auth-Token into every subsequent request.

b) Add a Device

---
- name: Add a network device to Catalyst Center
  hosts: catalyst_center
  gather_facts: false
  tasks:
    - name: Add device
      cisco.dnac.network_device:
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: "{{ dnac_verify }}"
        state: present
        ipAddress:
          - "192.168.1.100"
        snmpVersion: "v2"
        snmpROCommunity: "readonly"
        snmpRWCommunity: "readwrite"
        cliTransport: "ssh"
        userName: "device_admin"
        password: "device_password"
        enablePassword: "enable_secret"
      register: add_result
 
    - name: Check task status
      cisco.dnac.task_info:
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: "{{ dnac_verify }}"
        taskId: "{{ add_result.dnac_response.response.taskId }}"
      register: task_status
      until: task_status.dnac_response.response.isError is defined
      retries: 10
      delay: 5
 
    - name: Display task result
      ansible.builtin.debug:
        var: task_status.dnac_response.response

Catalyst Center Writes Are Asynchronous

Adding a device returns a taskId, NOT the device itself. You must poll with cisco.dnac.task_info to confirm success. This is one of the most common exam questions for topic 3.4/3.6.

c) Deploy a Configuration Template

---
- name: Deploy a Jinja2 template via Catalyst Center
  hosts: catalyst_center
  gather_facts: false
  tasks:
    - name: Deploy template to device
      cisco.dnac.configuration_template_deploy_v2:
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: "{{ dnac_verify }}"
        forcePushTemplate: false
        mainTemplateId: "abc12345-def6-7890-abcd-ef1234567890"
        targetInfo:
          - id: "192.168.1.100"
            type: "MANAGED_DEVICE_IP"
            params:
              hostname: "BRANCH-SW01"
              vlan_id: "100"
              vlan_name: "DATA"
      register: deploy_result
 
    - name: Poll deployment task
      cisco.dnac.task_info:
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: "{{ dnac_verify }}"
        taskId: "{{ deploy_result.dnac_response.response.taskId }}"
      register: task_result
      until: task_result.dnac_response.response.endTime is defined
      retries: 30
      delay: 10

d) Get Site Health

---
- name: Get site health from Catalyst Center
  hosts: catalyst_center
  gather_facts: false
  tasks:
    - name: Retrieve site health data
      cisco.dnac.site_health_info:
        dnac_host: "{{ ansible_host }}"
        dnac_port: "{{ dnac_port }}"
        dnac_username: "{{ ansible_user }}"
        dnac_password: "{{ ansible_password }}"
        dnac_version: "{{ dnac_version }}"
        dnac_verify: "{{ dnac_verify }}"
      register: health
 
    - name: Display site health scores
      ansible.builtin.debug:
        msg: >
          Site: {{ item.siteName }}
          Network Health: {{ item.networkHealthAverage }}
          Client Health: {{ item.clientHealthWired }}
      loop: "{{ health.dnac_response.response }}"

4 – Meraki with cisco.meraki

Installation

ansible-galaxy collection install cisco.meraki

Inventory

---
all:
  children:
    meraki:
      hosts:
        meraki_dashboard:
          ansible_host: api.meraki.com
      vars:
        ansible_connection: ansible.netcommon.httpapi
        ansible_httpapi_use_ssl: true
        ansible_httpapi_validate_certs: true
        ansible_network_os: cisco.meraki.dashboard
        meraki_api_key: "{{ vault_meraki_api_key }}"
        meraki_base_url: "https://api.meraki.com/api/v1"
        meraki_output_log: true

Meraki Auth Is Simpler

Unlike Catalyst Center, there is no token exchange. The meraki_api_key is sent as the X-Cisco-Meraki-API-Key header on every request. The httpapi plugin handles this automatically.

a) List Organizations

---
- name: List Meraki organizations
  hosts: meraki
  gather_facts: false
  tasks:
    - name: Get all organizations
      cisco.meraki.organizations_info:
        meraki_api_key: "{{ meraki_api_key }}"
      register: orgs
 
    - name: Display organizations
      ansible.builtin.debug:
        msg: "Org: {{ item.name }} (ID: {{ item.id }})"
      loop: "{{ orgs.meraki_response }}"

b) List Networks

---
- name: List networks in an organization
  hosts: meraki
  gather_facts: false
  tasks:
    - name: Get all networks
      cisco.meraki.networks_info:
        meraki_api_key: "{{ meraki_api_key }}"
        organizationId: "123456"
      register: networks
 
    - name: Display networks
      ansible.builtin.debug:
        msg: "Network: {{ item.name }} ({{ item.productTypes | join(', ') }})"
      loop: "{{ networks.meraki_response }}"

c) Create/Update SSID

---
- name: Configure a wireless SSID
  hosts: meraki
  gather_facts: false
  tasks:
    - name: Create or update SSID
      cisco.meraki.networks_wireless_ssids:
        meraki_api_key: "{{ meraki_api_key }}"
        networkId: "L_123456789012345678"
        number: "0"
        state: present
        name: "Corporate-WiFi"
        enabled: true
        authMode: "psk"
        encryptionMode: "wpa"
        wpaEncryptionMode: "WPA2 only"
        psk: "{{ vault_wifi_psk }}"
        ipAssignmentMode: "Bridge mode"
      register: ssid_result
 
    - name: Display SSID config
      ansible.builtin.debug:
        var: ssid_result.meraki_response

Meraki Writes Are Synchronous

The response from the SSID module contains the complete, updated SSID configuration. There is no taskId to poll — the operation completes in the API response. This is a key difference from Catalyst Center.

d) Claim Device into Network

---
- name: Claim a device into a Meraki network
  hosts: meraki
  gather_facts: false
  tasks:
    - name: Claim device by serial number
      cisco.meraki.networks_devices_claim:
        meraki_api_key: "{{ meraki_api_key }}"
        networkId: "L_123456789012345678"
        serials:
          - "Q2AB-CDE4-FGHI"
      register: claim_result
 
    - name: Show claimed device
      ansible.builtin.debug:
        var: claim_result.meraki_response

Meraki Rate Limiting

Meraki enforces a limit of 5 API calls per second per organization. If your playbook has many tasks, you may hit HTTP 429 responses. Use throttle: 1 on tasks or serial: 1 on plays to slow down execution. See 8 – Common Patterns and Gotchas for details.


5 – ISE with cisco.ise

Installation

ansible-galaxy collection install cisco.ise

Inventory

---
all:
  children:
    ise:
      hosts:
        ise1:
          ansible_host: 10.10.20.70
      vars:
        ise_hostname: "{{ ansible_host }}"
        ise_username: admin
        ise_password: "{{ vault_ise_password }}"
        ise_verify: false
        ise_debug: false
        ise_uses_api_gateway: true
        ise_version: "3.3_patch_1"

ISE ERS API Must Be Enabled

The cisco.ise collection uses the ERS (External RESTful Services) API. This API is disabled by default on ISE. You must enable it via: Administration > Settings > API Settings > API Service Settings > ERS (Read/Write) Forgetting this is a common exam trap.

ISE Auth Method

ISE uses Basic Auth on every request — no token exchange, no session cookie. The cisco.ise modules handle this automatically using ise_username and ise_password.

a) Get All Network Devices

---
- name: Get all network devices from ISE
  hosts: ise
  gather_facts: false
  tasks:
    - name: Get network device list
      cisco.ise.network_device_info:
        ise_hostname: "{{ ise_hostname }}"
        ise_username: "{{ ise_username }}"
        ise_password: "{{ ise_password }}"
        ise_verify: "{{ ise_verify }}"
      register: devices
 
    - name: Display devices
      ansible.builtin.debug:
        msg: "{{ item.name }} — {{ item.NetworkDeviceIPList }}"
      loop: "{{ devices.ise_response.SearchResult.resources }}"

b) Create Internal User

---
- name: Create an internal user in ISE
  hosts: ise
  gather_facts: false
  tasks:
    - name: Create user
      cisco.ise.internal_user:
        ise_hostname: "{{ ise_hostname }}"
        ise_username: "{{ ise_username }}"
        ise_password: "{{ ise_password }}"
        ise_verify: "{{ ise_verify }}"
        state: present
        name: "jsmith"
        password: "UserP@ss123"
        email: "[email protected]"
        firstName: "John"
        lastName: "Smith"
        identityGroups: "Employee"
        enabled: true
      register: user_result
 
    - name: Display result
      ansible.builtin.debug:
        var: user_result

c) Create Scalable Group Tag (SGT)

---
- name: Create a Scalable Group Tag (SGT) in ISE
  hosts: ise
  gather_facts: false
  tasks:
    - name: Create SGT
      cisco.ise.sgt:
        ise_hostname: "{{ ise_hostname }}"
        ise_username: "{{ ise_username }}"
        ise_password: "{{ ise_password }}"
        ise_verify: "{{ ise_verify }}"
        state: present
        name: "IoT_Devices"
        description: "SGT for IoT devices"
        value: 100
        generationId: "0"
      register: sgt_result
 
    - name: Display SGT
      ansible.builtin.debug:
        var: sgt_result

d) Create Authorization Profile

---
- name: Create an authorization profile in ISE
  hosts: ise
  gather_facts: false
  tasks:
    - name: Create authz profile
      cisco.ise.authorization_profile:
        ise_hostname: "{{ ise_hostname }}"
        ise_username: "{{ ise_username }}"
        ise_password: "{{ ise_password }}"
        ise_verify: "{{ ise_verify }}"
        state: present
        name: "IoT_Authz_Profile"
        description: "Authorization profile for IoT devices"
        accessType: "ACCESS_ACCEPT"
        authzProfileType: "SWITCH"
        vlan:
          nameID: "IOT_VLAN"
          tagID: "200"
        trackMovement: false
      register: authz_result
 
    - name: Display result
      ansible.builtin.debug:
        var: authz_result

6 – SD-WAN with cisco.catalystwan

Installation

ansible-galaxy collection install cisco.catalystwan

Inventory

---
all:
  children:
    sdwan:
      hosts:
        vmanage1:
          ansible_host: 10.10.20.90
      vars:
        ansible_connection: ansible.netcommon.httpapi
        ansible_httpapi_use_ssl: true
        ansible_httpapi_validate_certs: false
        ansible_network_os: cisco.catalystwan.catalystwan
        ansible_user: admin
        ansible_password: "{{ vault_vmanage_password }}"

SD-WAN Auth Is Session-Based

SD-WAN vManage uses session authentication. The httpapi plugin performs a login to obtain a JSESSIONID cookie, then includes it on subsequent requests. A CSRF token is also obtained and sent automatically. You do NOT manage this manually in playbooks.

a) Get Device List

---
- name: Get SD-WAN device list
  hosts: sdwan
  gather_facts: false
  tasks:
    - name: Get all devices from vManage
      cisco.catalystwan.devices_info:
        device_category: vedges
      register: devices
 
    - name: Display devices
      ansible.builtin.debug:
        msg: "{{ item.host_name }} — {{ item.system_ip }} ({{ item.reachability }})"
      loop: "{{ devices.devices }}"

b) Attach Device Template

---
- name: Attach a device template in SD-WAN
  hosts: sdwan
  gather_facts: false
  tasks:
    - name: Attach template to device
      cisco.catalystwan.device_templates:
        state: attached
        template_name: "Branch-Router-Template"
        hostname: "BR-ROUTER-01"
        device_specific_vars:
          system_ip: "10.0.0.1"
          site_id: "100"
          hostname: "BR-ROUTER-01"
      register: attach_result
 
    - name: Show result
      ansible.builtin.debug:
        var: attach_result

c) Get Policy List

---
- name: Get SD-WAN policies
  hosts: sdwan
  gather_facts: false
  tasks:
    - name: Get all centralized policies
      cisco.catalystwan.policy_info:
        policy_type: centralized
      register: policies
 
    - name: Display policies
      ansible.builtin.debug:
        msg: "Policy: {{ item.policyName }} — {{ item.policyType }}"
      loop: "{{ policies.policies }}"

SD-WAN Operations Are Asynchronous

Like Catalyst Center, SD-WAN template pushes and policy activations return an action ID. You may need to poll for completion status depending on the module used.


7 – Comparison: requests vs SDK vs Ansible

This table summarizes the three automation approaches tested on the ENAUTO exam. Understanding when to use each is critical for exam topic 3.4.

Aspectrequests LibraryPython SDKAnsible
LanguagePythonPythonYAML (declarative)
Auth handlingManual (get token, set headers)Automatic (SDK manages tokens)Automatic (httpapi plugin)
Error handlingManual try/except, status codesSDK-specific exceptionsAnsible error handling (ignore_errors, failed_when)
IdempotencyManual (check-before-write)Partial (some SDK methods)Built-in (state: present/absent)
Async task pollingManual loopSDK helper methodsuntil/retries/delay keywords
Best forLearning, debugging, one-off scriptsProduction scripts, applicationsOrchestration, operations, CI/CD
CI/CD integrationCustom scriptingCustom scriptingNative (ansible-playbook in pipeline)
ReusabilityFunctions/classesFunctions/classesRoles, collections, playbooks
Credential managementEnvironment vars, config filesEnvironment vars, config filesAnsible Vault (encrypted at rest)
Learning curveLowMediumMedium
Exam weightTopic 3.2 (Python)Topic 3.2 (Python)Topic 3.4 (Ansible)

Exam Strategy

The exam expects you to recognize which approach (requests, SDK, Ansible) is most appropriate for a given scenario. Key signals:

  • “orchestrate across multiple controllers” → Ansible
  • “debug a specific API call” → requests
  • “build a web application” → SDK
  • “ensure idempotent configuration” → Ansible
  • “CI/CD pipeline for network changes” → Ansible

8 – Common Patterns and Gotchas

Collection Version Mismatches

The cisco.dnac collection version must match your Catalyst Center software version. If you use dnac_version: "2.3.7.9" in inventory but have an older collection installed, modules may fail with unexpected errors. Always check:

ansible-galaxy collection list | grep cisco.dnac

httpapi vs local Connection Confusion

Older Ansible examples may use connection: local. Modern Cisco collections require connection: ansible.netcommon.httpapi. Using local may cause authentication failures or unexpected behavior.

# WRONG (legacy)
ansible_connection: local
 
# CORRECT (modern)
ansible_connection: ansible.netcommon.httpapi

Forgetting ansible_network_os

Every httpapi-based collection requires ansible_network_os to be set correctly. Without it, Ansible does not know which httpapi plugin to load.

# Catalyst Center
ansible_network_os: cisco.dnac.dnac
 
# Meraki
ansible_network_os: cisco.meraki.dashboard
 
# SD-WAN
ansible_network_os: cisco.catalystwan.catalystwan

Catalyst Center Async Tasks in Ansible

Every write operation on Catalyst Center returns a taskId. If you forget to poll the task, your playbook may report “success” even though the actual operation failed on the controller.

# Always add a task polling step after write operations
- name: Poll task until complete
  cisco.dnac.task_info:
    dnac_host: "{{ ansible_host }}"
    dnac_port: "{{ dnac_port }}"
    dnac_username: "{{ ansible_user }}"
    dnac_password: "{{ ansible_password }}"
    dnac_version: "{{ dnac_version }}"
    dnac_verify: "{{ dnac_verify }}"
    taskId: "{{ previous_task.dnac_response.response.taskId }}"
  register: task_status
  until: task_status.dnac_response.response.endTime is defined
  retries: 20
  delay: 5

Meraki Rate Limiting in Ansible

Meraki allows 5 API calls per second per organization. A playbook with many tasks or loops can easily exceed this.

Fix 1 — Throttle individual tasks:

- name: Update SSIDs
  cisco.meraki.networks_wireless_ssids:
    # ... parameters ...
  loop: "{{ network_list }}"
  throttle: 1

Fix 2 — Serialize play execution:

- name: Configure Meraki networks
  hosts: meraki
  serial: 1
  # ... tasks ...

Fix 3 — Add pause between tasks:

- name: Wait for rate limit
  ansible.builtin.pause:
    seconds: 1

ISE ERS API Must Be Enabled

The cisco.ise collection communicates with the ERS API, which is disabled by default. If you see Connection refused or 403 Forbidden, the ERS API is likely not enabled. This is a common exam trick — the playbook YAML is correct but the ISE node is not configured to accept ERS requests.


9 – Exam-Style Scenarios

Scenario 1: 401 Unauthorized Against Catalyst Center

An engineer writes an Ansible playbook to retrieve devices from Catalyst Center. The playbook fails with HTTP 401 Unauthorized. The credentials are confirmed correct. The inventory file contains:

ansible_connection: ansible.netcommon.network_cli
ansible_network_os: ios

What is the problem?


Scenario 2: Meraki 429 Too Many Requests

A playbook loops through 50 Meraki networks to update SSID configurations. After processing a few networks, tasks start failing with HTTP 429 errors. How do you fix this?


Scenario 3: ISE SGT Creation Fails

An Ansible playbook to create a Scalable Group Tag on ISE fails with a connection error. The ISE web GUI is accessible and the credentials work for GUI login. What is missing?


Scenario 4: Which Connection Type for cisco.dnac?

On the exam, you see a fill-in-the-blank question:

ansible_connection: _______________
ansible_network_os: cisco.dnac.dnac

What is the correct value?


Scenario 5: Playbook Works for GET but Fails for POST

An engineer has a working playbook that retrieves devices from Catalyst Center. They add a new task to add a device using cisco.dnac.network_device. The task completes with changed: true, but the device never appears in Catalyst Center. What went wrong?


Scenario 6: Identify the Bug in This Playbook

The following playbook is supposed to list Meraki organizations but fails. What is wrong?

---
- name: List Meraki orgs
  hosts: meraki
  gather_facts: true
  tasks:
    - name: Get orgs
      cisco.meraki.organizations_info:
        meraki_api_key: "{{ meraki_api_key }}"
        ansible_host: api.meraki.com
      register: orgs

Scenario 7: Ansible Vault Password Prompt

An engineer runs ansible-playbook get_devices.yml and the playbook fails because the vault-encrypted password variable cannot be decrypted. What command-line flag is missing?


Scenario 8: SD-WAN Template Push Appears Successful but No Change

A playbook attaches a device template via cisco.catalystwan.device_templates. The task shows ok but the device configuration does not change in vManage. What should the engineer check?


10 – Quick Reference Cheat Sheet

Collection Install Commands

ansible-galaxy collection install cisco.dnac
ansible-galaxy collection install cisco.meraki
ansible-galaxy collection install cisco.ise
ansible-galaxy collection install cisco.catalystwan
ansible-galaxy collection install ansible.netcommon    # Required dependency

Minimum Inventory Per Controller

Catalyst Center:

ansible_connection: ansible.netcommon.httpapi
ansible_network_os: cisco.dnac.dnac
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: false
ansible_user: admin
ansible_password: password
dnac_port: 443
dnac_version: "2.3.7.9"

Meraki:

ansible_connection: ansible.netcommon.httpapi
ansible_network_os: cisco.meraki.dashboard
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: true
meraki_api_key: your_api_key

ISE:

ise_hostname: 10.10.20.70
ise_username: admin
ise_password: password
ise_verify: false

SD-WAN:

ansible_connection: ansible.netcommon.httpapi
ansible_network_os: cisco.catalystwan.catalystwan
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: false
ansible_user: admin
ansible_password: password

Most-Used Modules Per Collection

CollectionModulePurpose
cisco.dnacnetwork_device_infoGet all devices
cisco.dnacnetwork_deviceAdd/remove device
cisco.dnactask_infoPoll async tasks
cisco.dnacsite_health_infoGet site health
cisco.dnacconfiguration_template_deploy_v2Deploy template
cisco.merakiorganizations_infoList orgs
cisco.merakinetworks_infoList networks
cisco.merakinetworks_wireless_ssidsManage SSIDs
cisco.merakinetworks_devices_claimClaim devices
cisco.isenetwork_device_infoGet network devices
cisco.iseinternal_userManage users
cisco.isesgtManage SGTs
cisco.iseauthorization_profileManage authz profiles
cisco.catalystwandevices_infoGet device list
cisco.catalystwandevice_templatesAttach templates
cisco.catalystwanpolicy_infoGet policies

Key ansible.cfg Settings for Controller Automation

SettingSectionValuePurpose
host_key_checking[defaults]falseSkip SSH key verification (lab)
timeout[defaults]60General connection timeout
connect_timeout[persistent_connection]120httpapi connection timeout
command_timeout[persistent_connection]120httpapi command timeout
stdout_callback[defaults]yamlReadable output format
retry_files_enabled[defaults]falseDisable .retry files

Auth Summary

ControllerAuth TypeToken HeaderAnsible Handling
Catalyst CenterToken exchangeX-Auth-TokenAutomatic via httpapi
MerakiAPI keyX-Cisco-Meraki-API-KeyAutomatic via httpapi
ISEBasic AuthAuthorization: BasicAutomatic per request
SD-WANSession cookieJSESSIONID + CSRF tokenAutomatic via httpapi

enauto ansible controller-automation cisco-dnac meraki ise sdwan exam-topic-3-4

See Also