Getting Started With BGP Address Family Resource Modules

July 27, 2021 by Rohit Thakur

Modern enterprise networks have grown a lot in size and complexity, making them more difficult to manage than ever before. Working on these complex networks manually can be tedious, time-consuming, costly, and more prone to error.

Red Hat Ansible Network Automation resource modules provide a path for users to ease the network management, especially across multiple different product vendors. This enables users to manipulate configuration as structured data without worrying about network platform specific details.

In this blog post, we’ll explore the newly added BGP address family resource module using junipernetwork.junos.junos_bgp_address_family as an example. Similar blogs are also available, which cover resource modules for OSPFv2, ACLs, BGP Global and route maps.

Border Gateway Protocol (BGP) is a standardized exterior gateway protocol designed to exchange routing and reachability information between autonomous systems (AS) on the internet. The protocol is often classified as a path vector protocol, but is sometimes also classed as a distance-vector routing protocol. It is used in larger network setups, as Network World so aptly observes:

BGP has been called the glue of the Internet and the postal service of the internet. One comparison likens BGP to GPS applications on mobile phones.

Managing the BGP address family manually for a network device can be a very difficult and tedious task, and more often this needs to be performed carefully, as the manual process is prone to human error.

This blog post will demonstrate the BGP address family resource module with the Juniper Networks Junos Platform. We will walk through several examples and describe the use cases for each state parameter and how we envision these being used in real world scenarios.

 

BGP address family resource modules example: 

The goal of BGP address family resource modules is to make sure configurations are consistently applied across the infrastructure with less effort. It simplifies management and makes it faster and easier to scale without worrying about the actual implementation details of the network platforms working under the hood.

Ansible content refers to Ansible Playbooks, modules, module utilities and plugins. Basically all of the Ansible tools that users utilize to create their Ansible automation. The BGP address family resource module is part of Ansible Content Collections. To learn more about Ansible Content Collections, you can check out this introductory blog: Getting Started with Ansible Content Collections or this quick YouTube overview and demonstration.

Let’s have a closer look at how the BGP address family resource modules work. As an example, we pick the junos_bgp_address_family resource module. In this blog, we’ll be using a Junos device model vSRX with version 18.4R1-S3.1 for all the configuration management specific operations. Also, to better showcase the effect of the modules, we will start with some BGP  address family config specific attributes already configured. 

 

Accessing and using the Juniper Networks Junos Collection

To download the Junos Collection, refer to Ansible automation hub (fully supported, requires a Red Hat Ansible Automation Platform subscription) or Ansible Galaxy (upstream community supported):

To learn more about how to configure downloading via the ansible.cfg file or requirements.yml file, please refer to the blog, Hands On With Ansible Collections.

Before we get started, let’s quickly explain the rationale behind naming the network resource modules. Notice for resource modules that configure BGP routes address family, the newly added modules will be named based on their behaviour or impact. To manage global BGP config, we have the separate resource module junos_bgp_global and for address family configuration we have the separate resource module junos_bgp_address_family. This was done so that Ansible Playbooks would continue working for customers using existing network modules  while allowing sufficient time to migrate to the new network automation modules.

A module to configure BGP address family attributes is also available for the following supported platforms:

The BGP address family resource module provides the same level of functionality that a user can achieve when configuring manually on the Juniper Junos device with all advantages of Ansible automation, plus with an added edge of Ansible fact gathering and resource module approach, which is more closely aligned with network professionals’ day-to-day working.

In Ansible Core 2.10 and newer, short names are supported with the meta/runtime.yml file for any Ansible Collection. For more information on redirects, read the documentation here.

Long name: junipernetworks.junos.junos_bgp_address_family
Short Name: junipernetworks.junos.bgp_address_family

However, we will maintain backwards compatibility so your automation does not lose functionality.

There is also an online webinar that goes into more details on webinar naming and fully qualified Collection names (FQCN): Webinar: Migrating to Ansible Collections.

 

Use Case: BGP address family configuration changes

 

Using state gathered - Building an Ansible inventory

Resource modules allow the user to read in existing network configuration and convert that into a structured data model. The state: gathered is the equivalent for gathering Ansible facts for this specific resource. This example will read in the existing network configuration and store it as a flat-file.

Here is an Ansible Playbook example of using state: gathered and storing the result as YAML into host_vars. If you are new to Ansible inventory and want to learn about group_vars and host_vars, please refer to the documentation here.

The original running configuration from the Juniper Junos device is here:

