initial checkin - a start of the generator, and some config files and overlays

This commit is contained in:
Pim van Pelt
2022-10-13 20:41:34 +02:00
commit 27d4ec6194
50 changed files with 1008 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.json
__pycache__/
.peeringdb/

102
README.md Normal file
View File

@ -0,0 +1,102 @@
# IPng Networks Lab environment
## High level overview
There's a disk image on each hypervisor called the `proto` image, which serves as the base
image for all VMs on it. Every now and again, the proto image is updated (Debian, FRR and VPP)
and from that base image, lab VMs are cloned from it and local filesystem overrides are put
in place on each clone. The lab is used, and when we're done with it, we simply destroy all
clones. This way, each time the lab is started, it is in a pristine state.
The `proto` image is shared among the hypervisors. Typically, maintenance will be performed
on one of the hypervisors, and then the `proto` image is snapshotted and copied to the other
machines.
### Proto maintenance
The main `vpp-proto` image runs on `hvn0.chbtl0.ipng.ch` with a VM called `vpp-proto`.
When you want to refresh the image, you can
```
spongebob:~$ ssh -A root@hvn0.chbtl0.ipng.ch
SNAP=$(date +%Y%m%d) ## 20221012
zfs snapshot ssd-vol0/vpp-proto-disk0@${SNAP}-before
virsh start --console vpp-proto
## Do the upgrades, make changes to vpp-proto's disk image
## You can always roll back to the -before image if you'd like to revert
virsh shutdown --console vpp-proto
zfs snapshot ssd-vol0/vpp-proto-disk0@${SNAP}-release
zrepl signal wakeup vpp-proto-snapshots
```
There is a `zrepl` running on this machine, which can pick up the snapshot by manually
waking up the daemon (see the last command above). Each of the hypervisors in the fleet
will watch this replication endpoint, and if they see new snapshots arrive, they will
do an incremental pull of the data to their own ZFS filesystem as a snapshot. Old/current
running labs will not be disrupted, as they will be cloned off of old snapshots.
You will find the image as `ssd-vol0/hvn0.chbtl0.ipng.ch/ssd-vol0/vpp-proto-disk0`:
```
spongebob:~$ ssh -A root@hvn0.lab.ipng.ch 'zfs list -t snap'
NAME USED AVAIL REFER MOUNTPOINT
ssd-vol0/hvn0.chbtl0.ipng.ch/ssd-vol0/vpp-proto-disk0@20221013-release 0B - 6.04G -
```
## Usage
There are three hypervisor nodes each running one isolated lab environment:
* hvn0.lab.ipng.ch runs VPP lab0
* hvn1.lab.ipng.ch runs VPP lab1
* hvn2.lab.ipng.ch runs VPP lab2
Now that we have a base image (in the form of `vpp-proto-disk0@$(date)-release`), we can
make point-in-time clones of them, copy over any specifics (like IP addresses, hostname,
SSH keys, Bird/FRR configs, etc). We do this on the lab controller `lab.ipng.ch` which:
1. Looks on the hypervisor to see if there is a running VM, and if there is, bails
1. Looks on the hypervisor to see if there is an existing cloned image, and if there is bails
1. Builds a local overlay directory using a generator and Jinja2 (ie. `build/vpp0-0/`)
1. Creates a new cloned filesystem based off of a base `vpp-proto-disk0` snapshot on the hypervisor
1. Mounts that filesystem
1. Rsync's the built overlay into that filesystem
1. Unmounts the filesystem
1. Starts the VM using the newly built filesystem
Of course, the first two steps are meant to ensure we don't clobber running labs, which can
be overridden with the `--force` flag. And when the lab is finished, it's common practice to
shut down the VMs and destroy the clones.
```
lab:~/src/ipng-lab$ ./destroy --host hvn0.lab.ipng.ch
lab:~/src/ipng-lab$ ./generate --host hvn0.lab.ipng.ch --overlay bird
lab:~/src/ipng-lab$ ./create --host hvn0.lab.ipng.ch --overlay bird
```
### Generate
The generator reads input YAML files one after another merging and overriding them as it goes along,
then for each node building a `node` dictionary alongside the `lab` and other information from the
config files. Then, it read the `overlays` dictionary for a given --overlay type, reading all the
template files from that overlay directory and assembling an output directory which will hold the
per-node overrides, emitting them to the directory specified by the --build flag. It also copies in
any per-node files (if they exist) from the overlays/$(overlay)/blobs/$(node.hostname)/ giving full
control of the filesystem's contents.
### Create
Based on a generated directory and a lab YAML description, uses SSH to connect to the hypervisor,
create a clone of the base `vpp-proto` snapshot, mount it locally in a staging directory, then rsync
over the generated overlay from files from the generator output (build/$(overlay)/$(node.hostname))
after which the directory is unmounted and the virtual machine booted from the clone.
If the VM is running, or there exists a clone, an error is printed and the process skips over that
node. It's wise to run `destroy` before `create` to ensure the hypervisors are in a pristine state.
### Destroy
Ensures that both the VMs are not running (and will stop them if they are), and their filesystem
clones are destroyed. Obviously this is the most dangerous operation of the bunch.

