Skip to content

Playbook Testing

This guide demonstrates testing Ansible playbooks using Molecule within an ansible-creator playbook project. It covers the fundamentals of playbook testing, container and network device testing scenarios, and complete test lifecycle management.

Prerequisites

Before starting, ensure you have the following installed:

# Install ansible-creator in your virtual environment
pip install ansible-creator

Creating a Playbook Project

Initialize a playbook project using ansible-creator:

ansible-creator init playbook myorg.myproject /tmp/my-playbooks
cd /tmp/my-playbooks

This creates the following structure:

my-playbooks/
├── ansible.cfg
├── ansible-navigator.yml
├── collections/
│   └── requirements.yml
├── inventory/
│   ├── group_vars/
│   ├── host_vars/
│   └── hosts.yml
├── linux_playbook.yml
├── network_playbook.yml
└── site.yml

Create Molecule requirements file:

mkdir molecule
cat > molecule/requirements.yml << 'EOF'
---
collections:
  - name: containers.podman
    version: ">=1.10.0"
  - name: arista.eos
    version: ">=6.0.0"
EOF

Initialize Molecule scenarios for different testing needs:

# Linux container testing scenario
molecule init scenario linux

# Network device testing scenario
molecule init scenario network

Linux Container Testing Scenario

This scenario tests playbooks against Linux containers using Podman.

Configure the Linux Scenario

Update molecule/linux/molecule.yml:

The molecule.yml file is the scenario-specific configuration that tailors Molecule's behavior to the needs of this testing scenario.

---
dependency:
  name: galaxy
  options:
    requirements-file: ../requirements.yml

ansible:
  cfg:
    defaults:
      collections_path: collections
      inventory: inventory/hosts.yml

scenario:
  test_sequence:
    - dependency
    - create
    - prepare
    - converge
    - idempotence
    - verify
    - cleanup
    - destroy

Create molecule/linux/inventory.yml:

The inventory defines the testing resources and their configuration details that Molecule will use to create and manage test instances.

---
all:
  children:
    webservers:
      hosts:
        web-server:
          ansible_host: web-server
          container_image: quay.io/centos/centos:stream9
          container_command: /sbin/init
          container_privileged: true
      vars:
        http_port: 80
        server_name: test-web
        required_packages:
          - python3
          - systemd
    databases:
      hosts:
        db-server:
          ansible_host: db-server
          container_image: quay.io/centos/centos:stream9
          container_command: /sbin/init
          container_privileged: true
      vars:
        db_name: testdb
        db_user: testuser
        required_packages:
          - python3
          - systemd

Create molecule/linux/create.yml:

The create playbook is used to instantiate the testing resources defined in the inventory, provisioning containers and establishing connectivity.

---
- name: Create container instances
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Create container network
      containers.podman.podman_network:
        name: molecule-linux-test
        state: present

    - name: Create test containers
      containers.podman.podman_container:
        name: "{{ hostvars[item].ansible_host }}"
        image: "{{ hostvars[item].container_image }}"
        command: "{{ hostvars[item].container_command }}"
        privileged: "{{ hostvars[item].container_privileged }}"
        state: started
        networks:
          - molecule-linux-test
        systemd: true
      loop: "{{ groups['all'] }}"

    - name: Wait for containers to be ready
      ansible.builtin.wait_for_connection:
        timeout: 300
      delegate_to: "{{ hostvars[item].ansible_host }}"
      loop: "{{ groups['all'] }}"

Create molecule/linux/prepare.yml:

The prepare playbook configures the testing resources with any prerequisites needed before running the main playbook under test.

---
- name: Prepare container instances
  hosts: all
  gather_facts: false
  become: true
  tasks:
    - name: Install required packages
      ansible.builtin.dnf:
        name: "{{ required_packages }}"
        state: present
      when: required_packages is defined

Create molecule/linux/converge.yml:

The converge playbook executes the main playbook being tested, applying your automation logic to the prepared testing resources.

---
- name: Converge
  ansible.builtin.import_playbook: ../../linux_playbook.yml

Create molecule/linux/verify.yml:

The verify playbook validates that the converge playbook achieved the desired results by testing the final state of the resources.

---
- name: Verify
  hosts: all
  gather_facts: true
  tasks:
    - name: Check required packages are installed
      ansible.builtin.package_facts:
        manager: auto

    - name: Verify python3 is installed
      ansible.builtin.assert:
        that:
          - "'python3' in ansible_facts.packages"
        fail_msg: "Python3 package not found"

    - name: Verify systemd service is running
      ansible.builtin.systemd:
        name: systemd-logind
        state: started
      check_mode: true
      register: systemd_check
      failed_when: false

    - name: Assert systemd is active
      ansible.builtin.assert:
        that:
          - systemd_check is not failed
        fail_msg: "Systemd service not running"