vagrant@vsrx# show protocols bgp    
family inet {
    flow {
        loops 4;
        no-install;
        output-queue-priority expedited;
        legacy-redirect-ip-action {
            receive;
            send;
        }
        secondary-independent-resolution;
    }
}
family evpn {
    signaling {
        accepted-prefix-limit {
            maximum 20;
            teardown 98 idle-timeout 2001;
        }
        damping;
        defer-initial-multipath-build {
            maximum-delay 2;
        }
    }
}
group internal {
    neighbor 10.10.10.1 {
        peer-as 64532;
    }
}

Gist source available here.

---
- name: convert configured BGP address family resource to structured data
  hosts: junos
  vars:
    inventory_dir: "lab_inventory"
    inventory_hostname: "junos"
  gather_facts: false
  tasks:

  - name: Use the bgp_address_family resource module to gather the config
    junipernetworks.junos.junos_bgp_address_family:
      state: gathered
    register: bgp_address_family

  - name: Create inventory directory
    file:
      path: "{{ inventory_dir }}/host_vars/{{ inventory_hostname }}"
      state: directory

  - name: Write the BGP Address Family configuration to a file
    copy:
      content: "{{ {'bgp_address_family': bgp_address_family['gathered']} | to_nice_yaml }}"
      dest: "{{ inventory_dir }}/host_vars/{{ inventory_hostname }}/bgp_address_family.yaml"

Gist source available here

Execute the Ansible Playbook with the ansible-playbook command:

$ ansible-playbook example.yml

Here is the data structure that was created from reading/gathered operation in a brown-field configuration:

$ cat lab_inventory/host_vars/junos/bgp_address_family.yaml 
bgp_address_family:
    address_family:
    -   af_type:
        -   accepted_prefix_limit:
                idle_timeout_value: 2001
                limit_threshold: 98
                maximum: 20
            damping: true
            defer_initial_multipath_build:
                maximum_delay: 2
            type: signaling
        afi: evpn
    -   af_type:
        -   legacy_redirect_ip_action:
                receive: true
                send: true
            loops: 4
            no_install: true
            output_queue_priority_expedited: true
            secondary_independent_resolution: true
            type: flow
        afi: inet

You can check out the full detailed listing of the output of this example in the state: gathered reference gist.

 

Using state merged - Pushing configuration changes

The state merged will take your Ansible configuration data (i.e. Ansible variables) and merge them into the network device’s running configuration. The merge parameter will not affect unspecified configuration. Let’s walk through an example.

We will modify the flat-file created in the first example with a configuration to be merged, which includes the following major changes:

  • Configure family attribute for groups 
  • Configure family attribute for neighbors

 So now our Source of Truth (SOT) file should have the following content. 

address_family:
    -   af_type:
        -   accepted_prefix_limit:
                idle_timeout_value: 2001
                limit_threshold: 98
                maximum: 20
            damping: true
            defer_initial_multipath_build:
                maximum_delay: 2
            type: signaling
        afi: evpn
    -   af_type:
        -   legacy_redirect_ip_action:
                receive: true
                send: true
            loops: 4
            no_install: true
            output_queue_priority_expedited: true
            secondary_independent_resolution: true
            type: flow
        -   type: 'unicast'
            extended_nexthop: true
            extended_nexthop_color: true
            local_ipv4_address: '9.9.9.9'
        -   type: 'labeled-unicast'
            entropy_label:
              no_next_hop_validation: true
            explicit_null:
              connected_only: true
            per_prefix_label: true
            per_group_label: true
            prefix_limit:
              maximum: 20
              limit_threshold: 99
              forever: true
            resolve_vpn: true
            rib: 'inet.3'
            route_refresh_priority_expedited: true
            route_refresh_priority_priority: 3
        afi: inet
    groups:
    -   name: "internal"
        address_family:
          - afi: 'evpn'
            af_type:
              - type: 'signaling'
                accepted_prefix_limit:
                  maximum: 20
                  limit_threshold: 98
                  idle_timeout_value: 2001
                damping: true
                defer_initial_multipath_build:
                  maximum_delay: 2
        neighbors:
        -   neighbor_address: 10.10.10.1
            address_family:
              - afi: 'evpn'
                af_type:
                  - type: 'signaling'
                    accepted_prefix_limit:
                      maximum: 20

Now let’s create an Ansible Playbook to merge this new configuration into the network device’s running configuration:

---
- name: Merged state play
  hosts: junos
  gather_facts: false
  tasks:
    - name: Merge BGP address family config with device existing bgp address family config
      junipernetworks.junos.junos_bgp_address_family:
        state: merged
        config: "{{ bgp_address_family }}"

Execute the Ansible Playbook with the ansible-playbook command:

$ ansible-playbook merged.yml

And, once we run the respective merge play, all of the provided parameters will be configured on the Junos appliance with Ansible changed=True in the task output.

