In this series of posts I am going to attempt to build and maintain a reference layer 3 leaf spine design using a python script. I will be using Arista vEOS for this, but it could be adapted to another NOS with some template modifications.

Topology

L3LS

IP Addressing

  • 192.168.0.0/16 will be dedicated to loopbacks, starting with 192.168.0.1/32.
  • 172.16.0.0/12 will be dedicated to point to point links between the spines and leafs, starting with 172.16.0.0/30.
  • 10.0.0.0/8 will be used for server VLANs. With leaf-1 using 10.1.0.0/16, leaf-2 using 10.2.0.0/16 and so on.

The only IP info that is not on the diagram is the management network, which will be in its own VRF. The subnet is 198.51.100.0/24. We will be using it in the baseconfig portion after that we will be using the DNS names of the switches.

Building the baseconfig

The plan is to use python to configure the switches without having to manually ssh or console to the switches, however, we cannot do that until the servers have a base configuration applied. Rather than applying that manually in advance, I am going create a Jinja2 template with the base config and two yaml files to store the data. We will still be dumping the config into the console after its generated, but these files will be expanded in future posts where we actually automate configuration changes.

hosts.yaml

spine-1:
  managementip: 198.51.100.254/24
  managementgw: 198.51.100.1
  site: ny
spine-2:
  managementip: 198.51.100.253/24
  managementgw: 198.51.100.1
  site: ny
leaf-1:
  managementip: 198.51.100.252/24
  managementgw: 198.51.100.1
  site: ny
leaf-2:
  managementip: 198.51.100.251/24
  managementgw: 198.51.100.1
  site: ny

groups.yaml

defaults:
  domain: example.net
  ntpserver1: 0.pool.ntp.org
  ntpserver2: 1.pool.ntp.org
  nameserver1: 8.8.8.8
  nameserver2: 8.8.4.4
  domainname: example.net
  username: admin
  password: admin

ny:
  timezone: America/New_York

arista_eos:
  nos: arista_eos
  iprouting: True
  lldp: True

baseconfig.j2

! Base configuration
!
hostname {{ hostname }}
ip name-server {{ defaults.nameserver1 }}
ip name-server {{ defaults.nameserver2 }}
ip domain-name {{ defaults.domainname }}
!
ntp source Management1
ntp server {{ defaults.ntpserver1 }} prefer
ntp server {{ defaults.ntpserver2 }}
!
username admin role network-admin secret {{ defaults.password }}
!
clock timezone {{ site.timezone }}
!
vrf definition management
!
! Use https in production
!
management api http-commands
   protocol http
   no shutdown
   !
   vrf management
      no shutdown
!
interface Management1
  vrf forwarding management
  ip address {{ host.managementip }}
!
ip route vrf management 0.0.0.0/0 {{ host.managementgw }}
!
end

Now that we have our data and config template its time to write some python to generate the configs.

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
            ):
        with open(hosts) as file1:
            self.hosts = yaml.load(file1)
        with open(groups) as file2:
            self.groups = yaml.load(file2)
        self.baseconfig = baseconfig
        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)


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

I added if __name__ == "__main__": so that I could run the file and it would generate the configs. I set it up as class because the intention is to use this class in other scripts.

After running the file there are 4 files in the configs folders.

spine-1.base.config

! Base configuration
!
hostname spine-1
ip name-server 8.8.8.8
ip name-server 8.8.4.4
ip domain-name example.net
!
ntp source Management1
ntp server 0.pool.ntp.org prefer
ntp server 1.pool.ntp.org
!
username admin role network-admin secret admin
!
clock timezone America/New_York
!
vrf definition management
!
! Use https in production
!
management api http-commands
   protocol http
   no shutdown
   !
   vrf management
      no shutdown
!
interface Management1
  vrf forwarding management
  ip address 198.51.100.254/24
!
ip route vrf management 0.0.0.0/0 198.51.100.1
!
end

spine-2.base.config

! Base configuration
!
hostname spine-2
ip name-server 8.8.8.8
ip name-server 8.8.4.4
ip domain-name example.net
!
ntp source Management1
ntp server 0.pool.ntp.org prefer
ntp server 1.pool.ntp.org
!
username admin role network-admin secret admin
!
clock timezone America/New_York
!
vrf definition management
!
! Use https in production
!
management api http-commands
   protocol http
   no shutdown
   !
   vrf management
      no shutdown
!
interface Management1
  vrf forwarding management
  ip address 198.51.100.253/24
!
ip route vrf management 0.0.0.0/0 198.51.100.1
!
end

leaf-1.base.config

! Base configuration
!
hostname leaf-1
ip name-server 8.8.8.8
ip name-server 8.8.4.4
ip domain-name example.net
!
ntp source Management1
ntp server 0.pool.ntp.org prefer
ntp server 1.pool.ntp.org
!
username admin role network-admin secret admin
!
clock timezone America/New_York
!
vrf definition management
!
! Use https in production
!
management api http-commands
   protocol http
   no shutdown
   !
   vrf management
      no shutdown
!
interface Management1
  vrf forwarding management
  ip address 198.51.100.252/24
!
ip route vrf management 0.0.0.0/0 198.51.100.1
!
end

leaf-2.base.config

! Base configuration
!
hostname leaf-2
ip name-server 8.8.8.8
ip name-server 8.8.4.4
ip domain-name example.net
!
ntp source Management1
ntp server 0.pool.ntp.org prefer
ntp server 1.pool.ntp.org
!
username admin role network-admin secret admin
!
clock timezone America/New_York
!
vrf definition management
!
! Use https in production
!
management api http-commands
   protocol http
   no shutdown
   !
   vrf management
      no shutdown
!
interface Management1
  vrf forwarding management
  ip address 198.51.100.251/24
!
ip route vrf management 0.0.0.0/0 198.51.100.1
!
end

Apply config and test

I cut and paste each config into in the switches and confirmed connectivity.

PING spine-1 (198.51.100.254) 56(84) bytes of data.
64 bytes from spine-1 (198.51.100.254): icmp_seq=1 ttl=64 time=1.15 ms
64 bytes from spine-1 (198.51.100.254): icmp_seq=2 ttl=64 time=0.676 ms
64 bytes from spine-1 (198.51.100.254): icmp_seq=3 ttl=64 time=0.771 ms

--- spine-1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 28ms
rtt min/avg/max/mdev = 0.676/0.865/1.149/0.205 ms

Next Steps

So far we have built a base configuration generator and cut and pasted a config. This is not really automation yet, but it is a start. In the next post we will be creating the data files and building the configuration for the spine switches.

All of the files used in this post can be found here.