mirror of
ssh://git.janware.com/srv/git/janware/proj/jw-devtest
synced 2026-01-18 03:23:57 +01:00
First commit
Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
commit
4b912741cb
73 changed files with 3753 additions and 0 deletions
383
src/python/devtest/os/be/qemu/Machine.py
Normal file
383
src/python/devtest/os/be/qemu/Machine.py
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import subprocess
|
||||
import importlib
|
||||
import re
|
||||
import io
|
||||
import shutil
|
||||
import functools
|
||||
from jwutils.log import *
|
||||
from jwutils.misc import get_derived_classes
|
||||
from ...misc import *
|
||||
from ...test import *
|
||||
from ...Machine import Machine as MachineBase
|
||||
from ...Connection import Connection
|
||||
from ...conn.Fifos import Fifos as ConnFifos
|
||||
from . import Invocation
|
||||
|
||||
_ifupdown_script="""#!/bin/bash
|
||||
|
||||
#echo exit for test purposes; exit 1
|
||||
goodbye()
|
||||
{
|
||||
:
|
||||
rm -rf $tmp_files
|
||||
}
|
||||
|
||||
usage()
|
||||
{
|
||||
cat << EOT >&2
|
||||
|
||||
$myname -h
|
||||
$myname {net|<image-file>} [-b hostname]
|
||||
|
||||
EOT
|
||||
[ "$1" ] && exit $1
|
||||
exit 0
|
||||
}
|
||||
|
||||
log()
|
||||
{
|
||||
local tag=`whoami`@$myname
|
||||
echo "$log_delim [$tag] $*"
|
||||
/usr/bin/logger -t "$tag" "$*"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
log "$@" >&2
|
||||
}
|
||||
|
||||
fatal()
|
||||
{
|
||||
err "Fatal: $@ ... giving up"
|
||||
exit 1
|
||||
}
|
||||
|
||||
run()
|
||||
{
|
||||
log running $@
|
||||
"$@"
|
||||
return $?
|
||||
}
|
||||
|
||||
bridge()
|
||||
{
|
||||
case "$1" in
|
||||
start)
|
||||
die() {
|
||||
log $@
|
||||
bridge stop
|
||||
exit 1
|
||||
}
|
||||
|
||||
failed_to() {
|
||||
die "failed to $@"
|
||||
}
|
||||
|
||||
log setting up network on $bridge_name
|
||||
|
||||
local -r net_prefix_len=$(echo $bridge_net | sed 's:.*/::')
|
||||
[ $net_prefix_len -lt 24 ] && fatal currently only class-C networks are supported
|
||||
local -r net_addr=$(echo $bridge_net | sed 's:/.*::')
|
||||
local -r last_octet_net_addr=$(echo $net_addr | sed 's/.*\.//')
|
||||
if [ "$bridge_ip" ]; then
|
||||
local_bridge_ip=$bridge_ip
|
||||
else
|
||||
local -r last_octet_bridge_ip=$(( $last_octet_net_addr + 1 ))
|
||||
local_bridge_ip=$(echo $net_addr | sed "s/\.[0-9]\+$/.$last_octet_bridge_ip/")
|
||||
fi
|
||||
local -r last_octet_broadcast=$(( $last_octet_net_addr + 2 ** (32 - $net_prefix_len) - 1 ))
|
||||
local -r broadcast=$(echo $net_addr | sed "s/\.[0-9]\+$/.$last_octet_broadcast/")
|
||||
|
||||
run $brctl_exe addbr $bridge_name || failed_to "add bridge $bridge_name"
|
||||
#run $brctl_exe stp $bridge_name off || failed_to "disable spanning tree protocol on bridge $bridge_name"
|
||||
run $brctl_exe setfd $bridge_name 0 || failed_to "set forward delay of bridge $bridge_name"
|
||||
run $ip_exe address add $local_bridge_ip broadcast $broadcast dev $bridge_name \
|
||||
|| failed_to "add IP address to bridge $bridge_name"
|
||||
run $ip_exe link set $bridge_name up arp on || failed_to "switch on arp on bridge $bridge_name"
|
||||
run $ip_exe route add to $bridge_net dev $bridge_name || failed_to "add route over bridge $bridge_name"
|
||||
;;
|
||||
stop)
|
||||
log shutting down network on $bridge_name
|
||||
run $ip_exe link set $bridge_name down
|
||||
run $brctl_exe delbr $bridge_name
|
||||
;;
|
||||
restart)
|
||||
bridge stop
|
||||
sleep 1
|
||||
bridge start
|
||||
;;
|
||||
check)
|
||||
LANG=POSIX $brctl_exe show $bridge_name 2>&1 | grep -q "$bridge_name.*No such device" && return 1
|
||||
$brctl_exe show $bridge_name >/dev/null 2>&1
|
||||
return $?
|
||||
;;
|
||||
*)
|
||||
echo "Usage: bridge {start|stop|restart|check}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ref_bridge()
|
||||
{
|
||||
bridge check || bridge start
|
||||
}
|
||||
|
||||
unref_bridge()
|
||||
{
|
||||
bridge check || return 0
|
||||
$brctl_exe show $bridge_name | awk "/^$bridge_name/ {print \$4}" | grep -q . || {
|
||||
log bridge $bridge_name is unused, cleaning up
|
||||
bridge stop
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# -- here we go
|
||||
|
||||
myname=`basename $0`
|
||||
log_delim="---------------"
|
||||
log running $0 $@
|
||||
[ -x $0 ] || chmod u+x $0 || fatal "$0 is not executable"
|
||||
exe=`readlink -f $0`
|
||||
dirname=`dirname $exe`
|
||||
bridge_name=brdev0
|
||||
#bridge_name=in1
|
||||
base=dc=priv,dc=lcl
|
||||
brctl_exe=`PATH=/usr/sbin:/sbin /usr/bin/which brctl`
|
||||
ip_exe=`PATH=/usr/sbin:/sbin /usr/bin/which ip`
|
||||
tmp_files=""
|
||||
tmpdir=`dirname $0`
|
||||
config=$tmpdir/config.sh
|
||||
|
||||
# -- qemu default options
|
||||
bridge_net=192.168.100.0/24
|
||||
mac_addr=,macaddr=00:0B:DC:9B:D6:DA
|
||||
|
||||
[ -r "$config" ] && . $config
|
||||
|
||||
trap goodbye INT QUIT EXIT KILL TERM PIPE
|
||||
|
||||
case $myname in
|
||||
*ifup*)
|
||||
ref_bridge
|
||||
virt_if=$1
|
||||
run $ip_exe link set $virt_if up
|
||||
run $brctl_exe addif $bridge_name $virt_if
|
||||
;;
|
||||
|
||||
*ifdown*)
|
||||
virt_if=$1
|
||||
run $brctl_exe delif $bridge_name $virt_if
|
||||
run $ip_exe link set $virt_if down
|
||||
unref_bridge
|
||||
;;
|
||||
*)
|
||||
echo called as unknown executable name $0
|
||||
;;
|
||||
esac
|
||||
"""
|
||||
|
||||
class SubprocessProtocol(asyncio.SubprocessProtocol):
|
||||
|
||||
def __init__(self, machine, name):
|
||||
self.machine = machine
|
||||
self.name = name
|
||||
super().__init__()
|
||||
|
||||
def pipe_data_received(self, fd, data):
|
||||
stream = "stdout" if fd == 1 else ("stderr" if fd == 2 else str(fd))
|
||||
tag = stream + '@' + self.name
|
||||
data = data.decode().rstrip('\n')
|
||||
prio = WARNING if fd == 2 else NOTICE
|
||||
for line in data.split('\n'):
|
||||
slog(prio, "[%s] %s" % (tag, line.rstrip('\r\n')))
|
||||
|
||||
def process_exited(self):
|
||||
slog(NOTICE, "[%s] process exited" % (self.name))
|
||||
super().process_exited()
|
||||
self.machine.qemu_exited()
|
||||
|
||||
class Machine(MachineBase): # export
|
||||
|
||||
def __init__(self, env):
|
||||
super().__init__(env)
|
||||
self.monitor = None
|
||||
self.console = None
|
||||
self.__running = False
|
||||
self.__shutdown_requested = False
|
||||
self.__clear_for_tests = True
|
||||
self.__net_helper = None
|
||||
self.__invocation = None
|
||||
self.__transport = None
|
||||
self.__protocol = None
|
||||
self.__proc = None
|
||||
self.__rc = None
|
||||
self.__task = None
|
||||
|
||||
async def __await_monitor_prompt(self, act_timeout=2.):
|
||||
if await expect(self.monitor, regex='\(qemu\) ', subject="Qemu monitor prompt",
|
||||
act_timeout=act_timeout) is None:
|
||||
raise Exception("timed out waiting for Qemu monitor prompt")
|
||||
|
||||
def __mkfifo(self, tp, out):
|
||||
name = self.fifo_base(tp) + "." + ("out" if out else "in")
|
||||
try:
|
||||
st = os.stat(name)
|
||||
except:
|
||||
slog(DEBUG, "running mkfifo(%s)" % name)
|
||||
os.mkfifo(name)
|
||||
return name
|
||||
|
||||
def __mkfifos(self, tp):
|
||||
return (self.__mkfifo(tp, True), self.__mkfifo(tp, False))
|
||||
|
||||
async def __exec_qemu(self, env):
|
||||
|
||||
def format_cmdline(arr):
|
||||
r = ''
|
||||
for tok in arr:
|
||||
if re.search(' ', tok):
|
||||
r += ' "%s"' % tok
|
||||
continue
|
||||
r += ' ' + tok
|
||||
return r[1:]
|
||||
|
||||
try:
|
||||
slog(INFO, "opening read-FIFOs to machine")
|
||||
for name in [ "monitor", "console" ]:
|
||||
c = ConnFifos(self.env, "Serial", paths=self.__mkfifos(name))
|
||||
await c.fifo(ConnFifos.Dir.In).open(timeout=1.)
|
||||
setattr(self, name, c)
|
||||
slog(NOTICE, "==== invoking qemu: ", format_cmdline(self.invocation.cmdline))
|
||||
self.__transport, self.__protocol = await env.eloop.subprocess_exec(
|
||||
lambda: SubprocessProtocol(self, "qemu"),
|
||||
*self.invocation.cmdline,
|
||||
)
|
||||
self.__proc = self.__transport.get_extra_info('subprocess') # Popen instance
|
||||
|
||||
for name in [ "monitor", "console" ]:
|
||||
c = self.__dict__[name]
|
||||
await c.fifo(ConnFifos.Dir.Out).open(retry=4, retry_log_level=INFO)
|
||||
await self.__await_monitor_prompt()
|
||||
except:
|
||||
slog(ERR, "failed to run Qemu process")
|
||||
raise
|
||||
|
||||
def __reap_qemu(self):
|
||||
if self.__rc is None and self.__transport:
|
||||
self.__transport = None
|
||||
self.__rc = self.__proc.wait()
|
||||
|
||||
async def __cleanup_qemu(self):
|
||||
pid = self.__reap_qemu()
|
||||
if self.__rc == 0 and self.__shutdown_requested:
|
||||
slog(NOTICE, "the Qemu process (pid {}) has exited cleanly".format(self.__proc.pid))
|
||||
self.monitor = self.console = self.__protocol = self.__task = None
|
||||
return 0
|
||||
self.__clear_for_tests = False
|
||||
slog(ERR, "the Qemu process (pid {}) has exited {}with status code {}, aborting test".format(
|
||||
pid, "" if self.__shutdown_requested else "prematurely ", self.__rc))
|
||||
exit(1)
|
||||
|
||||
# ---- utilities
|
||||
|
||||
# to be called from SIGCHLD handler
|
||||
def qemu_exited(self):
|
||||
slog(INFO, "Qemu process exited")
|
||||
self.__clear_for_tests = False
|
||||
self.__reap_qemu()
|
||||
#self.__cleanup_qemu()
|
||||
|
||||
@property
|
||||
def invocation(self):
|
||||
if not self.__invocation:
|
||||
mod_name = "Invocation_" + self.env.args.platform
|
||||
mod = importlib.import_module("devtest.os.be.qemu." + mod_name)
|
||||
classes = get_derived_classes(mod, Invocation.Invocation)
|
||||
if not len(classes):
|
||||
raise Exception("unsupported platform >" + self.env.args.platform + "<")
|
||||
self.__invocation = classes[0](self)
|
||||
slog(DEBUG, "found invocation definition class >%s<" % self.__invocation.typename())
|
||||
return self.__invocation
|
||||
|
||||
def net_helper_path(self, up):
|
||||
if re.search('ifup\|ifdown', self.exe_basename):
|
||||
return None
|
||||
if not self.__net_helper:
|
||||
slog_m(INFO, "config = >%s<" % self.invocation.helper_config)
|
||||
self.__net_helper = []
|
||||
script_name = 'ifupdown.sh'
|
||||
script_path = self.tmpdir + "/" + script_name
|
||||
if self.env.args.qe_network_script:
|
||||
shutil.copyfile(self.env.args.qe_network_script, script_path)
|
||||
else:
|
||||
with open(script_path, "w") as fd:
|
||||
fd.write(_ifupdown_script)
|
||||
os.chmod(script_path, 0o755)
|
||||
with open(self.tmpdir + "/config.sh", "w") as fd:
|
||||
fd.write(self.invocation.helper_config)
|
||||
for name in [ "ifdown", "ifup" ]:
|
||||
path = "{}/{}.sh".format(self.tmpdir, name)
|
||||
os.symlink(script_name, path)
|
||||
self.__net_helper.append(path)
|
||||
return self.__net_helper[up]
|
||||
|
||||
def fifo_base(self, tp):
|
||||
return self.tmpdir + "/qemu-" + self.env.args.platform + "-" + tp
|
||||
|
||||
# ---- reimplementation of class API methods
|
||||
|
||||
async def init(self):
|
||||
self.__task = await self.env.eloop.create_task(self.__exec_qemu(self.env))
|
||||
|
||||
async def cleanup(self, env):
|
||||
await self.__cleanup_qemu()
|
||||
|
||||
def clear_for_tests(self):
|
||||
return self.__clear_for_tests
|
||||
|
||||
def register_connection(self, env, info):
|
||||
slog(NOTICE, 'registering Qemu connection "{}" to platform "{}"'.format(
|
||||
info, self.env.args.platform))
|
||||
try:
|
||||
if info.proto == Connection.Proto.Console:
|
||||
r = self.console # TODO: info.spec ignored
|
||||
else:
|
||||
return super().register_connection(env, info)
|
||||
self._register_connection(r)
|
||||
except Exception as e:
|
||||
slog(ERR, "failed to open connection {}: {}".format(info, e))
|
||||
raise
|
||||
return r
|
||||
|
||||
async def unregister_connection(self, conn):
|
||||
return await super().unregister_connection(conn)
|
||||
|
||||
async def request_power_on(self, env):
|
||||
if self.__running:
|
||||
raise Exception("Tried to power on a running Qemu machine")
|
||||
slog(NOTICE, "switching on CPU")
|
||||
await self.monitor.write(b'cont\n')
|
||||
await self.__await_monitor_prompt()
|
||||
slog(NOTICE, "switched on CPU")
|
||||
self.__running = True
|
||||
|
||||
async def wait_up(self, env):
|
||||
return True
|
||||
|
||||
async def request_shutdown(self, env):
|
||||
if not self.__shutdown_requested:
|
||||
slog(NOTICE, "requesting shutdown")
|
||||
self.__shutdown_requested = True
|
||||
if self.monitor and self.__rc is None:
|
||||
await self.monitor.write(b'quit\n')
|
||||
|
||||
async def wait_poweroff(self, env):
|
||||
slog(NOTICE, "waiting on powerdown")
|
||||
await self.__cleanup_qemu()
|
||||
return True
|
||||
Loading…
Add table
Add a link
Reference in a new issue