Skip to content

[FEATURE] The juniper.device.junos_facts module does not populate the acl_interfaces network resource when a Junos firewall filter is attached to an interface using the standard input or output configuration hierarchy. #846

Description

@chidanandpujar

Category

  • Bug
  • Feature
  • Task

Collection Namespace & Version

Field Value
Collection namespace juniper.device
Collection version 2.0.2
Collection install path /home/jcluser/.ansible/collections/ansible_collection
# How to find it:
ansible-galaxy collection list | grep -E 'juniper|junipernetworks'

Module / Plugin Details

Field Value
Module / plugin name juniper.device.junos_facts
Module version (if shown in output)
Plugin type module
# How to inspect module documentation:
ansible-doc juniper.device.config

Description

The juniper.device.junos_facts module does not populate the acl_interfaces network resource when a Junos firewall filter is attached to an interface using the standard input or output configuration hierarchy.

The Junos acl_interfaces fact parser currently searches for firewall filter attachments under:

["input-list", "output-list"]

However, for a standard interface firewall filter attachment, Junos returns the filter under:

<input>
    <filter-name>ACL-FACT-TEST</filter-name>
</input>

or:

<output>
    <filter-name>ACL-FACT-TEST</filter-name>
</output>

As a result, the parser does not find the configured ACL attachment and ansible_network_resources does not populate acl_interfaces.

After changing the parser to process input and output, a second issue is exposed: he ACL is discovered, but Junos returns the ACL name as a dictionary:

{"filter-name": "ACL-FACT-TEST"}

The collector currently stores this entire dictionary as the ACL name:

name: "{'filter-name': 'ACL-FACT-TEST'}"

Instead, it should extract the value of filter-name and store:

name: ACL-FACT-TEST

Therefore, both the changes are required

Inventory File (sanitized)

[junos_devices]
device1 ansible_host=<redacted-ip>

[junos_devices:vars]
ansible_connection=netconf            # or juniper.device, ansible.netcommon.netconf
ansible_network_os=junipernetworks.junos.junos   # or juniper.device
ansible_user=<redacted>
ansible_ssh_pass=<redacted>
ansible_port=830
all:
  hosts:
    vendor_qfx:
      ansible_host: xx.xx.xx.xx
      ansible_user: "xyz"
      ansible_password: <xyz>
      ansible_port: 830
      ansible_connection: ansible.netcommon.netconf
      ansible_network_os: juniper.device.junos

Playbook Details

---
- name: Test Junos ACL interface facts
  hosts: junos
  gather_facts: false

  tasks:
    - name: Gather Junos ACL interface facts
      juniper.device.junos_facts:
        gather_subset:
          - min
        gather_network_resources:
          - acl_interfaces

    - name: Display gathered network resources
      ansible.builtin.debug:
        var: ansible_facts.network_resources
ansible-playbook -i inventory.ini test_acl_facts.yml -vvv

Steps to Reproduce

  1. Install or use the juniper.device collection.
ansible-galaxy collection install juniper.device
  1. Configure a Junos firewall filter as an input filter on an interface.
set interfaces xe-0/0/7 unit 0 family inet filter input ACL-FACT-TEST
<interface>
    <name>xe-0/0/7</name>
    <unit>
        <name>0</name>
        <family>
            <inet>
                <filter>
                    <input>
                        <filter-name>ACL-FACT-TEST</filter-name>
                    </input>
                </filter>
            </inet>
        </family>
    </unit>
</interface>
  1. Create the inventory file using NETCONF connectivity.

  2. Run the supplied playbook.

ansible-playbook -i inventory.ini test_acl_facts.yml -vvv
  1. Observe that ansible_facts.network_resources is empty even though the interface has an attached firewall filter.

Expected Behavior

The module should populate the interface ACL attachment (acl_interfaces) under ansible_network_resources:

"ansible_facts": {
        "ansible_net_api": "netconf",
        "ansible_net_gather_network_resources": [
            "acl_interfaces"
        ],
        "ansible_net_gather_subset": [
            "default"
        ],
        "ansible_net_hostname": "dc1-borderleaf2",
        "ansible_net_model": "vqfx-10000",
        "ansible_net_python_version": "3.9.19",
        "ansible_net_serialnum": "62521362500",
        "ansible_net_system": "junos",
        "ansible_net_version": "18.1R3-S5.3",
        "ansible_network_resources": {
            "acl_interfaces": [
                {
                    "access_groups": [
                        {
                            "acls": [
                                {
                                    "direction": "in",
                                    "name": "ACL-FACT-TEST"
                                }
                            ],
                            "afi": "ipv4"
                        }
                    ],
                    "name": "xe-0/0/7"
                }
            ],
            "junos_acl_interfaces": [
                {
                    "access_groups": [
                        {
                            "acls": [
                                {
                                     "direction": "in",
                                    "name": "ACL-FACT-TEST"
                                }
                            ],
                            "afi": "ipv4"
                        }
                    ],
                    "name": "xe-0/0/7"
                }
            ]
        }
    }

