In the last post we built an Ansible playbook to manage interfaces on EOS and NX-OS switches. In this post we are going to add BGP configuration to advertise the loopbacks we created early.

Updated Topology

Ansible_BGP

The only change in the drawing is the addition of the AS numbers.

Update host_vars

We will start with the files created in the last post.

.
├── ansible.cfg
├── backup
├── group_vars
│   ├── eos.yml
│   └── nxos.yml
├── hosts-net
├── host_vars
│   ├── sw1.yml
│   └── sw2.yml
└── interface-playbook.yml

The first thing we are going to do is add the BGP configuration to the files in host_vars.

sw1.yml

bgp:
  asn: 1
  routerid: 1.1.1.1
  neighbors:
    - address: 192.168.12.2
      remoteas: 2
  networks:
    - address: 1.1.1.1/32

sw2.yml

bgp:
  asn: 2
  routerid: 2.2.2.2
  neighbors:
    - address: 192.168.12.1
      remoteas: 1
      addrfamily: ipv4 unicast
  networks:
    - address: 2.2.2.2/32
      addrfamily: ipv4 unicast

Create configuration templates

In the last post we used ansible modules that were written to perform interface specific tasks. For BGP, an EOS module does not exist so we are going to load a configuration files to the switches using the eos_config and nxos_config ansible modules. The easiest way to do this is to create Jinja2 templates. They will be stored in new folder called templates.

eos_bgp.j2

ip routing
!
router bgp {{ bgp.asn }}
   router-id {{ bgp.routerid }}
   {% for neighbor in bgp.neighbors -%}
   neighbor {{ neighbor.address }} remote-as {{ neighbor.remoteas }}
   {% endfor -%}
   {% for network in bgp.networks -%}
   network {{ network.address }}
   {% endfor -%}

nxos_bgp.j2

license grace-period
feature bgp
!
router bgp {{ bgp.asn }}
  router-id {{ bgp.routerid }}
  {% for network in bgp.networks -%}
  address-family {{ network.addrfamily }}
    network {{ network.address }}
  {% endfor -%}
  {% for neighbor in bgp.neighbors -%}
  neighbor {{ neighbor.address }}
    remote-as {{ neighbor.remoteas }}
    address-family {{ neighbor.addrfamily }}
  {% endfor -%}

The license grace-period is because I am using an NX-OS VM. A device with a license for BGP would not require this.

The files above will loop through all of the networks and neighbors in each switches Yaml file. We only have one for each right now but we can add networks or neighbors without having to rewrite the template.

Creating and running the playbooks

We currently have a single playbook that backups the configuration, sets up the interfaces, and finally writes the switches memory. In order to avoid duplicating code, I am going to split out the backup and write memory tasks into separate playbooks.

backup-playbook.yml

---

  - hosts: eos
    gather_facts: no

    tasks:

      - name: Backup eos device
        eos_config:
          backup: yes
        register: "{{ inventory_hostname }}"

  - hosts: nxos
    gather_facts: no

    tasks:

      - name: Backup nxos device
        nxos_config:
          backup: yes
        register: "{{ inventory_hostname }}"

writemem-playbook.yml

---

  - hosts: eos
    gather_facts: no

    tasks:

      - name: Save eos configuration
        eos_config:
          save_when: modified

  - hosts: nxos
    gather_facts: no

    tasks:

      - name: Save nxos configuration
        nxos_config:
          save_when: modified
        changed_when: false

Now we can create the playbook that adds the BGP configuration. I will also add a configs folder.

bgp-playbook.yml

---
  - hosts: eos:nxos
    gather_facts: no

    tasks:

      - name: Create configuration templates
        template:
          src: "templates/{{ ansible_network_os }}_bgp.j2"
          dest: "configs/{{ inventory_hostname }}.config"
  
  - hosts: eos
    gather_facts: no

    tasks:

      - name: load BGP config eos
        eos_config:
          src: "configs/{{ inventory_hostname }}.config"
        register: updates

      - name: debug updates
        debug:
          msg: "{{ updates.updates }}"

  - hosts: nxos
    gather_facts: no

    tasks:

      - name: load BGP config nxos
        nxos_config:
          src: "configs/{{ inventory_hostname }}.config"
        register: updates

      - name: debug updates
        debug:
          msg: "{{ updates.updates }}