Create molecule/linux/cleanup.yml:

The cleanup playbook removes temporary artifacts and resets the testing resources to a clean state before destruction.

---
- name: Cleanup
  hosts: all
  gather_facts: false
  tasks:
    - name: Remove test artifacts
      ansible.builtin.file:
        path: /tmp/molecule-test
        state: absent
      become: true

Create molecule/linux/destroy.yml:

The destroy playbook tears down all testing resources, removing containers and networks to return the system to its original state.

---
- name: Destroy container instances
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Remove test containers
      containers.podman.podman_container:
        name: "{{ hostvars[item].ansible_host }}"
        state: absent
      loop: "{{ groups['all'] }}"
      failed_when: false

    - name: Remove container network
      containers.podman.podman_network:
        name: molecule-linux-test
        state: absent
      failed_when: false

Network Device Testing Scenario

This scenario tests playbooks against Arista EOS network devices using containerized cEOS.

Configure the Network Scenario

Update molecule/network/molecule.yml:

This scenario-specific configuration adapts Molecule for network device testing with simplified sequences and network-specific settings.

---
dependency:
  name: galaxy
  options:
    requirements-file: ../requirements.yml

ansible:
  cfg:
    defaults:
      collections_path: collections
      inventory: inventory/hosts.yml
      host_key_checking: false

scenario:
  name: network
  description: Test against network devices
  test_sequence:
    - dependency
    - create
    - converge
    - verify
    - cleanup
    - destroy

Create molecule/network/inventory.yml:

The network inventory defines containerized network devices with connection parameters and container configuration for realistic device testing.

---
all:
  children:
    arista_switches:
      hosts:
        eos-switch-01:
          ansible_host: eos-switch-01
          ansible_network_os: eos
          ansible_user: admin
          ansible_password: admin
          ansible_connection: ansible.netcommon.network_cli
          container_image: ceos:latest
          container_privileged: true
          container_env:
            CEOS: 1
            EOS_PLATFORM: ceoslab
            container: docker
      vars:
        ansible_python_interpreter: "{{ ansible_playbook_python }}"

Create molecule/network/create.yml:

The create playbook launches containerized network devices and waits for both SSH and API services to become available for testing.

---
- name: Create network device containers
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Create container network
      containers.podman.podman_network:
        name: molecule-network-test
        state: present

    - name: Create EOS containers
      containers.podman.podman_container:
        name: "{{ hostvars[item].ansible_host }}"
        image: "{{ hostvars[item].container_image }}"
        privileged: "{{ hostvars[item].container_privileged }}"
        state: started
        networks:
          - molecule-network-test
        ports:
          - "2200:22"
          - "2600:443"
        volumes:
          - /etc/sysctl.d/99-zceos.conf:/etc/sysctl.d/99-zceos.conf:ro
        tmpfs:
          - /tmp
        command: /sbin/init
        env: "{{ hostvars[item].container_env }}"
        network_cli_ssh_type: paramiko
      loop: "{{ groups['arista_switches'] }}"

    - name: Wait for SSH to be available
      ansible.builtin.wait_for:
        host: "{{ hostvars[item].ansible_host }}"
        port: 22
        timeout: 300
      loop: "{{ groups['arista_switches'] }}"

    - name: Wait for EOS API to be ready
      ansible.builtin.uri:
        url: "https://{{ hostvars[item].ansible_host }}:443/command-api"
        method: GET
        validate_certs: false
        timeout: 10
      register: api_check
      until: api_check.status == 200
      retries: 30
      delay: 10
      loop: "{{ groups['arista_switches'] }}"
      failed_when: false

Create molecule/network/converge.yml:

The converge playbook executes the network playbook under test against the containerized network devices.

---
- name: Converge
  ansible.builtin.import_playbook: ../../network_playbook.yml

Create molecule/network/verify.yml:

The verify playbook tests network device functionality using EOS-specific commands to validate the playbook's effects on device configuration.

---
- name: Verify network devices
  hosts: arista_switches
  gather_facts: false
  connection: ansible.netcommon.network_cli
  tasks:
    - name: Get EOS version
      arista.eos.eos_command:
        commands:
          - show version
      register: version_output

    - name: Verify EOS version contains expected info
      ansible.builtin.assert:
        that:
          - "'Arista' in version_output.stdout[0]"
        fail_msg: "EOS version check failed"

    - name: Gather EOS facts
      arista.eos.eos_facts:
        gather_subset: min
      register: eos_facts

    - name: Verify system facts
      ansible.builtin.assert:
        that:
          - eos_facts.ansible_facts.ansible_net_version is defined
          - eos_facts.ansible_facts.ansible_net_hostname is defined
        fail_msg: "Required EOS facts not available"

