This part is going to be very similar to part two. The only difference is we are going to generate the leaf configuration this time.

Topology

L3LS

The first step is to update ‘hosts.yaml`

hosts.yaml

Leaf-1

leaf-1:
  managementip: 198.51.100.252/24
  managementgw: 198.51.100.1
  site: ny
  role: leaf
  interfaces:
    - number: 1
      ip: 172.16.0.2/30
      description: P2P Link to Spine-1
    - number: 2
      ip: 172.16.0.10/30
      description: P2P Link to Spine-2
  loopbacks:
    - number: 0
      ip: 192.168.0.3/32
  routerid: 192.168.0.3
  bgpnetworks:
    - 192.168.0.3/32
  peergroups:
    - name: EBGP-TO-SPINE
      maxroutes: 12000
      remoteas: 65000
      neighbors:
        - 172.16.0.1
        - 172.16.0.9
      allowasin: 1
      routemaps:
        - name: ROUTE-MAP-OUT
          direction: out
  prefixlists:
    - name: PREFIX-LIST-OUT
      action: permit
      sequence: 10
      ip: 192.168.0.3/32
    - name: PREFIX-LIST-OUT
      action: permit
      sequence: 20
      ip: 172.16.0.0/16
    - name: PREFIX-LIST-OUT
      action: permit
      sequence: 30
      ip: 10.1.100.0/24
  vlaninterfaces:
    - number: 100
      description: Servers1
      mtu: 9214
      ip: 10.1.100.1/24
      arptimeout: 900

Leaf-2

leaf-2:
  managementip: 198.51.100.251/24
  managementgw: 198.51.100.1
  site: ny
  role: leaf
  interfaces:
    - number: 1
      ip: 172.16.0.6/30
      description: P2P Link to Spine-1
    - number: 2
      ip: 172.16.0.14/30
      description: P2P Link to Spine-2
  loopbacks:
    - number: 0
      ip: 192.168.0.4/32
  routerid: 192.168.0.4
  bgpnetworks:
    - 192.168.0.4/32
  peergroups:
    - name: EBGP-TO-SPINE
      maxroutes: 12000
      remoteas: 65000
      neighbors:
        - 172.16.0.5
        - 172.16.0.13
      allowasin: 1
  routemaps:
    - name: ROUTE-MAP-OUT
      direction: out
  prefixlists:
    - name: PREFIX-LIST-OUT
      action: permit
      sequence: 10
      ip: 192.168.0.4/32
    - name: PREFIX-LIST-OUT
      action: permit
      sequence: 20
      ip: 172.16.0.0/16
    - name: PREFIX-LIST-OUT
      action: permit
      sequence: 30
      ip: 10.2.100.0/24
  vlaninterfaces:
    - number: 100
      description: Servers1
      mtu: 9214
      ip: 10.2.100.1/24
      arptimeout: 900

Next we are going to create a file called leaf.yaml that will hold the information that is shared by all of the leaf switches.

leaf.yaml

---

vlans:
  - number: 100
    description: Servers1

routemaps:
  - name: ROUTE-MAP-OUT
    filter: permit
    sequence: 10
    action: matchip
    type: prefix-list
    actionname: PREFIX-LIST-OUT

accessinterfaces:
  - number: 9
    switchport: access
    vlan: 100

bgp:
  as: 65100
  distance: 20 200 200
  maxpaths: 4
  maxroutes: 4
  redistribute: connected

Next we will create the configuration template leaf.j2.

leaf.j2

ip routing
!
lldp run
!
{% for vlan in vlans -%}
vlan {{ vlan.number }}
  name {{ vlan.description }}
!
{% endfor -%}
{% for interface in host.interfaces -%}
interface Ethernet{{ interface.number }}
  description {{ interface.description }}
  logging event link-status
  no switchport
  ip address {{ interface.ip }}
  arp timeout 900
  mtu 9214
  no shutdown
!
{% endfor -%}
{% for loopback in host.loopbacks -%}
interface loopback{{ loopback.number }}
  ip address {{ loopback.ip }}
!
{% endfor -%}
{% for accessinterface in accessinterfaces -%}
interface Ethernet{{ accessinterface.number }}
  switchport access vlan {{ accessinterface.vlan }}
  no snmp trap link-status
  spanning-tree portfast
  spanning-tree bpduguard enable
  no shutdown
!
{% endfor -%}
{% for vlaninterface in host.vlaninterfaces -%}
interface Vlan{{ vlaninterface.number }}
  description {{ vlaninterface.description }}
  {% if vlaninterface.mtu -%}
  mtu {{ vlaninterface.mtu }}
  {% endif -%}
  ip address {{ vlaninterface.ip }}
  arp timeout 900
  no shutdown
!
{% endfor -%}
{% for routemap in routemaps -%}
route-map {{ routemap.name }} {{ routemap.filter }} 10
{% if routemap.action == 'matchip' -%}
  match ip address {{ routemap.type }} {{ routemap.actionname }}
{% endif -%}
!
{% endfor -%}
{% for prefixlist in host.prefixlists -%}
ip prefix-list {{ prefixlist.name }} seq {{ prefixlist.sequence }} {{ prefixlist.action }} {{ prefixlist.ip }}
{% endfor -%}
!
router bgp {{ bgp.as }}
  bgp log-neighbor-changes
  distance bgp {{ bgp.distance }}
  maximum-paths {{ bgp.maxpaths }} ecmp {{ bgp.maxroutes }}
  {% for peergroup in host.peergroups -%}
  neighbor {{ peergroup.name}} peer-group
  neighbor {{ peergroup.name }} remote-as {{ peergroup.remoteas }}
  neighbor {{ peergroup.name }} maximum-routes {{ peergroup.maxroutes }}
  neighbor {{ peergroup.name }} allowas-in {{ peergroup.allowasin }}
  {% for routemap in peergroup.routemap -%}
  neighbor {{ peergroup.name }} route-map {{ routemap.name }} {{ routemap.direction }}
  {% endfor -%}
  {% for neighbor in peergroup.neighbors -%}
  neighbor {{ neighbor }} peer-group {{ peergroup.name }}
  {% endfor -%}
  {% endfor -%}
  {% for network in host.bgpnetworks -%}
  network {{ network }}
  {% endfor -%}
  redistribute {{ bgp.redistribute }}
!
end

Finally we are going add a new function called generateleafconfig to the ConfigureLeafSpine class.

configureleafspine.py

#!/usr/bin/env python3

from jinja2 import Environment, FileSystemLoader
import yaml


class ConfigureLeafSpine():
    """Class to configure and maintain leaf spine switches"""

    def __init__(
            self,
            hosts,
            groups,
            baseconfig,
            spines,
            spineconfig,
            leafs,
            leafconfig
            ):
        with open(hosts) as file1:
            self.hosts = yaml.load(file1)
        with open(groups) as file2:
            self.groups = yaml.load(file2)
        with open(spines) as file3:
            self.spines = yaml.load(file3)
        with open(leafs) as file4:
            self.leafs = yaml.load(file4)
        self.baseconfig = baseconfig
        self.spineconfig = spineconfig
        self.leafconfig = leafconfig
        self.ENV = Environment(loader=FileSystemLoader('.'))

    def generatebaseconfig(self):
        """Generates base configuration files"""
        template = self.ENV.get_template(self.baseconfig)
        for key, value in self.hosts.items():
            config = template.render(
                defaults=self.groups['defaults'],
                hostname=key,
                host=value,
                site=self.groups[value['site']]
            )
            filename = 'configs/{0}-base.config'.format(key)
            with open(filename, 'w') as file:
                file.writelines(config)

    def generatespineconfig(self):
        """Generates the spine configuration"""
        template = self.ENV.get_template(self.spineconfig)
        for key, value in self.hosts.items():
            if value['role'] == 'spine':
                config = template.render(
                    host=value,
                    bgp=self.spines['bgp']
                    )
                filename = 'configs/{0}.config'.format(key)
                with open(filename, 'w') as file:
                    file.writelines(config)

    def generateleafconfig(self):
        """Generates the leaf configuration"""
        template = self.ENV.get_template(self.leafconfig)
        for key, value in self.hosts.items():
            if value['role'] == 'leaf':
                config = template.render(
                    host=value,
                    vlans=self.leafs['vlans'],
                    routemaps=self.leafs['routemaps'],
                    bgp=self.leafs['bgp']
                    )
                filename = 'configs/{0}.config'.format(key)
                with open(filename, 'w') as file:
                    file.writelines(config)


if __name__ == "__main__":
    lsconfig = ConfigureLeafSpine(
        'hosts.yaml',
        'groups.yaml',
        'baseconfig.j2',
        'spine.yaml',
        'spine.j2',
        'leaf.yaml',
        'leaf.j2'
    )
    lsconfig.generatebaseconfig()
    lsconfig.generatespineconfig()
    lsconfig.generateleafconfig()

Running the configureleafspine.py file will create the configuration files and save them to the configs folder.

leaf-1.config

ip routing
!
lldp run
!
vlan 100
  name Servers1
!
interface Ethernet1
  description P2P Link to Spine-1
  logging event link-status
  no switchport
  ip address 172.16.0.2/30
  arp timeout 900
  mtu 9214
  no shutdown
!
interface Ethernet2
  description P2P Link to Spine-2
  logging event link-status
  no switchport
  ip address 172.16.0.10/30
  arp timeout 900
  mtu 9214
  no shutdown
!
interface loopback0
  ip address 192.168.0.3/32
!
interface Ethernet9
  switchport access vlan 100
  no snmp trap link-status
  spanning-tree portfast
  spanning-tree bpduguard enable
  no shutdown
!
interface Vlan100
  description Servers1
  mtu 9214
  ip address 10.1.100.1/24
  arp timeout 900
  no shutdown
!
route-map ROUTE-MAP-OUT permit 10
match ip address prefix-list PREFIX-LIST-OUT
!
ip prefix-list PREFIX-LIST-OUT seq 10 permit 192.168.0.3/32
ip prefix-list PREFIX-LIST-OUT seq 20 permit 172.16.0.0/16
ip prefix-list PREFIX-LIST-OUT seq 30 permit 10.1.100.0/24
!
router bgp 65100
  bgp log-neighbor-changes
  distance bgp 20 200 200
  maximum-paths 4 ecmp 4
  neighbor EBGP-TO-SPINE peer-group
  neighbor EBGP-TO-SPINE remote-as 65000
  neighbor EBGP-TO-SPINE maximum-routes 12000
  neighbor EBGP-TO-SPINE allowas-in 1
  neighbor 172.16.0.1 peer-group EBGP-TO-SPINE
  neighbor 172.16.0.9 peer-group EBGP-TO-SPINE
  network 192.168.0.3/32
  redistribute connected
!
end

leaf-2.config

ip routing
!
lldp run
!
vlan 100
  name Servers1
!
interface Ethernet1
  description P2P Link to Spine-1
  logging event link-status
  no switchport
  ip address 172.16.0.6/30
  arp timeout 900
  mtu 9214
  no shutdown
!
interface Ethernet2
  description P2P Link to Spine-2
  logging event link-status
  no switchport
  ip address 172.16.0.14/30
  arp timeout 900
  mtu 9214
  no shutdown
!
interface loopback0
  ip address 192.168.0.4/32
!
interface Ethernet9
  switchport access vlan 100
  no snmp trap link-status
  spanning-tree portfast
  spanning-tree bpduguard enable
  no shutdown
!
interface Vlan100
  description Servers1
  mtu 9214
  ip address 10.2.100.1/24
  arp timeout 900
  no shutdown
!
route-map ROUTE-MAP-OUT permit 10
match ip address prefix-list PREFIX-LIST-OUT
!
ip prefix-list PREFIX-LIST-OUT seq 10 permit 192.168.0.4/32
ip prefix-list PREFIX-LIST-OUT seq 20 permit 172.16.0.0/16
ip prefix-list PREFIX-LIST-OUT seq 30 permit 10.2.100.0/24
!
router bgp 65100
  bgp log-neighbor-changes
  distance bgp 20 200 200
  maximum-paths 4 ecmp 4
  neighbor EBGP-TO-SPINE peer-group
  neighbor EBGP-TO-SPINE remote-as 65000
  neighbor EBGP-TO-SPINE maximum-routes 12000
  neighbor EBGP-TO-SPINE allowas-in 1
  neighbor 172.16.0.5 peer-group EBGP-TO-SPINE
  neighbor 172.16.0.13 peer-group EBGP-TO-SPINE
  network 192.168.0.4/32
  redistribute connected
!
end

In part four we are going to use Napalm to push the configurations to the switches. All of the code for this part can be found here.