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

Open
wants to merge 30 commits into
base: scrapli-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
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 .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.0.1

ARG VERSION
ENV VERSION=${VERSION}
Expand Down
261 changes: 125 additions & 136 deletions c8000v/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import vrnetlab

STARTUP_CONFIG_FILE = "/config/startup-config.cfg"

DEFAULT_SCRAPLI_TIMEOUT = 900

def handle_SIGCHLD(signal, frame):
os.waitpid(-1, os.WNOHANG)
Expand Down Expand Up @@ -52,38 +52,114 @@ 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(f"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 @@ -92,8 +168,9 @@ def create_boot_image(self):
"/" + self.image_name,
"/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 @@ -103,129 +180,41 @@ def bootstrap_spin(self):
self.stop()
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):
super(C8000v, self).__init__(username, password)
Expand All @@ -240,17 +229,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