The first task builds the configuration files for the hosts. The next task loads the configuration to switches in the eos group. The final task loads the configuration to switches in the nxos group. The debug task prints the commands that will be loaded on the switch. The final file we are going to create is a playbook that runs all of the playbooks at once.

all-playbook.yml

---

  - import_playbook: backup-playbook.yml
  - import_playbook: interface-playbook.yml
  - import_playbook: bgp-playbook.yml
  - import_playbook: writemem-playbook.yml

Here is what our directory looks like now.

.
├── all-playbook.yml
├── ansible.cfg
├── backup
├── backup-playbook.yml
├── bgp-playbook.yml
├── configs
├── group_vars
│   ├── eos.yml
│   └── nxos.yml
├── hosts-net
├── host_vars
│   ├── sw1.yml
│   └── sw2.yml
├── interface-playbook.yml
├── templates
│   ├── eos_bgp.j2
│   └── nxos_bgp.j2
└── writemem-playbook.yml

It is finally time to run the playbooks. I am going omit the output of the tasks that were run in the last post and focus on the BGP changes.

ansible-playbook all-playbook.yml

PLAY [eos] ************************************************************************************************************************************************************************************

TASK [Backup eos device] **********************************************************************************************************************************************************************

--snip--

PLAY [eos:nxos] *******************************************************************************************************************************************************************************

TASK [Create configuration templates] *********************************************************************************************************************************************************
changed: [sw2]
changed: [sw1]

PLAY [eos] ************************************************************************************************************************************************************************************

TASK [load BGP config eos] ********************************************************************************************************************************************************************
changed: [sw1]

TASK [debug updates] **************************************************************************************************************************************************************************
ok: [sw1] => {
    "msg": [
        "ip routing",
        "router bgp 1",
        "router-id 1.1.1.1",
        "neighbor 192.168.12.2 remote-as 2",
        "network 1.1.1.1/32"
    ]
}

PLAY [nxos] ***********************************************************************************************************************************************************************************

TASK [load BGP config nxos] *******************************************************************************************************************************************************************
changed: [sw2]

TASK [debug updates] **************************************************************************************************************************************************************************
ok: [sw2] => {
    "msg": [
        "license grace-period",
        "feature bgp",
        "router bgp 2",
        "router-id 2.2.2.2",
        "address-family ipv4 unicast",
        "network 2.2.2.2/32",
        "neighbor 192.168.12.1",
        "remote-as 1",
        "address-family ipv4 unicast"
    ]
}

--snip--

PLAY RECAP ************************************************************************************************************************************************************************************
sw1                        : ok=8    changed=3    unreachable=0    failed=0
sw2                        : ok=7    changed=2    unreachable=0    failed=0

The first play created the configuration files from the templates.

sw1.config

ip routing
!
router bgp 1
   router-id 1.1.1.1
   neighbor 192.168.12.2 remote-as 2
   network 1.1.1.1/32

sw2.config

license grace-period
feature bgp
!
router bgp 2
  router-id 2.2.2.2
  address-family ipv4 unicast
    network 2.2.2.2/32
  neighbor 192.168.12.1
    remote-as 1
    address-family ipv4 unicast

The next two plays load the the .config file on the devices. Lets see if it worked.

SW1#show ip bgp 
BGP routing table information for VRF default
Router identifier 1.1.1.1, local AS number 1
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
                    S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

        Network                Next Hop              Metric  LocPref Weight  Path
 * >    1.1.1.1/32             -                     0       0       -       i
 * >    2.2.2.2/32             192.168.12.2          0       100     0       2 i
SW2# show ip bgp 
BGP routing table information for VRF default, address family IPv4 Unicast
BGP table version is 5, local router ID is 2.2.2.2
Status: s-suppressed, x-deleted, S-stale, d-dampened, h-history, *-valid, >-best
Path type: i-internal, e-external, c-confed, l-local, a-aggregate, r-redist, I-i
njected
Origin codes: i - IGP, e - EGP, ? - incomplete, | - multipath, & - backup

   Network            Next Hop            Metric     LocPrf     Weight Path
*>e1.1.1.1/32         192.168.12.1                                   0 1 i
*>l2.2.2.2/32         0.0.0.0                           100      32768 i

Looks like everything worked. The Jinja Templates are a very powerful tool and can handle things like for loops and if statements, which allows you to use the same template for multiple tasks. The source code for this post can be found here.