Skip to content

Commit 1e1ec73

Browse files
tomncarterhellt
andauthored
Add vJunos-router support (#196)
* Initial vJunos-router support * Spelling correction * use ecr.aws --------- Co-authored-by: Roman Dodin <[email protected]>
1 parent d273133 commit 1e1ec73

File tree

7 files changed

+339
-0
lines changed

7 files changed

+339
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Since the changes we made in this fork are VM specific, we added a few popular r
4949
* Juniper vMX
5050
* Juniper vSRX
5151
* Juniper vJunos-switch
52+
* Juniper vJunos-router
5253
* Juniper vJunosEvolved
5354
* Nokia SR OS
5455
* OpenBSD

vjunosrouter/Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
VENDOR=Juniper
2+
NAME=vJunos-router
3+
IMAGE_FORMAT=qcow
4+
IMAGE_GLOB=*.qcow2
5+
6+
# match versions like:
7+
# vJunos-router-23.2R1.15.qcow2
8+
# ...
9+
VERSION=$(shell echo $(IMAGE) | sed -e 's/vJunos-router-//i' | sed -e 's/.qcow2//i')
10+
11+
-include ../makefile-sanity.include
12+
-include ../makefile.include

vjunosrouter/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# vrnetlab / Juniper vJunos-router
2+
3+
This is the vrnetlab docker image for Juniper's vJunos-router. This is built from the vJunos-switch template.
4+
5+
## Building the docker image
6+
7+
Download the vJunos-router .qcow2 image from <https://support.juniper.net/support/downloads/?p=vjunos-router>
8+
and place it in this directory. After typing `make`, a new image will appear called `vrnetlab/vjunosrouter`.
9+
Run `docker images` to confirm this.
10+
11+
## System requirements
12+
13+
CPU: 4 cores
14+
RAM: 5GB
15+
DISK: ~4.5GB

vjunosrouter/docker/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM public.ecr.aws/docker/library/debian:bookworm-slim
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
5+
RUN apt-get update -qy \
6+
&& apt-get install -y \
7+
dosfstools \
8+
bridge-utils \
9+
iproute2 \
10+
socat \
11+
ssh \
12+
qemu-kvm \
13+
inetutils-ping \
14+
dnsutils \
15+
telnet \
16+
&& rm -rf /var/lib/apt/lists/*
17+
18+
ARG IMAGE
19+
COPY $IMAGE* /
20+
21+
# copy conf file
22+
COPY init.conf /
23+
# copy config shell script
24+
COPY make-config.sh /
25+
# copy python scripts for launching VM
26+
COPY *.py /
27+
28+
EXPOSE 22 161/udp 830 5000 10000-10099 57400
29+
HEALTHCHECK CMD ["/healthcheck.py"]
30+
ENTRYPOINT ["/launch.py"]

vjunosrouter/docker/init.conf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
system {
2+
host-name {HOSTNAME};
3+
root-authentication {
4+
plain-text-password-value "admin@123";
5+
}
6+
login {
7+
user admin {
8+
class super-user;
9+
authentication {
10+
plain-text-password-value "admin@123";
11+
}
12+
}
13+
}
14+
services {
15+
ssh {
16+
root-login allow;
17+
}
18+
netconf {
19+
ssh;
20+
}
21+
}
22+
}
23+
interfaces {
24+
fxp0 {
25+
unit 0 {
26+
family inet {
27+
address 10.0.0.15/24;
28+
}
29+
}
30+
}
31+
}

vjunosrouter/docker/launch.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/usr/bin/env python3
2+
import datetime
3+
import logging
4+
import os
5+
import re
6+
import signal
7+
import subprocess
8+
import sys
9+
10+
import vrnetlab
11+
12+
# loadable startup config
13+
STARTUP_CONFIG_FILE = "/config/startup-config.cfg"
14+
15+
16+
def handle_SIGCHLD(signal, frame):
17+
os.waitpid(-1, os.WNOHANG)
18+
19+
20+
def handle_SIGTERM(signal, frame):
21+
sys.exit(0)
22+
23+
24+
signal.signal(signal.SIGINT, handle_SIGTERM)
25+
signal.signal(signal.SIGTERM, handle_SIGTERM)
26+
signal.signal(signal.SIGCHLD, handle_SIGCHLD)
27+
28+
TRACE_LEVEL_NUM = 9
29+
logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
30+
31+
32+
def trace(self, message, *args, **kws):
33+
# Yes, logger takes its '*args' as 'args'.
34+
if self.isEnabledFor(TRACE_LEVEL_NUM):
35+
self._log(TRACE_LEVEL_NUM, message, args, **kws)
36+
37+
38+
logging.Logger.trace = trace
39+
40+
41+
class VJUNOSROUTER_vm(vrnetlab.VM):
42+
def __init__(self, hostname, username, password, conn_mode):
43+
for e in os.listdir("/"):
44+
if re.search(".qcow2$", e):
45+
disk_image = "/" + e
46+
super(VJUNOSROUTER_vm, self).__init__(
47+
username, password, disk_image=disk_image, ram=5120
48+
)
49+
# device hostname
50+
self.hostname = hostname
51+
52+
# read init.conf configuration file to replace hostname placehodler
53+
# with given hostname
54+
with open("init.conf", "r") as file:
55+
cfg = file.read()
56+
57+
# replace HOSTNAME file var with nodes given hostname
58+
new_cfg = cfg.replace("{HOSTNAME}", hostname)
59+
60+
# write changes to init.conf file
61+
with open("init.conf", "w") as file:
62+
file.write(new_cfg)
63+
64+
# pass in user startup config
65+
self.startup_config()
66+
67+
# these QEMU cmd line args are translated from the shipped libvirt XML file
68+
self.qemu_args.extend(["-smp", "4,sockets=1,cores=4,threads=1"])
69+
# Additional CPU info
70+
self.qemu_args.extend(
71+
[
72+
"-cpu",
73+
"IvyBridge,vme=on,ss=on,vmx=on,f16c=on,rdrand=on,hypervisor=on,arat=on,tsc-adjust=on,umip=on,arch-capabilities=on,pdpe1gb=on,skip-l1dfl-vmentry=on,pschange-mc-no=on,bmi1=off,avx2=off,bmi2=off,erms=off,invpcid=off,rdseed=off,adx=off,smap=off,xsaveopt=off,abm=off,svm=on",
74+
]
75+
)
76+
# mount config disk with juniper.conf base configs
77+
self.qemu_args.extend(
78+
[
79+
"-drive",
80+
"if=none,id=config_disk,file=/config.img,format=raw",
81+
"-device",
82+
"virtio-blk-pci,drive=config_disk",
83+
]
84+
)
85+
self.qemu_args.extend(["-overcommit", "mem-lock=off"])
86+
self.qemu_args.extend(
87+
["-display", "none", "-no-user-config", "-nodefaults", "-boot", "strict=on"]
88+
)
89+
self.nic_type = "virtio-net-pci"
90+
self.num_nics = 11
91+
self.smbios = ["type=1,product=VM-VMX,family=lab"]
92+
self.qemu_args.extend(["-machine", "pc,usb=off,dump-guest-core=off,accel=kvm"])
93+
self.qemu_args.extend(
94+
["-device", "piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2"]
95+
)
96+
self.conn_mode = conn_mode
97+
98+
def startup_config(self):
99+
"""Load additional config provided by user and append initial
100+
configurations set by vrnetlab."""
101+
# if startup cfg DNE
102+
if not os.path.exists(STARTUP_CONFIG_FILE):
103+
self.logger.trace(f"Startup config file {STARTUP_CONFIG_FILE} is not found")
104+
# rename init.conf to juniper.conf, this is our startup config
105+
os.rename("init.conf", "juniper.conf")
106+
107+
# if startup cfg file is found
108+
else:
109+
self.logger.trace(
110+
f"Startup config file {STARTUP_CONFIG_FILE} found, appending initial configuration"
111+
)
112+
# append startup cfg to inital configuration
113+
append_cfg = f"cat init.conf {STARTUP_CONFIG_FILE} >> juniper.conf"
114+
subprocess.run(append_cfg, shell=True)
115+
116+
# generate mountable config disk based on juniper.conf file with base vrnetlab configs
117+
subprocess.run(["./make-config.sh", "juniper.conf", "config.img"], check=True)
118+
119+
def bootstrap_spin(self):
120+
"""This function should be called periodically to do work."""
121+
if self.spins > 300:
122+
# too many spins with no result -> give up
123+
self.stop()
124+
self.start()
125+
return
126+
127+
# lets wait for the OS/platform log to determine if VM is booted,
128+
# login prompt can get lost in boot logs
129+
(ridx, match, res) = self.tn.expect([b"FreeBSD/amd64"], 1)
130+
if match: # got a match!
131+
if ridx == 0: # login
132+
self.logger.info("VM started")
133+
134+
# Login
135+
self.wait_write("\r", None)
136+
self.wait_write("admin", wait="login:")
137+
self.wait_write(self.password, wait="Password:")
138+
self.wait_write("\r", None)
139+
self.logger.info("Login completed")
140+
141+
# close telnet connection
142+
self.tn.close()
143+
# startup time?
144+
startup_time = datetime.datetime.now() - self.start_time
145+
self.logger.info("Startup complete in: %s" % startup_time)
146+
# mark as running
147+
self.running = True
148+
return
149+
150+
# no match, if we saw some output from the router it's probably
151+
# booting, so let's give it some more time
152+
if res != b"":
153+
self.logger.trace("OUTPUT: %s" % res.decode())
154+
# reset spins if we saw some output
155+
self.spins = 0
156+
157+
self.spins += 1
158+
159+
return
160+
161+
162+
class VJUNOSROUTER(vrnetlab.VR):
163+
def __init__(self, hostname, username, password, conn_mode):
164+
super(VJUNOSROUTER, self).__init__(username, password)
165+
self.vms = [VJUNOSROUTER_vm(hostname, username, password, conn_mode)]
166+
167+
168+
if __name__ == "__main__":
169+
import argparse
170+
171+
parser = argparse.ArgumentParser(description="")
172+
parser.add_argument(
173+
"--trace", action="store_true", help="enable trace level logging"
174+
)
175+
parser.add_argument(
176+
"--hostname", default="vr-VJUNOSROUTER", help="vJunos-router hostname"
177+
)
178+
parser.add_argument("--username", default="vrnetlab", help="Username")
179+
parser.add_argument("--password", default="VR-netlab9", help="Password")
180+
parser.add_argument(
181+
"--connection-mode", default="tc", help="Connection mode to use in the datapath"
182+
)
183+
args = parser.parse_args()
184+
185+
LOG_FORMAT = "%(asctime)s: %(module)-10s %(levelname)-8s %(message)s"
186+
logging.basicConfig(format=LOG_FORMAT)
187+
logger = logging.getLogger()
188+
189+
logger.setLevel(logging.DEBUG)
190+
if args.trace:
191+
logger.setLevel(1)
192+
193+
vr = VJUNOSROUTER(
194+
args.hostname,
195+
args.username,
196+
args.password,
197+
conn_mode=args.connection_mode,
198+
)
199+
vr.start()

vjunosrouter/docker/make-config.sh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
# Create a config metadisk from a supplied juniper.conf to attach
3+
# to a vJunos VM instance
4+
usage() {
5+
echo "Usage : make-config.sh <juniper-config> <config-disk>"
6+
exit 0;
7+
}
8+
cleanup () {
9+
echo "Cleaning up..."
10+
umount -f -q $MNTDIR
11+
losetup -d $LOOPDEV
12+
rm -rfv $STAGING
13+
rm -rfv $MNTDIR
14+
}
15+
16+
cleanup_failed () {
17+
cleanup;
18+
rm -rfv $2
19+
exit 1
20+
}
21+
22+
if [ $# != 2 ]; then
23+
usage;
24+
fi
25+
26+
27+
STAGING=`mktemp -d -p /var/tmp`
28+
MNTDIR=`mktemp -d -p /var/tmp`
29+
mkdir $STAGING/config
30+
cp -v $1 $STAGING/config
31+
qemu-img create -f qcow2 $2 1M
32+
LOOPDEV=`losetup --show -f $2`
33+
if [ $? != 0 ]; then
34+
cleanup_failed;
35+
fi
36+
mkfs.vfat -v -n "vmm-data" $LOOPDEV
37+
if [ $? != 0 ]; then
38+
echo "Failed to format disk $LOOPDEV; exiting"
39+
cleanup_failed;
40+
fi
41+
mount -t vfat $LOOPDEV $MNTDIR
42+
if [ $? != 0 ]; then
43+
echo "Failed to mount metadisk $LOOPDEV; exiting"
44+
cleanup_failed;
45+
46+
fi
47+
echo "Copying file(s) to config disk $2"
48+
(cd $STAGING; tar cvzf $MNTDIR/vmm-config.tgz .)
49+
cleanup
50+
echo "Config disk $2 created"
51+
exit 0

0 commit comments

Comments
 (0)