Note the network device configuration after the merge operation:

vagrant@vsrx# show protocols bgp    

family evpn {
    signaling {
        accepted-prefix-limit {
            maximum 20;
            teardown 98 idle-timeout 2001;
        }
        damping;
        defer-initial-multipath-build {
            maximum-delay 2;
        }
    }
}
group internal {
    family evpn {
        signaling {
            accepted-prefix-limit {
                maximum 20;
                teardown 98 idle-timeout 2001;
            }
            damping;
            defer-initial-multipath-build {
                maximum-delay 2;
            }                           
        }                               
    }                                   
    neighbor 10.10.10.1 {               
        family evpn {                   
            signaling {                 
                accepted-prefix-limit { 
                    maximum 20;         
                }                       
            }                           
        }                               
        peer-as 64532;                  
    }                                   
}

Note that this listing only shows a few highlights; the full listing is available in the merged Gist.

Let’s take a look at what has changed through this operation: if we go through the device output, there are a few observations:

  • Attribute group with name “internal” configured with intended changes. 
  • Address family attributes configured for the intended neighbor.

Ansible charm of idempotency 

With the second run, the respective merge play runs again and idempotency comes into the picture. If nothing’s changed, play run results in changed=False, which confirms to the user that all of the provided configurations in the play are already configured on the Junos appliance, and that our BGP address family source of truth is indeed in sync with the actual device configuration.

The idempotency validation task playbook should look like this:

- name: Merge with the existing on-box configuration (IDEMPOTENT)
  junipernetworks.junos.junos_bgp_address_family:
    config: "{{ bgp_address_family }}"
    state: merged
  register: merged

- name: Asset that the source of truth is in sync with device config
  assert:
    that:
    - merged.changed == False

 

Using state replaced - Replacing configuration

The replaced state enables the user to replace the on-box configuration subsection with the provided configuration subsection in the task. Let us explore the replaced state behaviour for the bgp_address_family resource modules:

  1. What could be removed?
    - All the address family sub attributes (identified by the afi) that are in running-config but not present in the task, will be negated.

  2. What could be merged?
    - New address family entries (including groups and neighbors).
    - New Network Layer Reachability Information (NLRI) entries within an existing address family.
    - New attributes in existing NLRI entries.

  3. What shall remain unchanged?
    - Existing address families that are not specified in the task will remain unaffected.

Now that we know what a replaced state offers us, let's complete a task that demonstrates all of the above.

  1. Remove ‘labeled-unicast’ NLRI in the address family ‘inet’.
  2. Remove ‘output_queue_priority_expedited’ for ‘flow’ NLRI entry.
  3. Add ‘prefix_limit’ to NLRI type ‘flow’ in address family->af_type->type-> inet .
  4. Add address family ‘inet’  for neighbor “10.10.10.1” in group ‘internal’
  5. Update local_ipv4_address in address_family->af_type->type->unicast to’ 192.168.122.40’

So now our updated (SOT) file should have the following content. 

bgp_address_family:
  address_family:
    - af_type:
        - accepted_prefix_limit:
            idle_timeout_value: 2001
            limit_threshold: 98
            maximum: 20
          damping: true
          defer_initial_multipath_build:
            maximum_delay: 2
          type: signaling
      afi: evpn
    - af_type:
        - legacy_redirect_ip_action:
            receive: true
            send: true
          loops: 4
          no_install: true
          secondary_independent_resolution: true
          prefix_limit:
            maximum: 15
            limit_threshold: 98
            forever: true
          type: flow
        - type: unicast
          extended_nexthop: true
          extended_nexthop_color: true
          local_ipv4_address: 192.168.122.40
        - type: labeled-unicast
          entropy_label:
            no_next_hop_validation: true
          explicit_null:
            connected_only: true
          per_prefix_label: true
          per_group_label: true
          prefix_limit:
            maximum: 20
            limit_threshold: 99
            forever: true
          resolve_vpn: true
          rib: inet.3
          route_refresh_priority_expedited: true
          route_refresh_priority_priority: 3
      afi: inet
  groups:
    - name: internal
      address_family:
        - afi: evpn
          af_type:
            - type: signaling
              accepted_prefix_limit:
                maximum: 20
                limit_threshold: 98
                idle_timeout_value: 2001
              damping: true
              defer_initial_multipath_build:
                maximum_delay: 2
      neighbors:
        - neighbor_address: 10.10.10.1
          address_family:
            - afi: evpn
              af_type:
                - type: signaling
                  accepted_prefix_limit:
                    maximum: 20
            - afi: inet
              af_type:
                - type: unicast
                  extended_nexthop: true
                  extended_nexthop_color: true
                  local_ipv4_address: 192.168.56.120