or

ansible_network_resources:
  acl_interfaces:
    - name: xe-0/0/7
      access_groups:
        - afi: ipv4
          acls:
            - name: ACL-FACT-TEST
              direction: in

additionally junos_acl_interfaces should also get populated

ansible_network_resources:
  junos_acl_interfaces:
    - name: xe-0/0/7
      access_groups:
        - afi: ipv4
          acls:
            - name: ACL-FACT-TEST
              direction: in

Actual Behavior

The module completes successfully but does not populate acl_interfaces and junos_acl_interfaces resource:

"ansible_facts": {
        "ansible_net_api": "netconf",
        "ansible_net_gather_network_resources": [
            "acl_interfaces"
        ],
        "ansible_net_gather_subset": [
            "default"
        ],
        "ansible_net_hostname": "dc1-borderleaf2",
        "ansible_net_model": "vqfx-10000",
        "ansible_net_python_version": "3.9.19",
        "ansible_net_serialnum": "62521362500",
        "ansible_net_system": "junos",
        "ansible_net_version": "18.1R3-S5.3",
        "ansible_network_resources": {}
    },
    "changed": false,
    "invocation": {
        "module_args": {
            "available_network_resources": false,
            "config_format": "text",
            "gather_network_resources": [
                "acl_interfaces"
            ],
            "gather_subset": [
                "min"
            ]
        }
    }

configured interface ACL attachment is silently skipped.


Environment

Ansible & Python

Item Version
ansible-core 2.15.13]
Python 3.9.19
OS Linux nita 5.14.0-503.14.1.el9_5.x86_64

Collection & Python Package Versions

ansible-galaxy collection list | grep -E 'juniper|junipernetworks'
pip show junos-eznc ncclient paramiko lxml
# ansible-galaxy collection list output:

juniper.device                2.0.2  
junipernetworks.junos         5.3.1  

# pip show output:
junos-eznc    : 2.8.2
ncclient      : 0.7.0
paramiko      : 5.0.0
lxml          : 6.1.1

Junos Device

Item Value
Junos version 18.1R3-S5.3
Junos platform / model vQFX-10000
Transport NETCONF over SSH
NETCONF port 830

Junos RPC / XML (if applicable)

<!-- RPC request -->

<!-- RPC response / error -->

Additional Context

Proposed Fix

Change 1: Process input and output

Change from:

for direction in ["input-list", "output-list"]:
    rendered_direction = "in" if direction == "input-list" else "out"

to:

for direction in ["input", "output"]:
    rendered_direction = "in" if direction == "input" else "out"

Change 2: Extract the ACL Name

Change from

for filter_name in acl_name:
    access_groups["acls"].append(
        {
            "name": filter_name,
            "direction": rendered_direction,
        },
    )

to:

for filter_name in acl_name:
    if isinstance(filter_name, dict):
        filter_name = filter_name.get("filter-name")

    access_groups["acls"].append(
        {
            "name": filter_name,
            "direction": rendered_direction,
        },
    )

The modified parser section is:

for direction in ["input", "output"]:
    rendered_direction = "in" if direction == "input" else "out"

    if conf["interface"]["unit"]["family"][family]["filter"].get(direction):
        acl_name = conf["interface"]["unit"]["family"][family]["filter"][
            direction
        ]

        if not isinstance(acl_name, list):
            acl_name = [acl_name]

        for filter_name in acl_name:
            if isinstance(filter_name, dict):
                filter_name = filter_name.get("filter-name")

            access_groups["acls"].append(
                {
                    "name": filter_name,
                    "direction": rendered_direction,
                },
            )


Support

As Red Hat Ansible Certified Content, this collection is entitled to support through the Ansible Automation Platform (AAP) using the Create issue button on the top right corner of the AAP support portal.

If a support case cannot be opened with Red Hat and the collection has been obtained either from Galaxy or GitHub, there may be community help available on the Ansible Forum.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions