mirror of
https://github.com/vyos/vyos-build.git
synced 2025-10-01 20:28:40 +02:00
463 lines
15 KiB
Python
Executable File
463 lines
15 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2019, VyOS maintainers and contributors
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License version 2 or later as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# File: check-qemu-install
|
|
# Purpose:
|
|
# This script installs a system on a emulated qemu host to verify
|
|
# that the iso produced is installable and boots.
|
|
# after the iso is booted from disk it also tries to execute the
|
|
# vyos-smoketest script to verify checks there.
|
|
#
|
|
# For now it will not fail on failed smoketest but will fail on
|
|
# install and boot errors.
|
|
# Arguments:
|
|
# iso iso image to install
|
|
# [disk] disk filename to use, if none is provided it
|
|
# is autogenerated
|
|
# [--keep] Keep the disk image after completion
|
|
# [--logfile] name of logfile to save, defaulting to stdout
|
|
# [--silent] only print on errors
|
|
# [--debug] print all communication with the device
|
|
|
|
import pexpect
|
|
import sys
|
|
import os
|
|
import time
|
|
import argparse
|
|
import subprocess
|
|
import random
|
|
import traceback
|
|
import logging
|
|
import re
|
|
|
|
from io import BytesIO
|
|
from io import StringIO
|
|
from datetime import datetime
|
|
|
|
EXCEPTION = 0
|
|
now = datetime.now()
|
|
|
|
parser = argparse.ArgumentParser(description='Install and start a test VyOS vm.')
|
|
parser.add_argument('iso', help='ISO file to install')
|
|
parser.add_argument('disk', help='name of disk image file',
|
|
nargs='?',
|
|
default='testinstall-{}-{}.img'.format(now.strftime('%Y%m%d-%H%M%S'),
|
|
"%04x" % random.randint(0,65535)))
|
|
parser.add_argument('--keep', help='Do not remove disk-image after installation',
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument('--silent', help='Do not show output on stdout unless an error has occured',
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument('--debug', help='Send all debug output to stdout',
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument('--logfile', help='Log to file')
|
|
parser.add_argument('--no-kvm', help='Disable use of kvm',
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument('--configd', help='Execute testsuite with config daemon',
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument('--configtest', help='Execute load/commit config tests',
|
|
action='store_true',
|
|
default=False)
|
|
|
|
args = parser.parse_args()
|
|
|
|
class StreamToLogger(object):
|
|
"""
|
|
Fake file-like stream object that redirects writes to a logger instance.
|
|
"""
|
|
def __init__(self, logger, log_level=logging.INFO):
|
|
self.logger = logger
|
|
self.log_level = log_level
|
|
self.linebuf = b''
|
|
self.ansi_escape = re.compile(r'\x1B[@-_][0-?]*[ -/]*[@-~]')
|
|
|
|
def write(self, buf):
|
|
self.linebuf += buf
|
|
#print('.')
|
|
while b'\n' in self.linebuf:
|
|
f = self.linebuf.split(b'\n', 1)
|
|
if len(f) == 2:
|
|
self.logger.debug(self.ansi_escape.sub('', f[0].decode(errors="replace").rstrip()))
|
|
self.linebuf = f[1]
|
|
#print(f)
|
|
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
def get_half_cpus():
|
|
""" return 1/2 of the numbers of available CPUs """
|
|
cpu = os.cpu_count()
|
|
if cpu > 1:
|
|
cpu /= 2
|
|
return int(cpu)
|
|
|
|
def get_qemu_cmd(name, enable_kvm, disk_img, iso_img=None):
|
|
kvm = ""
|
|
cpu = "-cpu host"
|
|
if not enable_kvm:
|
|
kvm = "--no-kvm"
|
|
cpu = ""
|
|
|
|
cdrom = ""
|
|
if iso_img:
|
|
cdrom = "-boot d -cdrom {}".format(iso_img)
|
|
|
|
# test using half of the available CPUs on the system
|
|
cpucount = get_half_cpus()
|
|
|
|
cmd = f'qemu-system-x86_64 \
|
|
-name "{name}" \
|
|
-smp {cpucount} \
|
|
-m 2G \
|
|
-nic user,model=virtio,mac=52:54:99:12:34:56 \
|
|
-nic user,model=virtio,mac=52:54:99:12:34:57 \
|
|
-nic user,model=virtio,mac=52:54:99:12:34:58 \
|
|
-nic user,model=virtio,mac=52:54:99:12:34:59 \
|
|
-machine accel=kvm \
|
|
-nographic \
|
|
{cpu} \
|
|
{cdrom} \
|
|
{kvm} \
|
|
-drive format=raw,file={disk_img}'
|
|
|
|
return cmd
|
|
|
|
|
|
# Setting up logger
|
|
log = logging.getLogger()
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
stl = StreamToLogger(log)
|
|
formatter = logging.Formatter('%(levelname)5s - %(message)s')
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
if args.silent:
|
|
handler.setLevel(logging.ERROR)
|
|
elif args.debug:
|
|
handler.setLevel(logging.DEBUG)
|
|
else:
|
|
handler.setLevel(logging.INFO)
|
|
|
|
handler.setFormatter(formatter)
|
|
log.addHandler(handler)
|
|
|
|
if args.logfile:
|
|
filehandler = logging.FileHandler(args.logfile)
|
|
filehandler.setLevel(logging.DEBUG)
|
|
filehandler.setFormatter(formatter)
|
|
log.addHandler(filehandler)
|
|
|
|
if args.silent:
|
|
output = BytesIO()
|
|
else:
|
|
output = sys.stdout.buffer
|
|
|
|
if not os.path.isfile(args.iso):
|
|
log.error('Unable to find iso image to install')
|
|
sys.exit(1)
|
|
|
|
if args.no_kvm:
|
|
log.error('KVM forced off by command line')
|
|
kvm=False
|
|
elif not os.path.exists('/dev/kvm'):
|
|
log.error('KVM not enabled on host, proceeding with software emulation')
|
|
kvm=False
|
|
else:
|
|
kvm=True
|
|
|
|
# Creating diskimage!!
|
|
if not os.path.isfile(args.disk):
|
|
log.info(f'Creating Disk image {args.disk}')
|
|
c = subprocess.check_output(['qemu-img', 'create', args.disk, '2G'])
|
|
log.debug(c.decode())
|
|
else:
|
|
log.info('Diskimage already exists, using the existing one')
|
|
|
|
try:
|
|
#################################################
|
|
# Installing image to disk
|
|
#################################################
|
|
log.info('Installing system')
|
|
cmd = get_qemu_cmd('TESTVM', kvm, args.disk, args.iso)
|
|
log.debug('Executing command: {cmd}')
|
|
c = pexpect.spawn(cmd, logfile=stl)
|
|
|
|
#################################################
|
|
# Logging into VyOS system
|
|
#################################################
|
|
try:
|
|
c.expect('Automatic boot in', timeout=10)
|
|
c.sendline('')
|
|
except pexpect.TIMEOUT:
|
|
log.warning('Did not find GRUB countdown window, ignoring')
|
|
|
|
log.info('Waiting for login prompt')
|
|
c.expect('[Ll]ogin:', timeout=300)
|
|
c.sendline('vyos')
|
|
c.expect('[Pp]assword:', timeout=10)
|
|
c.sendline('vyos')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
log.info('Logged in!')
|
|
|
|
#################################################
|
|
# Installing into VyOS system
|
|
#################################################
|
|
log.info('Starting installer')
|
|
c.sendline('install image')
|
|
c.expect('\nWould you like to continue?.*:')
|
|
c.sendline('yes')
|
|
log.info('Partitioning disk')
|
|
c.expect('\nPartition.*:')
|
|
c.sendline('')
|
|
c.expect('\nInstall the image on.*:')
|
|
c.sendline('')
|
|
c.expect(r'\nContinue\?.*:')
|
|
c.sendline('Yes')
|
|
c.expect('\nHow big of a root partition should I create?.*:')
|
|
c.sendline('')
|
|
log.info('Disk partitioned, installing')
|
|
c.expect('\nWhat would you like to name this image?.*:')
|
|
c.sendline('')
|
|
log.info('Copying files')
|
|
c.expect('\nWhich one should I copy to.*:', timeout=300)
|
|
c.sendline('')
|
|
log.info('Files Copied!')
|
|
c.expect('\nEnter password for user.*:')
|
|
c.sendline('vyos')
|
|
c.expect('\nRetype password for user.*:')
|
|
c.sendline('vyos')
|
|
c.expect('\nWhich drive should GRUB modify the boot partition on.*:')
|
|
c.sendline('')
|
|
c.expect(r'\nvyos@vyos:~\$')
|
|
log.info('system installed, shutting down')
|
|
|
|
#################################################
|
|
# Powering down installer
|
|
#################################################
|
|
log.info('Shutting down installation system')
|
|
c.sendline('poweroff')
|
|
c.expect(r'\nAre you sure you want to poweroff this system.*\]')
|
|
c.sendline('Y')
|
|
for i in range(30):
|
|
log.info('Waiting for shutdown...')
|
|
if not c.isalive():
|
|
log.info('VM shutdown!')
|
|
break
|
|
time.sleep(10)
|
|
else:
|
|
log.error('VM Did not shutdown after 300sec, killing it!')
|
|
c.close()
|
|
|
|
#################################################
|
|
# Booting installed system
|
|
#################################################
|
|
log.info('Booting installed system')
|
|
cmd = get_qemu_cmd('TESTVM', kvm, args.disk)
|
|
log.debug(f'Executing command: {cmd}')
|
|
c = pexpect.spawn(cmd, logfile=stl)
|
|
|
|
#################################################
|
|
# Logging into VyOS system
|
|
#################################################
|
|
try:
|
|
c.expect('The highlighted entry will be executed automatically in', timeout=10)
|
|
c.sendline('')
|
|
except pexpect.TIMEOUT:
|
|
log.warning('Did not find GRUB countdown window, ignoring')
|
|
|
|
log.info('Waiting for login prompt')
|
|
c.expect('[Ll]ogin:', timeout=300)
|
|
c.sendline('vyos')
|
|
c.expect('[Pp]assword:', timeout=10)
|
|
c.sendline('vyos')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
log.info('Logged in!')
|
|
|
|
# additional settling time
|
|
time.sleep(20)
|
|
|
|
################################################
|
|
# Always load the WiFi simulation module
|
|
################################################
|
|
c.sendline('sudo modprobe mac80211_hwsim')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
#################################################
|
|
# Start/stop config daemon
|
|
#################################################
|
|
if args.configd:
|
|
c.sendline('sudo systemctl start vyos-configd.service &> /dev/null')
|
|
else:
|
|
c.sendline('sudo systemctl stop vyos-configd.service &> /dev/null')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
#################################################
|
|
# Basic Configmode/Opmode switch
|
|
#################################################
|
|
log.info('Basic CLI configuration mode test')
|
|
c.sendline('configure')
|
|
c.expect(r'vyos@vyos#')
|
|
c.sendline('run show version')
|
|
c.sendline('exit')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
#################################################
|
|
# Executing test-suite
|
|
#################################################
|
|
|
|
# run default smoketest suite
|
|
if not args.configtest:
|
|
log.info('Executing VyOS smoketests')
|
|
c.sendline('/usr/bin/vyos-smoketest')
|
|
i = c.expect(['\n +Invalid command:', '\n +Set failed',
|
|
'No such file or directory', r'\n\S+@\S+[$#]'], timeout=7200)
|
|
|
|
if i == 0:
|
|
raise Exception('Invalid command detected')
|
|
elif i == 1:
|
|
raise Exception('Set syntax failed :/')
|
|
elif i == 2:
|
|
tmp = '(W)hy (T)he (F)ace? VyOS smoketest not found!'
|
|
log.error(tmp)
|
|
raise Exception(tmp)
|
|
|
|
c.sendline('echo EXITCODE:$\x16?')
|
|
i = c.expect(['EXITCODE:0', 'EXITCODE:\d+'], timeout=20)
|
|
if i == 0:
|
|
log.info('Smoketest finished successfully!')
|
|
pass
|
|
elif i == 1:
|
|
log.error('Smoketest failed :/')
|
|
raise Exception("Smoketest-failed, please look into debug output")
|
|
|
|
# else, run configtest suite
|
|
else:
|
|
log.info('Generating a WireGuard default keypair')
|
|
c.sendline('generate wireguard default-keypair')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
log.info('Generating some OpenVPN keys')
|
|
subject = '/C=DE/ST=BY/O=VyOS/localityName=Cloud/commonName=vyos/' \
|
|
'organizationalUnitName=VyOS/emailAddress=maintainers@vyos.io/'
|
|
ca_cert = '/config/auth/ovpn_test_ca.pem'
|
|
ssl_cert = '/config/auth/ovpn_test_server.pem'
|
|
ssl_key = '/config/auth/ovpn_test_server.key'
|
|
dh_pem = '/config/auth/ovpn_test_dh.pem'
|
|
s2s_key = '/config/auth/ovpn_test_site2site.key'
|
|
auth_key = '/config/auth/ovpn_test_tls_auth.key'
|
|
|
|
c.sendline(f'openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 '\
|
|
f'-keyout {ssl_key} -out {ssl_cert} -subj {subject}')
|
|
c.expect(r'vyos@vyos:~\$', timeout=600)
|
|
c.sendline(f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} -subj {subject}')
|
|
c.expect(r'vyos@vyos:~\$', timeout=600)
|
|
c.sendline(f'openssl dhparam -out {dh_pem} 2048')
|
|
c.expect(r'vyos@vyos:~\$', timeout=600)
|
|
c.sendline(f'generate openvpn key {s2s_key}')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
c.sendline(f'generate openvpn key {auth_key}')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
script_file = '/config/scripts/vyos-foo-update.script'
|
|
c.sendline(f'echo "#!/bin/sh" > {script_file}; chmod 775 {script_file}')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
for file in [ca_cert, ssl_cert, ssl_key, dh_pem, s2s_key, auth_key]:
|
|
c.sendline(f'sudo chown openvpn:openvpn {file}')
|
|
c.expect(r'vyos@vyos:~\$')
|
|
|
|
log.info('Executing load config tests')
|
|
c.sendline('/usr/bin/vyos-configtest')
|
|
i = c.expect(['\n +Invalid command:', 'No such file or directory',
|
|
r'\n\S+@\S+[$#]'], timeout=3600)
|
|
|
|
if i==0:
|
|
raise Exception('Invalid command detected')
|
|
elif i==1:
|
|
tmp = '(W)hy (T)he (F)ace? VyOS smoketest not found!'
|
|
log.error(tmp)
|
|
raise Exception(tmp)
|
|
|
|
c.sendline('echo EXITCODE:$\x16?')
|
|
i = c.expect(['EXITCODE:0', 'EXITCODE:\d+'], timeout=10)
|
|
if i == 0:
|
|
log.info('Configtest finished successfully!')
|
|
pass
|
|
elif i == 1:
|
|
tmp = 'Configtest failed :/ - check debug output'
|
|
log.error(tmp)
|
|
raise Exception(tmp)
|
|
|
|
#################################################
|
|
# Powering off system
|
|
#################################################
|
|
log.info("Powering off system ")
|
|
c.sendline('poweroff')
|
|
c.expect(r'\nAre you sure you want to poweroff this system.*\]')
|
|
c.sendline('Y')
|
|
log.info('Shutting down virtual machine')
|
|
for i in range(30):
|
|
log.info('Waiting for shutdown...')
|
|
if not c.isalive():
|
|
log.info('VM is shut down!')
|
|
break
|
|
time.sleep(10)
|
|
else:
|
|
tmp = 'VM Did not shut down after 300sec'
|
|
log.error(tmp)
|
|
raise Exception(tmp)
|
|
c.close()
|
|
|
|
except pexpect.exceptions.TIMEOUT:
|
|
log.error('Timeout waiting for VyOS system')
|
|
log.error(traceback.format_exc())
|
|
EXCEPTION = 1
|
|
|
|
except pexpect.exceptions.ExceptionPexpect:
|
|
log.error('Exeption while executing QEMU')
|
|
log.error('Is qemu working on this system?')
|
|
log.error(traceback.format_exc())
|
|
EXCEPTION = 1
|
|
|
|
except Exception:
|
|
log.error('Unknown error occured while VyOS!')
|
|
traceback.print_exc()
|
|
EXCEPTION = 1
|
|
|
|
#################################################
|
|
# Cleaning up
|
|
#################################################
|
|
log.info("Cleaning up")
|
|
|
|
if not args.keep:
|
|
log.info(f'Removing disk file: {args.disk}')
|
|
try:
|
|
os.remove(args.disk)
|
|
except Exception:
|
|
log.error('Exception while removing diskimage!')
|
|
log.error(traceback.format_exc())
|
|
EXCEPTION = 1
|
|
|
|
if EXCEPTION:
|
|
log.error('Hmm... system got an exception while processing.')
|
|
log.error('The ISO image is not considered usable!')
|
|
sys.exit(1)
|