Skip to content

Commit 67154b7

Browse files
committed
Support for ArubaOS CX
1 parent e561478 commit 67154b7

File tree

4 files changed

+250
-0
lines changed

4 files changed

+250
-0
lines changed

aoscx/Makefile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
VENDOR=Aruba
2+
NAME=ArubaOS-CX
3+
IMAGE_FORMAT=vmdk
4+
IMAGE_GLOB=*.vmdk
5+
6+
# match versions like:
7+
# arubaoscx-disk-image-genericx86-p4-20230531220439.vmdk
8+
VERSION=$(shell echo $(IMAGE) | sed -rn 's/.*arubaoscx-disk-image-genericx86-p4-(.+)\.vmdk/\1/p')
9+
10+
-include ../makefile-sanity.include
11+
-include ../makefile.include

aoscx/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# vrnetlab / ArubaOS-CX (aoscx)
2+
3+
This is the vrnetlab docker image for ArubaOS-CX.
4+
5+
## Building the docker image
6+
Download the OVA image from [Aruba Support Portal](https://asp.arubanetworks.com/downloads/software/RmlsZTpkOGRiYjc2Ni0wMTdkLTExZWUtYTY3Yi00Zjg4YjUyOWExMzQ%3D), and extract the vmdk file from it.
7+
Copy the vmdk image into this folder, then run `make docker-image`.
8+
9+
Tested booting and responding to SSH:
10+
* `ArubaOS-CX_10_12_0006.ova` (`arubaoscx-disk-image-genericx86-p4-20230531220439.vmdk`)
11+
12+
13+
## System requirements
14+
CPU: 2 core
15+
16+
RAM: 4GB
17+
18+
Disk: <1GB
19+

aoscx/docker/Dockerfile

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM debian:bullseye
2+
MAINTAINER Stefano Sasso <[email protected]>
3+
4+
ENV DEBIAN_FRONTEND=noninteractive
5+
6+
RUN apt-get update -qy \
7+
&& apt-get upgrade -qy \
8+
&& apt-get install -y \
9+
bridge-utils \
10+
iproute2 \
11+
python3-ipy \
12+
socat \
13+
tcpdump \
14+
ssh \
15+
inetutils-ping \
16+
dnsutils \
17+
iptables \
18+
telnet \
19+
ftp \
20+
qemu-system-x86=1:5.2+dfsg-11+deb11u2 \
21+
qemu-utils=1:5.2+dfsg-11+deb11u2 \
22+
&& rm -rf /var/lib/apt/lists/*
23+
24+
ARG IMAGE
25+
COPY $IMAGE* /
26+
COPY *.py /
27+
28+
EXPOSE 22 80 161/udp 443 830 5000 5678 8291 10000-10099
29+
HEALTHCHECK CMD ["/healthcheck.py"]
30+
ENTRYPOINT ["/launch.py"]

aoscx/docker/launch.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python3
2+
3+
import datetime
4+
import logging
5+
import os
6+
import re
7+
import signal
8+
import sys
9+
import time
10+
11+
import vrnetlab
12+
13+
STARTUP_CONFIG_FILE = "/config/startup-config.cfg"
14+
15+
def handle_SIGCHLD(signal, frame):
16+
os.waitpid(-1, os.WNOHANG)
17+
18+
19+
def handle_SIGTERM(signal, frame):
20+
sys.exit(0)
21+
22+
23+
signal.signal(signal.SIGINT, handle_SIGTERM)
24+
signal.signal(signal.SIGTERM, handle_SIGTERM)
25+
signal.signal(signal.SIGCHLD, handle_SIGCHLD)
26+
27+
TRACE_LEVEL_NUM = 9
28+
logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
29+
30+
31+
def trace(self, message, *args, **kws):
32+
# Yes, logger takes its '*args' as 'args'.
33+
if self.isEnabledFor(TRACE_LEVEL_NUM):
34+
self._log(TRACE_LEVEL_NUM, message, args, **kws)
35+
36+
37+
logging.Logger.trace = trace
38+
39+
40+
class AOSCX_vm(vrnetlab.VM):
41+
def __init__(self, hostname, username, password, conn_mode):
42+
disk_image = ""
43+
for e in os.listdir("/"):
44+
if re.search(".vmdk$", e):
45+
disk_image = "/" + e
46+
if disk_image == "":
47+
logging.getLogger().info("Disk image was not found")
48+
exit(1)
49+
super(AOSCX_vm, self).__init__(
50+
username, password, disk_image=disk_image, ram=4096
51+
)
52+
self.hostname = hostname
53+
self.conn_mode = conn_mode
54+
self.num_nics = 20
55+
self.nic_type = "virtio-net-pci"
56+
self.qemu_args.extend(["-cpu", "host,level=9"])
57+
self.qemu_args.extend(["-smp", "2"])
58+
59+
def bootstrap_spin(self):
60+
"""This function should be called periodically to do work."""
61+
62+
if self.spins > 300:
63+
# too many spins with no result -> give up
64+
self.logger.info("To many spins with no result, restarting")
65+
self.stop()
66+
self.start()
67+
return
68+
69+
(ridx, match, res) = self.tn.expect([b"switch login:"], 1)
70+
if match: # got a match!
71+
if ridx == 0: # login
72+
self.logger.debug("trying to log in with 'admin'")
73+
self.wait_write("\r", wait=None)
74+
self.logger.debug("sent newline")
75+
self.wait_write("admin", wait="switch login:")
76+
self.logger.debug("sent username")
77+
self.wait_write("\r", wait="Password:")
78+
self.logger.debug("sent empty password")
79+
self.logger.debug("resetting password")
80+
self.wait_write("admin", wait="Enter new password:")
81+
self.wait_write("admin", wait="Confirm new password:")
82+
83+
# run main config!
84+
self.bootstrap_config()
85+
self.startup_config()
86+
# close telnet connection
87+
self.tn.close()
88+
# startup time?
89+
startup_time = datetime.datetime.now() - self.start_time
90+
self.logger.info("Startup complete in: %s" % startup_time)
91+
# mark as running
92+
self.running = True
93+
return
94+
95+
# no match, if we saw some output from the router it's probably
96+
# booting, so let's give it some more time
97+
if res != b"":
98+
self.logger.trace("OUTPUT: %s" % res.decode())
99+
# reset spins if we saw some output
100+
self.spins = 0
101+
102+
self.spins += 1
103+
104+
return
105+
106+
def bootstrap_config(self):
107+
"""Do the actual bootstrap config"""
108+
self.logger.info("applying bootstrap configuration")
109+
self.wait_write("", None)
110+
self.wait_write("configure")
111+
self.wait_write(
112+
"user %s group administrators password plaintext %s"
113+
% (self.username, self.password)
114+
)
115+
116+
# configure mgmt interface
117+
self.wait_write("interface mgmt")
118+
self.wait_write("ip static 10.0.0.15/24")
119+
self.wait_write("no shutdown")
120+
self.wait_write("exit")
121+
self.wait_write("ssh server vrf mgmt")
122+
self.wait_write("https-server vrf mgmt")
123+
self.wait_write("ntp vrf mgmt")
124+
125+
self.wait_write(f"hostname {self.hostname}")
126+
127+
self.wait_write("end")
128+
self.wait_write("write memory")
129+
130+
def startup_config(self):
131+
"""Load additional config provided by user."""
132+
133+
if not os.path.exists(STARTUP_CONFIG_FILE):
134+
self.logger.trace(f"Startup config file {STARTUP_CONFIG_FILE} is not found")
135+
return
136+
137+
self.logger.trace(f"Startup config file {STARTUP_CONFIG_FILE} exists")
138+
with open(STARTUP_CONFIG_FILE) as file:
139+
config_lines = file.readlines()
140+
config_lines = [line.rstrip() for line in config_lines]
141+
self.logger.trace(f"Parsed startup config file {STARTUP_CONFIG_FILE}")
142+
143+
self.logger.info(f"Writing lines from {STARTUP_CONFIG_FILE}")
144+
145+
self.wait_write("configure")
146+
# Apply lines from file
147+
for line in config_lines:
148+
self.wait_write(line)
149+
# End and Save
150+
self.wait_write("end")
151+
self.wait_write("write memory")
152+
153+
154+
class AOSCX(vrnetlab.VR):
155+
def __init__(self, hostname, username, password, conn_mode):
156+
super(AOSCX, self).__init__(username, password)
157+
self.vms = [AOSCX_vm(hostname, username, password, conn_mode)]
158+
159+
160+
if __name__ == "__main__":
161+
import argparse
162+
163+
parser = argparse.ArgumentParser(description="")
164+
parser.add_argument(
165+
"--trace", action="store_true", help="enable trace level logging"
166+
)
167+
parser.add_argument("--hostname", default="vr-aoscx", help="Router hostname")
168+
parser.add_argument("--username", default="vrnetlab", help="Username")
169+
parser.add_argument("--password", default="VR-netlab9", help="Password")
170+
parser.add_argument(
171+
"--connection-mode",
172+
default="vrxcon",
173+
help="Connection mode to use in the datapath",
174+
)
175+
args = parser.parse_args()
176+
177+
LOG_FORMAT = "%(asctime)s: %(module)-10s %(levelname)-8s %(message)s"
178+
logging.basicConfig(format=LOG_FORMAT)
179+
logger = logging.getLogger()
180+
181+
logger.setLevel(logging.DEBUG)
182+
if args.trace:
183+
logger.setLevel(1)
184+
185+
logger.debug(f"Environment variables: {os.environ}")
186+
vrnetlab.boot_delay()
187+
188+
vr = AOSCX(args.hostname, args.username, args.password, args.connection_mode)
189+
vr.start()
190+

0 commit comments

Comments
 (0)