Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Scrapli 'core' and platform migration #297

Merged
merged 50 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
fef3129
AOS-CX: Update CPU and MEM values for newer versions (#294)
ssasso Dec 16, 2024
e11a7bf
backdoor to reset VR or specific VMs (#285)
jcpvdm Dec 16, 2024
a960199
give ocnos some time to boot in the login routine (#295)
hellt Dec 16, 2024
5665736
Add vrnetlab base image
kaelemc Dec 15, 2024
9984c66
Add `cidfile` to gitignore
kaelemc Dec 18, 2024
f420953
Implement Scrapli
kaelemc Dec 18, 2024
92a154c
cat8kv: Migrate to Scrapli
kaelemc Dec 18, 2024
29b1d64
cat9kv: Migrate to Scrapli
kaelemc Dec 18, 2024
9438bcc
csr1kv: Migrate to Scrapli
kaelemc Dec 18, 2024
a011fa6
xrv: Migrate to Scrapli
kaelemc Dec 18, 2024
2958e52
xrv: Add convert-image target in Makefile
kaelemc Dec 18, 2024
d8ca50d
xrv9k: Migrate to Scrapli
kaelemc Dec 18, 2024
8bdfba3
xrv: Remove env var printing
kaelemc Dec 19, 2024
eb427d3
n9kv: Migrate to Scrapli
kaelemc Dec 19, 2024
d95f462
nxos: Migrate to Scrapli
kaelemc Dec 19, 2024
17df8f5
vios: Migrate to Scrapli
kaelemc Dec 19, 2024
a7e076c
vrnetlab: Support scrapli qemu monitor option for VM reset
kaelemc Dec 19, 2024
b246f4f
vrnetlab: Move logging colour config outside of class init method
kaelemc Dec 19, 2024
be0db66
cat8kv: Fix logger warning (log.warning -> logger.warning)
kaelemc Dec 19, 2024
1a725d5
vrnetlab: Remove scrpali logging import
kaelemc Dec 19, 2024
1f37955
Cisco devices: Add/tweak configuration saving:
kaelemc Dec 20, 2024
4915f93
xrv, xrv9k: Return to root at end of bootstrap cfg
kaelemc Dec 20, 2024
b4a99f5
vrnetlab: add bool formatter func
kaelemc Dec 20, 2024
71de18b
sros: Migrate to Scrapli
kaelemc Dec 20, 2024
4812442
Use kaelemc/scrapli_community in base image
kaelemc Dec 20, 2024
3b83a7e
Disable eager mode for config saving on Cisco devices
kaelemc Dec 21, 2024
9340d3f
cat8kv: Migrate to CVAC configuration
kaelemc Dec 23, 2024
c42eed2
cat8kv: add log message and block while generating cfg ISO
kaelemc Dec 23, 2024
acbf0ab
cat9kv: Migrate startup config to CVAC
kaelemc Dec 23, 2024
157b8a0
Remove erroneous Scrapli Community submodule
kaelemc Dec 24, 2024
31263c5
sros: fix typos for classic CLI
kaelemc Jan 4, 2025
fdbe6bf
csr: Migrate to CVAC
kaelemc Jan 5, 2025
532a269
Switch back to scrapli/scrapli-community
kaelemc Jan 7, 2025
8d4608f
added uv lock/venv and env file for pylance resolve sequence (#303)
hellt Jan 9, 2025
8d1b4af
added uv dep for scrapli
hellt Jan 9, 2025
d97359a
use ruff formatting
hellt Jan 12, 2025
70c688d
update base image with pinned scrapli community
hellt Jan 12, 2025
7867c39
close sros driver connection to invoke on_exit commands (quit-config)
hellt Jan 12, 2025
f49d21f
added local deps
hellt Jan 12, 2025
fa2f50d
use uv in the base image
hellt Jan 12, 2025
d3d167e
ruff formatting
hellt Jan 12, 2025
dbe76ca
use single const for scrapli timeout
hellt Jan 12, 2025
3da1b40
Close the commandeered connection so the on close actions are run
kaelemc Jan 13, 2025
cffd0a8
Connection error log type from info->error
kaelemc Jan 13, 2025
1f80210
extracting image edit
hellt Jan 13, 2025
f42e138
persist bof and config after bootstrap config is applied and close sr…
hellt Jan 13, 2025
98f679d
Configure scrapli variant if startup config is classic
kaelemc Jan 14, 2025
c03c10f
Use a global var to determine when to send classic configs
kaelemc Jan 14, 2025
286b7b9
Use explicit `quit-config` and move persistBofAndConfig back to end o…
kaelemc Jan 15, 2025
5be1626
Don't enforce MD-CLI on versions older than 19.x
kaelemc Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PYTHONPATH=".:./common"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ cisco*.bin
*.xz
*.vmdk
*.iso
*cidfile

.DS_Store
*/.DS_Store
Expand Down
6 changes: 4 additions & 2 deletions aoscx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ docker tag vrnetlab/vr-aoscx:20210610000730 vrnetlab/vr-aoscx:10.07.0010
Tested booting and responding to SSH:

* `ArubaOS-CX_10_12_0006.ova` (`arubaoscx-disk-image-genericx86-p4-20230531220439.vmdk`)
* `ArubaOS-CX_10_13_0005.ova` (`arubaoscx-disk-image-genericx86-p4-20231110145644.vmdk`)
* `ArubaOS-CX_10_14_1000.ova` (`arubaoscx-disk-image-genericx86-p4-20240731173624.vmdk`)

## System requirements

CPU: 2 core
CPU: 4 core

RAM: 4GB
RAM: 8GB

Disk: <1GB
2 changes: 1 addition & 1 deletion aoscx/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def __init__(self, hostname, username, password, conn_mode):
logging.getLogger().info("Disk image was not found")
exit(1)
super(AOSCX_vm, self).__init__(
username, password, disk_image=disk_image, ram=4096, cpu="host,level=9", smp="2"
username, password, disk_image=disk_image, ram=8192, cpu="host,level=9", smp="4"
)
self.hostname = hostname
self.conn_mode = conn_mode
Expand Down
13 changes: 13 additions & 0 deletions build-base-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# this script builds the vrnetlab base container image
# that is used in the dockerfiles of the NOS images

set -e

if [ -z "$1" ]; then
echo "Usage: $0 <version>"
exit 1
fi

sudo docker build -t ghcr.io/srl-labs/vrnetlab-base:$1 \
-f vrnetlab-base.dockerfile .
18 changes: 1 addition & 17 deletions c8000v/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
FROM public.ecr.aws/docker/library/debian:bookworm-slim

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update -qy \
&& apt-get install -y \
bridge-utils \
iproute2 \
socat \
qemu-kvm \
tcpdump \
inetutils-ping \
ssh \
telnet \
procps \
genisoimage \
&& rm -rf /var/lib/apt/lists/*
FROM ghcr.io/srl-labs/vrnetlab-base:0.1.0

ARG VERSION
ENV VERSION=${VERSION}
Expand Down
256 changes: 124 additions & 132 deletions c8000v/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,38 +52,116 @@ def __init__(self, hostname, username, password, conn_mode, install_mode=False):
logger.info("License found")
self.license = True

super().__init__(username, password, disk_image=disk_image, ram=4096)
super().__init__(
username, password, disk_image=disk_image, ram=4096, use_scrapli=True
)
self.install_mode = install_mode
self.hostname = hostname
self.conn_mode = conn_mode
self.num_nics = 9
self.nic_type = "virtio-net-pci"
self.image_name = "config.iso"

if self.install_mode:
logger.trace("install mode")
self.image_name = "config.iso"
self.create_boot_image()

self.qemu_args.extend(["-cdrom", "/" + self.image_name])

def create_boot_image(self):
"""Creates a iso image with a bootstrap configuration"""

with open("/iosxe_config.txt", "w") as cfg_file:
if self.license:
cfg_file.write("do clock set 13:33:37 1 Jan 2010\r\n")
cfg_file.write("interface GigabitEthernet1\r\n")
cfg_file.write("ip address 10.0.0.15 255.255.255.0\r\n")
cfg_file.write("no shut\r\n")
cfg_file.write("exit\r\n")
cfg_file.write("license accept end user agreement\r\n")
cfg_file.write("yes\r\n")
cfg_file.write("do license install tftp://10.0.0.2/license.lic\r\n\r\n")
cfg_file.write("license boot level network-premier addon dna-premier\r\n")
cfg_file.write("platform console serial\r\n\r\n")
cfg_file.write("do clear platform software vnic-if nvtable\r\n")
cfg_file.write("do wr\r\n")
cfg_file.write("do reload\r\n")
self.logger.debug("Install mode")
self.create_config_image(self.gen_install_config())
else:
cfg = self.gen_bootstrap_config()
if os.path.exists(STARTUP_CONFIG_FILE):
self.logger.info("Startup configuration file found")
with open(STARTUP_CONFIG_FILE, "r") as startup_config:
cfg += startup_config.read()
else:
self.logger.warning("User provided startup configuration is not found.")
self.create_config_image(cfg)

self.qemu_args.extend(["-cdrom", "/" + self.image_name])

def gen_install_config(self) -> str:
"""
Returns the configuration to load in install mode
"""

config = ""

if self.license:
config += """do clock set 13:33:37 1 Jan 2010
interface GigabitEthernet1
ip address 10.0.0.15 255.255.255.0
no shut
exit
license accept end user agreement
yes
do license install tftp://10.0.0.2/license.lic
"""

config += """
license boot level network-premier addon dna-premier
platform console serial
do clear platform software vnic-if nvtable
do wr
do reload
"""

return config

def gen_bootstrap_config(self) -> str:
"""
Returns the system bootstrap configuration
"""

v4_mgmt_address = vrnetlab.cidr_to_ddn(self.mgmt_address_ipv4)

return f"""hostname {self.hostname}
username {self.username} privilege 15 password {self.password}
ip domain name example.com
!
crypto key generate rsa modulus 2048
!
line con 0
logging synchronous
!
line vty 0 4
logging synchronous
login local
transport input all
!
ipv6 unicast-routing
!
vrf definition clab-mgmt
description Containerlab management VRF (DO NOT DELETE)
address-family ipv4
exit
address-family ipv6
exit
exit
!
ip route vrf clab-mgmt 0.0.0.0 0.0.0.0 {self.mgmt_gw_ipv4}
ipv6 route vrf clab-mgmt ::/0 {self.mgmt_gw_ipv6}
!
interface GigabitEthernet 1
description Containerlab management interface
vrf forwarding clab-mgmt
ip address {v4_mgmt_address[0]} {v4_mgmt_address[1]}
ipv6 address {self.mgmt_address_ipv6}
no shut
exit
!
restconf
netconf-yang
netconf max-sessions 16
netconf detailed-error
!
ip ssh server algorithm mac hmac-sha2-512
ip ssh maxstartups 128
!
"""

def create_config_image(self, config):
"""Creates a iso image with a installation configuration"""

with open("/iosxe_config.txt", "w") as cfg:
cfg.write(config)

genisoimage_args = [
"genisoimage",
Expand All @@ -93,7 +171,8 @@ def create_boot_image(self):
"/iosxe_config.txt",
]

subprocess.Popen(genisoimage_args)
self.logger.debug("Generating boot ISO")
subprocess.Popen(genisoimage_args).wait()

def bootstrap_spin(self):
"""This function should be called periodically to do work."""
Expand All @@ -104,127 +183,40 @@ def bootstrap_spin(self):
self.start()
return

(ridx, match, res) = self.tn.expect(
[b"Press RETURN to get started!", b"IOSXEBOOT-4-FACTORY_RESET"], 1
(ridx, match, res) = self.con_expect(
[b"CVAC-4-CONFIG_DONE", b"IOSXEBOOT-4-FACTORY_RESET"]
)
if match: # got a match!
if ridx == 0: # login
self.logger.debug("matched, Press RETURN to get started.")
if self.install_mode:
self.logger.debug("Now we wait for the device to reload")
else:
self.wait_write("", wait=None)

# run main config!
self.bootstrap_config()
# add startup config if present
self.startup_config()
# close telnet connection
self.tn.close()
# startup time?
startup_time = datetime.datetime.now() - self.start_time
self.logger.info("Startup complete in: %s", startup_time)
# mark as running
self.running = True
return
if ridx == 0 and not self.install_mode: # configuration applied
self.logger.info("CVAC Configuration has been applied.")
# close telnet connection
self.scrapli_tn.close()
# startup time?
startup_time = datetime.datetime.now() - self.start_time
self.logger.info("Startup complete in: %s", startup_time)
# mark as running
self.running = True
return
elif ridx == 1: # IOSXEBOOT-4-FACTORY_RESET
if self.install_mode:
install_time = datetime.datetime.now() - self.start_time
self.logger.info("Install complete in: %s", install_time)
self.running = True
return
else:
self.log.warning("Unexpected reload while running")
self.logger.warning("Unexpected reload while running")

# no match, if we saw some output from the router it's probably
# booting, so let's give it some more time
if res != b"":
self.logger.trace("OUTPUT: %s", res.decode())
self.write_to_stdout(res)
# reset spins if we saw some output
self.spins = 0

self.spins += 1

return

def bootstrap_config(self):
"""Do the actual bootstrap config"""
self.logger.info("applying bootstrap configuration")

v4_mgmt_address = vrnetlab.cidr_to_ddn(self.mgmt_address_ipv4)

self.wait_write("", None)
self.wait_write("enable", wait=">")
self.wait_write("configure terminal", wait=">")

self.wait_write(f"hostname {self.hostname}")
self.wait_write(
"username %s privilege 15 password %s" % (self.username, self.password)
)
if int(self.version.split(".")[0]) >= 16:
self.wait_write("ip domain name example.com")
else:
self.wait_write("ip domain-name example.com")
self.wait_write("crypto key generate rsa modulus 2048")

self.wait_write("ipv6 unicast-routing")

self.wait_write("vrf definition clab-mgmt")
self.wait_write("description Containerlab management VRF (DO NOT DELETE)")
self.wait_write("address-family ipv4")
self.wait_write("exit")
self.wait_write("address-family ipv6")
self.wait_write("exit")
self.wait_write("exit")

self.wait_write(f"ip route vrf clab-mgmt 0.0.0.0 0.0.0.0 {self.mgmt_gw_ipv4}")
self.wait_write(f"ipv6 route vrf clab-mgmt ::/0 {self.mgmt_gw_ipv6}")

self.wait_write("interface GigabitEthernet1")
self.wait_write("vrf forwarding clab-mgmt")
self.wait_write(f"ip address {v4_mgmt_address[0]} {v4_mgmt_address[1]}")
self.wait_write(f"ipv6 address {self.mgmt_address_ipv6}")
self.wait_write("no shut")
self.wait_write("exit")
self.wait_write("restconf")
self.wait_write("netconf-yang")
self.wait_write("netconf max-sessions 16")
# I did not find any documentation about this, but is seems like a good idea!?
self.wait_write("netconf detailed-error")
self.wait_write("ip ssh server algorithm mac hmac-sha2-512")
self.wait_write("ip ssh maxstartups 128")

self.wait_write("line vty 0 4")
self.wait_write("login local")
self.wait_write("transport input all")
self.wait_write("end")
self.wait_write("copy running-config startup-config")
self.wait_write("\r", "Destination")

def startup_config(self):
"""Load additional config provided by user."""

if not os.path.exists(STARTUP_CONFIG_FILE):
self.logger.trace(f"Startup config file {STARTUP_CONFIG_FILE} is not found")
return

self.logger.trace(f"Startup config file {STARTUP_CONFIG_FILE} exists")
with open(STARTUP_CONFIG_FILE) as file:
config_lines = file.readlines()
config_lines = [line.rstrip() for line in config_lines]
self.logger.trace(f"Parsed startup config file {STARTUP_CONFIG_FILE}")

self.logger.info(f"Writing lines from {STARTUP_CONFIG_FILE}")

self.wait_write("configure terminal")
# Apply lines from file
for line in config_lines:
self.wait_write(line)
# End and Save
self.wait_write("end")
self.wait_write("copy running-config startup-config")
self.wait_write("\r", "Destination")


class C8000v(vrnetlab.VR):
def __init__(self, hostname, username, password, conn_mode):
Expand All @@ -240,17 +232,17 @@ class C8000v_installer(C8000v):
"""

def __init__(self, hostname, username, password, conn_mode):
super(C8000v_installer, self).__init__(hostname, username, password, conn_mode)
super(C8000v, self).__init__(username, password)
self.vms = [
C8000v_vm(hostname, username, password, conn_mode, install_mode=True)
]

def install(self):
self.logger.info("Installing C8000v")
csr = self.vms[0]
while not csr.running:
csr.work()
csr.stop()
cat8kv = self.vms[0]
while not cat8kv.running:
cat8kv.work()
cat8kv.stop()
self.logger.info("Installation complete")


Expand Down
Loading