View File

@ -0,0 +1,6 @@
protocol bfd bfd1 {
interface "e*" {
interval 100 ms;
multiplier 20;
};
}

View File

@ -0,0 +1,19 @@
router id 192.168.10.0;
protocol device { scan time 30; }
protocol direct { ipv4; ipv6; check link yes; }
protocol kernel kernel4 {
ipv4 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
protocol kernel kernel6 {
ipv6 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
include "static.conf";
include "bfd.conf";
include "ospf.conf";
include "ibgp.conf";

View File

@ -0,0 +1 @@
# NOTE(ipng): Not created yet

View File

@ -0,0 +1,21 @@
protocol ospf v2 ospf4 {
ipv4 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}
protocol ospf v3 ospf6 {
ipv6 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}

View File

@ -0,0 +1,11 @@
protocol static static4 {
ipv4 { export all; };
# route 192.0.2.0/24 via 10.0.0.1;
route 192.168.10.0/24 unreachable;
}
protocol static static6 {
ipv6 { export all; };
# route 2001:db8:cafe::/48 via 2001:db8::1;;
route 2001:678:d78:200::/60 unreachable;
}

View File

@ -0,0 +1 @@
vpp0-0

View File

@ -0,0 +1,7 @@
127.0.0.1 localhost
127.0.1.1 vpp0-0.lab.ipng.ch vpp0-0
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

View File

@ -0,0 +1,14 @@
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
optional: true
accept-ra: false
dhcp4: false
addresses: [ 192.168.1.80/24, 2001:678:d78:101::80/64 ]
gateway4: 192.168.1.252
gateway6: 2001:678:d78:101::1
nameservers:
addresses: [ "2001:678:d78::3", "2001:678:d78::4" ]
search: [ "lab.ipng.ch", "ipng.ch", "ipng.nl", "rfc1918.ipng.nl" ]

View File

@ -0,0 +1,38 @@
set logging class linux-cp rate-limit 1000 level warn syslog-level notice
lcp default netns dataplane
lcp lcp-sync on
lcp lcp-auto-subint off
comment { Create a loopback interface }
create loopback interface instance 0
lcp create loop0 host-if loop0
set interface state loop0 up
set interface ip address loop0 192.168.10.0/32
set interface ip address loop0 2001:678:d78:200::/128
comment { Create one LinuxCP Interface Pair for each phy }
lcp create GigabitEthernet10/0/0 host-if e0
lcp create GigabitEthernet10/0/1 host-if e1
lcp create GigabitEthernet10/0/2 host-if e2
lcp create GigabitEthernet10/0/3 host-if e3
comment { e0 is uplink to AS8298 }
set interface state GigabitEthernet10/0/0 up
set interface mtu packet 1500 GigabitEthernet10/0/0
set interface ip address GigabitEthernet10/0/0 192.168.10.7/31
set interface ip address GigabitEthernet10/0/0 2001:678:d78:201::00:00/112
comment { e1 is ptp with e0.vpp0-1 }
set interface state GigabitEthernet10/0/1 up
set interface mtu packet 9000 GigabitEthernet10/0/1
set interface ip address GigabitEthernet10/0/1 192.168.10.8/31
set interface ip address GigabitEthernet10/0/1 2001:678:d78:201::01:00/112
comment { e2 is free to use }
set interface state GigabitEthernet10/0/2 down
set interface mtu packet 9000 GigabitEthernet10/0/2
comment { e3 is free to use }
set interface state GigabitEthernet10/0/3 down
set interface mtu packet 9000 GigabitEthernet10/0/3

View File

@ -0,0 +1,6 @@
protocol bfd bfd1 {
interface "e*" {
interval 100 ms;
multiplier 20;
};
}

View File

@ -0,0 +1,19 @@
router id 192.168.10.1;
protocol device { scan time 30; }
protocol direct { ipv4; ipv6; check link yes; }
protocol kernel kernel4 {
ipv4 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
protocol kernel kernel6 {
ipv6 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
include "static.conf";
include "bfd.conf";
include "ospf.conf";
include "ibgp.conf";

View File

@ -0,0 +1 @@
# NOTE(ipng): Not created yet

View File

@ -0,0 +1,21 @@
protocol ospf v2 ospf4 {
ipv4 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}
protocol ospf v3 ospf6 {
ipv6 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}

View File

@ -0,0 +1,11 @@
protocol static static4 {
ipv4 { export all; };
# route 192.0.2.0/24 via 10.0.0.1;
route 192.168.10.0/24 unreachable;
}
protocol static static6 {
ipv6 { export all; };
# route 2001:db8:cafe::/48 via 2001:db8::1;;
route 2001:678:d78:200::/60 unreachable;
}

View File

@ -0,0 +1 @@
vpp0-1

View File

@ -0,0 +1,7 @@
127.0.0.1 localhost
127.0.1.1 vpp0-1.lab.ipng.ch vpp0-1
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

View File

@ -0,0 +1,14 @@
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
optional: true
accept-ra: false
dhcp4: false
addresses: [ 192.168.1.81/24, 2001:678:d78:101::81/64 ]
gateway4: 192.168.1.252
gateway6: 2001:678:d78:101::1
nameservers:
addresses: [ "2001:678:d78::3", "2001:678:d78::4" ]
search: [ "lab.ipng.ch", "ipng.ch", "ipng.nl", "rfc1918.ipng.nl" ]

View File

@ -0,0 +1,38 @@
set logging class linux-cp rate-limit 1000 level warn syslog-level notice
lcp default netns dataplane
lcp lcp-sync on
lcp lcp-auto-subint off
comment { Create a loopback interface }
create loopback interface instance 0
lcp create loop0 host-if loop0
set interface state loop0 up
set interface ip address loop0 192.168.10.1/32
set interface ip address loop0 2001:678:d78:200::1/128
comment { Create one LinuxCP Interface Pair for each phy }
lcp create GigabitEthernet10/0/0 host-if e0
lcp create GigabitEthernet10/0/1 host-if e1
lcp create GigabitEthernet10/0/2 host-if e2
lcp create GigabitEthernet10/0/3 host-if e3
comment { e0 is uplink to AS8298 }
set interface state GigabitEthernet10/0/0 up
set interface mtu packet 1500 GigabitEthernet10/0/0
set interface ip address GigabitEthernet10/0/0 192.168.10.7/31
set interface ip address GigabitEthernet10/0/0 2001:678:d78:201::00:00/112
comment { e1 is ptp with e0.vpp0-1 }
set interface state GigabitEthernet10/0/1 up
set interface mtu packet 9000 GigabitEthernet10/0/1
set interface ip address GigabitEthernet10/0/1 192.168.10.8/31
set interface ip address GigabitEthernet10/0/1 2001:678:d78:201::01:00/112
comment { e2 is free to use }
set interface state GigabitEthernet10/0/2 down
set interface mtu packet 9000 GigabitEthernet10/0/2
comment { e3 is free to use }
set interface state GigabitEthernet10/0/3 down
set interface mtu packet 9000 GigabitEthernet10/0/3

View File

@ -0,0 +1,6 @@
protocol bfd bfd1 {
interface "e*" {
interval 100 ms;
multiplier 20;
};
}

View File

@ -0,0 +1,19 @@
router id 192.168.10.2;
protocol device { scan time 30; }
protocol direct { ipv4; ipv6; check link yes; }
protocol kernel kernel4 {
ipv4 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
protocol kernel kernel6 {
ipv6 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
include "static.conf";
include "bfd.conf";
include "ospf.conf";
include "ibgp.conf";

View File

@ -0,0 +1 @@
# NOTE(ipng): Not created yet

View File

@ -0,0 +1,21 @@
protocol ospf v2 ospf4 {
ipv4 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}
protocol ospf v3 ospf6 {
ipv6 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}

View File

@ -0,0 +1,11 @@
protocol static static4 {
ipv4 { export all; };
# route 192.0.2.0/24 via 10.0.0.1;
route 192.168.10.0/24 unreachable;
}
protocol static static6 {
ipv6 { export all; };
# route 2001:db8:cafe::/48 via 2001:db8::1;;
route 2001:678:d78:200::/60 unreachable;
}

View File

@ -0,0 +1 @@
vpp0-2

View File

@ -0,0 +1,7 @@
127.0.0.1 localhost
127.0.1.1 vpp0-2.lab.ipng.ch vpp0-2
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

View File

@ -0,0 +1,14 @@
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
optional: true
accept-ra: false
dhcp4: false
addresses: [ 192.168.1.82/24, 2001:678:d78:101::82/64 ]
gateway4: 192.168.1.252
gateway6: 2001:678:d78:101::1
nameservers:
addresses: [ "2001:678:d78::3", "2001:678:d78::4" ]
search: [ "lab.ipng.ch", "ipng.ch", "ipng.nl", "rfc1918.ipng.nl" ]

View File

@ -0,0 +1,38 @@
set logging class linux-cp rate-limit 1000 level warn syslog-level notice
lcp default netns dataplane
lcp lcp-sync on
lcp lcp-auto-subint off
comment { Create a loopback interface }
create loopback interface instance 0
lcp create loop0 host-if loop0
set interface state loop0 up
set interface ip address loop0 192.168.10.2/32
set interface ip address loop0 2001:678:d78:200::2/128
comment { Create one LinuxCP Interface Pair for each phy }
lcp create GigabitEthernet10/0/0 host-if e0
lcp create GigabitEthernet10/0/1 host-if e1
lcp create GigabitEthernet10/0/2 host-if e2
lcp create GigabitEthernet10/0/3 host-if e3
comment { e0 is uplink to AS8298 }
set interface state GigabitEthernet10/0/0 up
set interface mtu packet 1500 GigabitEthernet10/0/0
set interface ip address GigabitEthernet10/0/0 192.168.10.7/31
set interface ip address GigabitEthernet10/0/0 2001:678:d78:201::00:00/112
comment { e1 is ptp with e0.vpp0-1 }
set interface state GigabitEthernet10/0/1 up
set interface mtu packet 9000 GigabitEthernet10/0/1
set interface ip address GigabitEthernet10/0/1 192.168.10.8/31
set interface ip address GigabitEthernet10/0/1 2001:678:d78:201::01:00/112
comment { e2 is free to use }
set interface state GigabitEthernet10/0/2 down
set interface mtu packet 9000 GigabitEthernet10/0/2
comment { e3 is free to use }
set interface state GigabitEthernet10/0/3 down
set interface mtu packet 9000 GigabitEthernet10/0/3

View File

@ -0,0 +1,6 @@
protocol bfd bfd1 {
interface "e*" {
interval 100 ms;
multiplier 20;
};
}

View File

@ -0,0 +1,19 @@
router id 192.168.10.3;
protocol device { scan time 30; }
protocol direct { ipv4; ipv6; check link yes; }
protocol kernel kernel4 {
ipv4 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
protocol kernel kernel6 {
ipv6 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
include "static.conf";
include "bfd.conf";
include "ospf.conf";
include "ibgp.conf";

View File

@ -0,0 +1 @@
# NOTE(ipng): Not created yet

View File

@ -0,0 +1,21 @@
protocol ospf v2 ospf4 {
ipv4 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}
protocol ospf v3 ospf6 {
ipv6 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}

View File

@ -0,0 +1,11 @@
protocol static static4 {
ipv4 { export all; };
# route 192.0.2.0/24 via 10.0.0.1;
route 192.168.10.0/24 unreachable;
}
protocol static static6 {
ipv6 { export all; };
# route 2001:db8:cafe::/48 via 2001:db8::1;;
route 2001:678:d78:200::/60 unreachable;
}

View File

@ -0,0 +1 @@
vpp0-3

View File

@ -0,0 +1,7 @@
127.0.0.1 localhost
127.0.1.1 vpp0-3.lab.ipng.ch vpp0-3
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

View File

@ -0,0 +1,14 @@
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
optional: true
accept-ra: false
dhcp4: false
addresses: [ 192.168.1.83/24, 2001:678:d78:101::83/64 ]
gateway4: 192.168.1.252
gateway6: 2001:678:d78:101::1
nameservers:
addresses: [ "2001:678:d78::3", "2001:678:d78::4" ]
search: [ "lab.ipng.ch", "ipng.ch", "ipng.nl", "rfc1918.ipng.nl" ]

View File

@ -0,0 +1,38 @@
set logging class linux-cp rate-limit 1000 level warn syslog-level notice
lcp default netns dataplane
lcp lcp-sync on
lcp lcp-auto-subint off
comment { Create a loopback interface }
create loopback interface instance 0
lcp create loop0 host-if loop0
set interface state loop0 up
set interface ip address loop0 192.168.10.3/32
set interface ip address loop0 2001:678:d78:200::3/128
comment { Create one LinuxCP Interface Pair for each phy }
lcp create GigabitEthernet10/0/0 host-if e0
lcp create GigabitEthernet10/0/1 host-if e1
lcp create GigabitEthernet10/0/2 host-if e2
lcp create GigabitEthernet10/0/3 host-if e3
comment { e0 is uplink to AS8298 }
set interface state GigabitEthernet10/0/0 up
set interface mtu packet 1500 GigabitEthernet10/0/0
set interface ip address GigabitEthernet10/0/0 192.168.10.7/31
set interface ip address GigabitEthernet10/0/0 2001:678:d78:201::00:00/112
comment { e1 is ptp with e0.vpp0-1 }
set interface state GigabitEthernet10/0/1 up
set interface mtu packet 9000 GigabitEthernet10/0/1
set interface ip address GigabitEthernet10/0/1 192.168.10.8/31
set interface ip address GigabitEthernet10/0/1 2001:678:d78:201::01:00/112
comment { e2 is free to use }
set interface state GigabitEthernet10/0/2 down
set interface mtu packet 9000 GigabitEthernet10/0/2
comment { e3 is free to use }
set interface state GigabitEthernet10/0/3 down
set interface mtu packet 9000 GigabitEthernet10/0/3

View File

@ -0,0 +1,21 @@
pubkeys:
root:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8X6oRdLn7PckWIRL+Fgp46qN+fglQLBJIvPHJ2P277v4tx/qlELaT8w45YyEPrUZ4XbbNIB4P59H63wPxIpk/d15k0C7Zx3kTESaEQuts3fne3ZFmrWm0dLD2yDTiB0zCraiQ5a0w++xuGEC3wdWPV+FHZh5Ea+WCd91g2xXPHJeosAQzBBBBaC9Shhx91h6lbCm4evvgqLnwt7JgnI2N4w2qr13lDDaRD4BXfyFrtLSTdhBgYEaFnUd6Afz5ilfDYXQW/yTSHZOIQ/vNVFpFxYrtmwHDdrSMiDpz0FE/4LLBG/rFl2VvRTmTEyjvwpGpEVaivMOLo/jRc3TA7jKB pim@ipng.nl"
- "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKDP/hLZusPNfKTy3t9bbbOHyczX+UACc4rYstc3QEDBDfxBnCZcMKN5Mv10o+q/+ap7wyFhONlz/qcUhEMbI1k="
ipng:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8X6oRdLn7PckWIRL+Fgp46qN+fglQLBJIvPHJ2P277v4tx/qlELaT8w45YyEPrUZ4XbbNIB4P59H63wPxIpk/d15k0C7Zx3kTESaEQuts3fne3ZFmrWm0dLD2yDTiB0zCraiQ5a0w++xuGEC3wdWPV+FHZh5Ea+WCd91g2xXPHJeosAQzBBBBaC9Shhx91h6lbCm4evvgqLnwt7JgnI2N4w2qr13lDDaRD4BXfyFrtLSTdhBgYEaFnUd6Afz5ilfDYXQW/yTSHZOIQ/vNVFpFxYrtmwHDdrSMiDpz0FE/4LLBG/rFl2VvRTmTEyjvwpGpEVaivMOLo/jRc3TA7jKB pim@ipng.nl"
- "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKDP/hLZusPNfKTy3t9bbbOHyczX+UACc4rYstc3QEDBDfxBnCZcMKN5Mv10o+q/+ap7wyFhONlz/qcUhEMbI1k="
overlays:
default:
path: overlays/bird/
build: build/default/
bird:
path: overlays/bird/
build: build/bird/
frr:
path: overlays/frr/
build: build/frr/

View File

@ -0,0 +1,22 @@
lab:
id: 0
mgmt:
ipv4: 192.168.1.80/24
ipv6: 2001:678:d78:101::80/64
gw4: 192.168.1.252
gw6: 2001:678:d78:101::1
ipv4: 192.168.10.0/24
ipv6: 2001:678:d78:200::/60
hypervisor: hvn0.lab.ipng.ch
nodes: 4
## for i in lab.nodes; do
# node:
# hostname: "vpp" + lab.id + "-" + i
# id: i
# mgmt:
# ipv4: lab.mgmt.ipv4 + node.id
# ipv6: lab.mgmt.ipv6 + node.id
# loopback:
# ipv4: lab.ipv4 + node.id + "/32"
# ipv6: lab.ipv6 + node.id + "/128"

270
generate Executable file
View File

@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""
Labs for IPng Networks
(c) 2022- Pim van Pelt <pim@ipng.nl>
"""
from jinja2 import Environment, FileSystemLoader
from jinja2_ansible_filters import AnsibleCoreFiltersExtension
import hiyapyco
import traceback
import os
import sys
import pprint
import logging
import ipaddress
import re
try:
import argparse
except ImportError:
print("ERROR: install argparse manually")
print("HINT: sudo pip install argparse")
sys.exit(2)
log = logging.getLogger("generate")
log.setLevel(logging.INFO)
formatter = logging.Formatter(
"[%(levelname)-8s] %(name)17s - %(funcName)-15s: %(message)s"
)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
log.addHandler(ch)
def toyaml(d, indent=0, result=""):
for key, value in d.items():
result += " " * indent + str(key) + ": "
if isinstance(value, dict):
result = toyaml(value, indent + 2, result + "\n")
else:
if isinstance(value, str) and [
e for e in [" ", ":", "{", "}", "[", "]", "#"] if e in value
]:
result += "'" + str(value) + "'\n"
else:
result += str(value) + "\n"
return result
def render(tpl_path, data, trim=True):
path, filename = os.path.split(tpl_path)
env = Environment(
loader=FileSystemLoader(path or "./"), extensions=[AnsibleCoreFiltersExtension]
)
env.trim_blocks = trim
env.lstrip_blocks = trim
env.rstrip_blocks = trim
env.filters["toyaml"] = toyaml
return env.get_template(filename).render(data)
def tpl2fn(tpl, prefix):
fn = tpl[len(prefix) :]
if fn.endswith(".j2"):
fn = fn[:-3]
return fn
def find(file_or_dir_list):
log.info("Finding templates in %s" % file_or_dir_list)
ret = {}
for e in file_or_dir_list:
if e.startswith("_"):
continue
if os.path.isfile(e):
ret[e] = tpl2fn(e, e)
elif os.path.isdir(e):
for root, dirnames, filenames in os.walk(e):
for filename in filenames:
if filename.startswith("_"):
continue
tpl = os.path.join(root, filename)
ret[tpl] = tpl2fn(tpl, e)
log.debug("Templates: %s" % ret)
return ret
def generate(templates, data, debug=False):
output = {}
for tpl, fn in templates.items():
log.info("Rendering %s into %s" % (tpl, fn))
try:
output[fn] = render(tpl, data)
except:
log.error("Could not render %s!" % tpl)
if debug:
traceback.print_exc(file=sys.stderr)
return None
return output
def emit(output, outdir):
log.debug("Emitting to %s" % outdir)
for fn, contents in output.items():
if outdir == "-":
log.info("Emitting %s" % fn)
print(contents)
continue
outfile = os.path.join(outdir, fn)
log.info("Emitting %s into %s" % (fn, outfile))
basedir = os.path.dirname(outfile)
os.makedirs(basedir, exist_ok=True)
f = open(outfile, "w")
f.write(contents)
f.close()
def prune(output, outdir):
if outdir == "-":
log.info("Skipping pruning, output is stdout")
return True
for root, dirnames, filenames in os.walk(outdir):
for filename in filenames:
fn = os.path.join(root, filename) # build/frggh0.ipng.ch/bird/bird.conf
rel_fn = fn.replace(outdir, "") # /bird/bird.conf
if rel_fn[0] == "/":
rel_fn = rel_fn[1:] # bird/bird.conf
if not rel_fn in output:
log.info("Pruning file %s (%s)" % (rel_fn, fn))
os.remove(fn)
for root, dirnames, filenames in os.walk(outdir):
for dirname in dirnames:
dn = os.path.join(root, dirname) # build/frggh0.ipng.ch/bird/empty
if not os.listdir(dn):
log.info("Pruning dir %s" % (dn))
os.rmdir(dn)
def create_node(lab, node_id):
v4_base, v4_plen = lab["mgmt"]["ipv4"].split("/")
v6_base, v6_plen = lab["mgmt"]["ipv6"].split("/")
lo4_base = lab["ipv4"].split("/")[0]
lo6_base = lab["ipv6"].split("/")[0]
ret = {
"hostname": "vpp%d-%d" % (lab["id"], node_id),
"id": node_id,
"mgmt": {
"ipv4": "%s/%s"
% (
ipaddress.IPv4Address(v4_base) + lab["nodes"] * lab["id"] + node_id,
v4_plen,
),
"ipv6": "%s/%s"
% (
ipaddress.IPv6Address(v6_base) + lab["nodes"] * lab["id"] + node_id,
v6_plen,
),
"gw4": lab["mgmt"]["gw4"],
"gw6": lab["mgmt"]["gw6"],
},
"loopback": {
"ipv4": "%s/32" % (ipaddress.IPv4Address(lo4_base) + node_id),
"ipv6": "%s/128" % (ipaddress.IPv6Address(lo6_base) + node_id),
},
}
return ret
def main():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"-d", dest="debug", action="store_true", help="""Enable debug"""
)
parser.add_argument(
"-q", dest="quiet", action="store_true", help="""Quiet output"""
)
parser.add_argument("--host", dest="hostname", help="""Hostname to configure for""")
parser.add_argument(
"--yaml",
dest="yamldata",
default=["config/common/generic.yaml"],
type=str,
nargs="*",
help="""Location of YAML data file(s)""",
)
parser.add_argument(
"--overlay",
dest="overlay",
default="default",
type=str,
help="""Type of lab setup (defined in config/common/generic.yaml 'overlays' dictionary, defaults to 'default')""",
)
parser.add_argument(
"-o",
dest="output",
type=str,
default=None,
help="Output directory (default: overlay.build)",
)
args = parser.parse_args()
if args.debug and args.quiet:
parser.print_help()
return
if not args.hostname:
parser.print_help()
return
if args.quiet:
log.setLevel(logging.ERROR)
elif args.debug:
log.setLevel(logging.DEBUG)
yamldata = "config/%s.yaml" % args.hostname
if not os.path.exists(yamldata):
log.error("Can't read config file %s" % yamldata)
return
log.info("Generating host %s" % (args.hostname))
# Assemble the YAML dictionary
yamldata = args.yamldata + [yamldata]
log.debug("YAML data: %s" % yamldata)
data = hiyapyco.load(*yamldata, method=hiyapyco.METHOD_MERGE, interpolate=True)
if args.debug:
log.debug("YAML merged configuration")
print(hiyapyco.dump(data, default_flow_style=False))
if not args.overlay in data["overlays"]:
log.error("Overlay not defined, bailing.")
return
# Assemble a dictionary of tpl=>fn
overlay = data["overlays"][args.overlay]
template_root = overlay["path"] + "templates/"
templates = find([template_root])
for node_id in range(data["lab"]["nodes"]):
log.info("Generating for node %d" % node_id)
data["node"] = create_node(data["lab"], node_id)
log.debug("node: %s" % data["node"])
# Assemble a dictionary of fn=>output
build = generate(templates, data, args.debug)
if not build:
return
# Emit the output (fn=>output)
if not args.output and "build" in overlay:
output = overlay["build"] + data["node"]["hostname"]
else:
output = "-"
emit(build, output)
# Remove all files/dirs not in (fn=>output)
# prune(output, args.output)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,6 @@
protocol bfd bfd1 {
interface "e*" {
interval 100 ms;
multiplier 20;
};
}

View File

@ -0,0 +1,19 @@
router id {{ node.loopback.ipv4.split("/")[0] }};
protocol device { scan time 30; }
protocol direct { ipv4; ipv6; check link yes; }
protocol kernel kernel4 {
ipv4 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
protocol kernel kernel6 {
ipv6 { import none; export where source != RTS_DEVICE; };
learn off;
scan time 300;
}
include "static.conf";
include "bfd.conf";
include "ospf.conf";
include "ibgp.conf";

View File

@ -0,0 +1 @@
# NOTE(ipng): Not created yet

View File

@ -0,0 +1,21 @@
protocol ospf v2 ospf4 {
ipv4 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}
protocol ospf v3 ospf6 {
ipv6 { export where source = RTS_DEVICE; import all; };
area 0 {
interface "loop0" { stub yes; };
interface "e0" { type pointopoint; cost 5; bfd off; };
interface "e1" { type pointopoint; cost 5; bfd off; };
interface "e2" { type pointopoint; cost 5; bfd off; };
interface "e3" { type pointopoint; cost 5; bfd off; };
};
}

View File

@ -0,0 +1,11 @@
protocol static static4 {
ipv4 { export all; };
# route 192.0.2.0/24 via 10.0.0.1;
route {{lab.ipv4}} unreachable;
}
protocol static static6 {
ipv6 { export all; };
# route 2001:db8:cafe::/48 via 2001:db8::1;;
route {{lab.ipv6}} unreachable;
}

View File

@ -0,0 +1 @@
{{ node.hostname }}

View File

@ -0,0 +1,7 @@
127.0.0.1 localhost
127.0.1.1 {{node.hostname}}.lab.ipng.ch {{node.hostname}}
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

View File

@ -0,0 +1,14 @@
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
optional: true
accept-ra: false
dhcp4: false
addresses: [ {{node.mgmt.ipv4}}, {{node.mgmt.ipv6}} ]
gateway4: {{lab.mgmt.gw4}}
gateway6: {{lab.mgmt.gw6}}
nameservers:
addresses: [ "2001:678:d78::3", "2001:678:d78::4" ]
search: [ "lab.ipng.ch", "ipng.ch", "ipng.nl", "rfc1918.ipng.nl" ]

View File

@ -0,0 +1,38 @@
set logging class linux-cp rate-limit 1000 level warn syslog-level notice
lcp default netns dataplane
lcp lcp-sync on
lcp lcp-auto-subint off
comment { Create a loopback interface }
create loopback interface instance 0
lcp create loop0 host-if loop0
set interface state loop0 up
set interface ip address loop0 {{ node.loopback.ipv4 }}
set interface ip address loop0 {{ node.loopback.ipv6 }}
comment { Create one LinuxCP Interface Pair for each phy }
lcp create GigabitEthernet10/0/0 host-if e0
lcp create GigabitEthernet10/0/1 host-if e1
lcp create GigabitEthernet10/0/2 host-if e2
lcp create GigabitEthernet10/0/3 host-if e3
comment { e0 is uplink to AS8298 }
set interface state GigabitEthernet10/0/0 up
set interface mtu packet 1500 GigabitEthernet10/0/0
set interface ip address GigabitEthernet10/0/0 192.168.10.7/31
set interface ip address GigabitEthernet10/0/0 2001:678:d78:201::00:00/112
comment { e1 is ptp with e0.vpp0-1 }
set interface state GigabitEthernet10/0/1 up
set interface mtu packet 9000 GigabitEthernet10/0/1
set interface ip address GigabitEthernet10/0/1 192.168.10.8/31
set interface ip address GigabitEthernet10/0/1 2001:678:d78:201::01:00/112
comment { e2 is free to use }
set interface state GigabitEthernet10/0/2 down
set interface mtu packet 9000 GigabitEthernet10/0/2
comment { e3 is free to use }
set interface state GigabitEthernet10/0/3 down
set interface mtu packet 9000 GigabitEthernet10/0/3