Check out the full input config structure if you want to learn more task details.

Again, we create an Ansible Playbook to replace this new configuration into the network device’s running configuration and verify with the following set of tasks.

We now push this structured configuration data to the target device and verify with the following set of tasks.

---
- name: Replaced state play
  hosts: junos
  gather_facts: false
  tasks:
    - name: Replace running BGP address family config with     provided config
      junipernetworks.junos.junos_bgp_address_family:
        state: replaced
        config: "{{ bgp_address_family }}"
        - name: Replace existing on-box configuration with provided (IDEMPOTENT)
          junipernetworks.junos.junos_bgp_address_family:
            config: "{{ bgp_address_family }}"
            state: replaced
          register: replaced

    	- name: Asset that the source of truth is in sync with device config
          assert:
          that:
           - overridden.changed == False

Once we run the respective replaced play, we can see that all extraneous configurations have been removed and updates have been applied on the Junos appliance with Ansible changed=True.

The Juniper network device configuration after the replaced operation:

vagrant@vsrx# show protocols bgp               
family inet {
    unicast {
        local-ipv4-address 192.168.122.40;
        extended-nexthop;
        extended-nexthop-color;
    }

Check out the corresponding Gist for the full Juniper device configuration.

 

Using state overridden - Overriding configuration 

If the user wants to re-configure the Junos appliance entirely, which means overriding existing BGP address family configuration with the provided BGP address family configuration, then the resource module overridden state comes into the picture.

Using the overridden state, a user can override all BGP resource address family attributes with user-provided BGP configuration. Since this resource module state overrides all pre-existing attributes of the resource module, the overridden state should be used cautiously; if all the configurations are mistakenly overridden with the play input configuration, it might create unnecessary issues for the network administrators. 

Now that we know what an overridden state offers us, let us review a scenario where we could make good use of the overridden state.

Let’s assume some unwanted address-family configuration got pushed to our target device. As a result, the device started behaving erroneously. Many of us already know how hard it could be to inspect and revert these changes manually, so instead we can push the existing structured data in our flat-file with state: overridden, thereby enforcing the source-of-truth and removing all unwanted configuration lines.

Before we revert the changes to the previous stable captured within our flat-file (SOT) file and align the on-box route-map configuration with its source of truth, let us find out how the current (running) config has deviated from the intended config. To do that, we will leverage the very useful fact_diff and to_paths plugins from the ansible.utils Collection.

- name: Gather current BGP address family configuration from the device
  junipernetworks.junos.junos_bgp_address_family:
    state: gathered
  register: result

- name: Find out diff between intended and current configuration
  ansible.utils.fact_diff:
    before: "{{ bgp_address_family|ansible.utils.to_paths }}"
    after: "{{ result['gathered']|ansible.utils.to_paths }}"

Check out the corresponding Gist if you want to learn more details.

In the above tasks, we first gather the existing BGP address family configuration from the target device as structured data and then compare it with the source of truth in the flat-file. Running this playbook renders the deviation in a dot delimited format, which can also be saved in a file for auditing purposes in the future.

Check out the corresponding Gist for more details.

Okay, so now that we know what changed, let’s restore the route map configuration to it’s correct state with the following:


---
- name: Overridden state play
  hosts: junos
  gather_facts: false
  tasks:
    - name: Override running BGP address family config with provided config
      junipernetworks.junos.junos_bgp_address_family:
        state: overridden
        config: "{{ bgp_address_family }}"
    - name: Override existing on-box configuration with provided (IDEMPOTENT)
      junipernetworks.junos.junos_bgp_address_family:
        config: "{{ bgp_address_family }}"
        state: overridden
      register: overridden

    - name: Asset that the source of truth is in sync with device config
      assert:
        that:
        - overridden.changed == False

Once we run the respective overridden play, all of the provided parameters will override all the existing BGP address family resource specific config on the Junos appliance with Ansible changed=True. We can verify this by inspecting the device that the BGP address family configurations have been reverted to in their desired state:

The Juniper network device configuration after the replaced operation:

vagrant@vsrx# show protocols bgp               
family inet {
    unicast {
        local-ipv4-address 192.168.122.40;
        extended-nexthop;
        extended-nexthop-color;
    }

Check out the corresponding Gist for full Juniper device configuration.

With the second run of the above play, there are no changes reported, which satisfies the Ansible idempotency.

 

Using state deleted - Delete configuration 

Now that we know how to configure and modify the BGP address family attributes, let’s take a look at how to delete them with state: deleted. 

Deleting specific BGP address family type

We have the ability to delete individual address family types (specified with afi) from the device with state: deleted. For example, the following task will remove BGP address family type ‘evpn’ config from the target device.

Let’s create an Ansible Playbook to delete running BGP address family resource configuration.

---
- name: Deleted state play
  hosts: Junos
  gather_facts: false
  tasks:
    - name: Delete BGP address family resource config
      Junipernetworks.junos.junos_bgp_address_family:
        config:
          address_family:
            - afi: evpn  
        state: deleted

After we execute the playbook, the network device configuration changed:

vagrant@vsrx# show protocols bgp               
family inet {
    unicast {
        local-ipv4-address 192.168.122.40;
        extended-nexthop;
        extended-nexthop-color;
    }

Make sure to look at the full listing of the changed values. If we dig into the above output briefly, we can observe following:

  • All the BGP address family config with type “evpn” have been removed from the on-box configuration.

 

Deleting all BGP address families config  from the device

When there is no config specified in the task, state: deleted would remove all the BGP address families from running-config.

---
- name: Deleted state play
  hosts: Junos
  gather_facts: false
  tasks:
    - name: Delete BGP address family resource config
      Junipernetworks.junos.junos_bgp_address_family:
        config:
        state: deleted

Executing the above task would remove all the BGP address families configuration from running config.

vagrant@vsrx# show protocols bgp            
group internal {
    neighbor 10.10.10.1 {
        peer-as 64532;
    }
}

 

Using state rendered - Development and working offline

Ansible renders the provided configuration in the task in the device-native format (for example, Junos XML). Ansible returns this rendered configuration in the rendered key in the result. Note this state does not communicate with the network device and can be used offline.

Let’s reuse the flat-file (SOT) as input config for the rendered operation.

---
- name: Rendered state play
  hosts: junos
  gather_facts: false
  tasks:
    - name: Render the provided configuration
      junipernetworks.junos.junos_bgp_address_family:
        config: "{{ bgp_address_family }}"
        state: rendered

This produces the following output:

"rendered": [
       <nc:protocols xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">
       <nc:bgp><nc:group><nc:name>internal/nc:name><nc:family>
 ]

Check out the corresponding Gist for more details.

If we dig into the above output, we can see that nothing has changed at all; rendered doesn’t even require the connection establishment with an actual network device.

 

Using state parsed - Development and working offline

Ansible parses the configuration from the running_configuration option into Ansible structured data in the parsed key in the result. Note this does not gather the configuration from the network device, so this state can be used offline.

As the config to be parsed, we take the XML format configuration as device support netconf:

 <?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="urn:uuid:0cadb4e8-5bba-47f4-986e-72906227007f">
    <configuration changed-seconds="1590139550" changed-localtime="2020-05-22 09:25:50 UTC">
        <version>18.4R1-S2.4</version>
        <protocols>
            <bgp>

See the complete running config input in the corresponding Gist.

The playbook to apply this configuration is:

---
- name: Parsed state play
  hosts: junos
  gather_facts: false
  tasks:
    - name: Parse the provided BGP address family configuration
      junipernetworks.junos.junos_bgp_address_family:
        running_config: "{{lookup('file', './bgp_family.cfg')}}"
        state: parsed

Execute the playbook, which generates the structured output:

"parsed": {
        "groups": [
            {
                "address_family": [
                    {
                        "af_type": [
                            {
                                "accepted_prefix_limit": {
                                    "idle_timeout_value": 2001,
                                    "limit_threshold": 98,
                                    "maximum": 20
                                },

See the full listing in the corresponding Gist.

If we dig into the above output, we can see that nothing has changed at all; parsed operation doesn’t even require the connection establishment with an actual network device because it is using the local file bgp_family.cfg. Note: parsed input to be provided as value to running_config key.

 

Takeaways & Next Steps

As shown above, with the help of the resource modules management of the BGP address family, resource-specific configurations can be greatly simplified. Users don't need to bother much about BGP address family implementation details for each platform, they can just enter the actual data. By using the merged, replaced and overridden parameters, we allow much more flexibility for network engineers to adopt automation in incremental steps. The other operations like gathered, rendered and parsed allow a better, user friendly handling of the facts and the data managed within these tasks.

If you want to learn more about Red Hat Ansible Automation Platform and network automation, you can check out these resources:

Share:

Topics:
Network Automation


 

Rohit Thakur

Rohit Thakur is a Senior Software Engineer for Red Hat Ansible Automation, where he brings over 6 years in the computer software industry.


rss-icon  RSS Feed

RH-ansible-automation-platform_trial-banner
rh-ansiblefest-blog-image-600x500