Create molecule/network/cleanup.yml:

The cleanup playbook removes any test configurations from the network devices to restore them to a clean state.

---
- name: Cleanup network configuration
  hosts: arista_switches
  gather_facts: false
  connection: ansible.netcommon.network_cli
  tasks:
    - name: Remove test configurations
      arista.eos.eos_config:
        lines:
          - no interface Loopback99
        backup: false
      failed_when: false

Create molecule/network/destroy.yml:

The destroy playbook removes the containerized network devices and networks, completing the test lifecycle cleanup.

---
- name: Destroy network device containers
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Remove EOS containers
      containers.podman.podman_container:
        name: "{{ hostvars[item].ansible_host }}"
        state: absent
      loop: "{{ groups['arista_switches'] }}"
      failed_when: false

    - name: Remove container network
      containers.podman.podman_network:
        name: molecule-network-test
        state: absent
      failed_when: false

Running the Tests

Testing Individual Scenarios

Linux Scenario:

# Test the complete lifecycle
molecule test --scenario-name linux

# Run specific actions
molecule create --scenario-name linux
molecule converge --scenario-name linux
molecule verify --scenario-name linux

Network Scenario:

# Test the network scenario
molecule test --scenario-name network

# Converge only
molecule converge --scenario-name network

Expected Output

$ molecule test --scenario-name linux

INFO     linux  discovery: scenario test matrix: dependency, create, prepare, converge, idempotence, verify, cleanup, destroy
INFO     linux  dependency: Executing
INFO     linux  create: Executing
  ┌──────────────────────────────────────────────────────────────────────────────────
   ansible-playbook --inventory /tmp/molecule/linux/inventory
     --skip-tags molecule-notest,notest
     /tmp/molecule/linux/create.yml
       PLAY [Create container instances] ********************************************
     TASK [Create container network] **********************************************
   changed: [localhost]
     TASK [Create test containers] ************************************************
   changed: [localhost] => (item=web-server)
   changed: [localhost] => (item=db-server)
     TASK [Wait for containers to be ready] **************************************
   ok: [web-server]
   ok: [db-server]
     PLAY RECAP ********************************************************************
   localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    └─ Return code: 0 ─────────────────────────────────────────────────────────────────
INFO     linux  create: Executed: Successful
INFO     linux  prepare: Executing
  ┌──────────────────────────────────────────────────────────────────────────────────
   ansible-playbook --inventory /tmp/molecule/linux/inventory
     --skip-tags molecule-notest,notest
     /tmp/molecule/linux/prepare.yml
       PLAY [Prepare container instances] *******************************************
     TASK [Install required packages] *********************************************
   changed: [web-server] => (item=python3)
   changed: [db-server] => (item=python3)
   changed: [web-server] => (item=systemd)
   changed: [db-server] => (item=systemd)
     PLAY RECAP ********************************************************************
   web-server                 : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   db-server                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    └─ Return code: 0 ─────────────────────────────────────────────────────────────────
INFO     linux  prepare: Executed: Successful
INFO     linux  converge: Executing
INFO     linux  idempotence: Executing
INFO     linux  verify: Executing
INFO     linux  cleanup: Executing
INFO     linux  destroy: Executing

SCENARIO RECAP
linux                       : actions=8  successful=8  disabled=0  skipped=0  missing=0  failed=0

Advanced Testing Patterns

Environment-Specific Testing

Test playbooks with different inventory configurations:

# Test with specific inventory
molecule converge --scenario-name linux --inventory inventory/production.yml

Multiple Playbook Testing

Create scenarios to test different playbook combinations:

# Initialize scenario for site.yml testing
molecule init scenario site-testing

# Test the complete site playbook
molecule test --scenario-name site-testing

Parallel Testing

Run multiple scenarios simultaneously:

# Test both scenarios in parallel
molecule test --parallel

Summary

This guide demonstrated comprehensive playbook testing using Molecule with both container and network device scenarios. Key benefits include:

  • Complete test lifecycle management with create, converge, verify, and cleanup phases
  • Multiple testing environments supporting both Linux containers and network devices
  • Realistic testing scenarios using actual device containers (cEOS) rather than simulation
  • Integration with ansible-creator for standardized project structure
  • Flexible inventory management supporting both static and dynamic configurations

The ansible-native approach ensures that your Molecule tests integrate seamlessly with existing Ansible workflows while providing comprehensive validation of playbook functionality across diverse infrastructure environments.