summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Williams <neil.williams@linaro.org>2016-09-07 06:37:07 (GMT)
committerNeil Williams <neil.williams@linaro.org>2016-09-07 06:37:07 (GMT)
commit7a30add2fd8e0de030ac75c3e6c34a15dc9a1d01 (patch)
tree9d28db3661a6fbaffd7379f1ed21c3cc1140531c
parent4b94ece3def562eb9883a1116ab373366e3deaed (diff)
parent4bb2e5f4ca06531d321bdcd96395a3f9b44c6410 (diff)
downloadlava-dispatcher-release.tar.gz
lava-dispatcher-release.tar.xz
Merge branch 'staging' into release2016.9release
-rw-r--r--etc/lava-slave15
-rwxr-xr-xetc/lava-slave.init50
-rw-r--r--etc/logrotate.d/lava-slave-log5
-rw-r--r--lava/dispatcher/daemonise.py14
-rwxr-xr-xlava/dispatcher/lava-dispatcher-slave97
-rw-r--r--lava_dispatcher/actions/lava_test_shell.py7
-rw-r--r--lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf6
-rw-r--r--lava_dispatcher/deployment_data.py13
-rw-r--r--lava_dispatcher/lava_test_shell/vland/lava-vland-names9
-rw-r--r--lava_dispatcher/pipeline/action.py48
-rw-r--r--lava_dispatcher/pipeline/actions/boot/environment.py4
-rw-r--r--lava_dispatcher/pipeline/actions/boot/fastboot.py10
-rw-r--r--lava_dispatcher/pipeline/actions/boot/lxc.py5
-rw-r--r--lava_dispatcher/pipeline/actions/boot/qemu.py112
-rw-r--r--lava_dispatcher/pipeline/actions/boot/strategies.py2
-rw-r--r--lava_dispatcher/pipeline/actions/boot/uefi_menu.py47
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/apply_overlay.py2
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/download.py14
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/fastboot.py4
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/image.py62
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/lxc.py13
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/overlay.py28
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/strategies.py2
-rw-r--r--lava_dispatcher/pipeline/actions/deploy/testdef.py3
-rw-r--r--lava_dispatcher/pipeline/actions/test/monitor.py175
-rw-r--r--lava_dispatcher/pipeline/actions/test/shell.py16
-rw-r--r--lava_dispatcher/pipeline/actions/test/strategies.py1
-rw-r--r--lava_dispatcher/pipeline/connections/lxc.py33
-rw-r--r--lava_dispatcher/pipeline/connections/serial.py2
-rw-r--r--lava_dispatcher/pipeline/deployment_data.py23
-rw-r--r--lava_dispatcher/pipeline/device.py14
-rw-r--r--lava_dispatcher/pipeline/device_types/hi6220-hikey.conf2
-rw-r--r--lava_dispatcher/pipeline/devices/hi6220-hikey-01.yaml24
-rw-r--r--lava_dispatcher/pipeline/job.py38
-rw-r--r--lava_dispatcher/pipeline/logical.py9
-rw-r--r--lava_dispatcher/pipeline/parser.py17
-rw-r--r--lava_dispatcher/pipeline/power.py42
-rw-r--r--lava_dispatcher/pipeline/protocols/vland.py8
-rw-r--r--lava_dispatcher/pipeline/test/pipeline_refs/fastboot.yaml1
-rw-r--r--lava_dispatcher/pipeline/test/pipeline_refs/lxc.yaml1
-rw-r--r--lava_dispatcher/pipeline/test/pipeline_refs/mustang-uefi.yaml2
-rw-r--r--lava_dispatcher/pipeline/test/sample_jobs/hi6220-hikey.yaml84
-rw-r--r--lava_dispatcher/pipeline/test/sample_jobs/luvos-monitor-qemu.yaml70
-rw-r--r--lava_dispatcher/pipeline/test/sample_jobs/qemu-monitor.yaml39
-rw-r--r--lava_dispatcher/pipeline/test/test_basic.py3
-rw-r--r--lava_dispatcher/pipeline/test/test_fastboot.py27
-rw-r--r--lava_dispatcher/pipeline/test/test_kvm.py14
-rw-r--r--lava_dispatcher/pipeline/test/test_utils.py18
-rw-r--r--lava_dispatcher/pipeline/test/test_vland.py15
-rw-r--r--lava_dispatcher/pipeline/utils/compression.py4
-rw-r--r--lava_dispatcher/pipeline/utils/constants.py3
-rw-r--r--lava_dispatcher/pipeline/utils/vcs.py15
-rw-r--r--man/lava-slave.rst16
53 files changed, 1035 insertions, 253 deletions
diff --git a/etc/lava-slave b/etc/lava-slave
new file mode 100644
index 0000000..89532d1
--- /dev/null
+++ b/etc/lava-slave
@@ -0,0 +1,15 @@
+# Configuration for lava-slave daemon
+
+# URL to the master and the logger
+# MASTER_URL="tcp://<lava-master-dns>:5556"
+# LOGGER_URL="tcp://<lava-master-dns>:5555"
+
+# Logging level should be uppercase (DEBUG, INFO, WARNING, ERROR)
+# LOGLEVEL="DEBUG"
+
+# Encryption
+# If set, will activate encryption using the master public and the slave
+# private keys
+# ENCRYPT="--encrypt"
+# MASTER_CERT="--master-cert /etc/lava-dispatcher/certificates.d/<master.key>"
+# SLAVE_CERT="--slave-cert /etc/lava-dispatcher/certificates.d/<slave.key_secret>"
diff --git a/etc/lava-slave.init b/etc/lava-slave.init
index c4bf1c9..55ee924 100755
--- a/etc/lava-slave.init
+++ b/etc/lava-slave.init
@@ -14,28 +14,31 @@
# example command:
# python dispatcher-slave.py --master tcp://localhost:5556 --socket-addr tcp://localhost:5555 --level DEBUG
-MASTER="--master tcp://localhost:5556"
-LOG_SOCKET="--socket-addr tcp://localhost:5555"
-LOGLEVEL="--level=DEBUG"
+# Default variables
+MASTER_URL="tcp://localhost:5556"
+LOGGER_URL="tcp://localhost:5555"
+LOGLEVEL="DEBUG"
+ENCRYPT=""
+MASTER_CERT=""
+SLAVE_CERT=""
+
+# Read configuration variable files if present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+# This script fragment can overwrite the variables defined above
+[ -r /etc/lava-dispatcher/lava-slave ] && . /etc/lava-dispatcher/lava-slave
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="lava-slave" # short description
NAME=lava-slave # short server's name
DAEMON="/usr/bin/lava-slave" # server's location
-DAEMON_ARGS="${MASTER} ${LOG_SOCKET} $LOGLEVEL" # Arguments to run the daemon with
+DAEMON_ARGS="--master $MASTER_URL --socket-addr $LOGGER_URL --level $LOGLEVEL $ENCRYPT $MASTER_CERT $SLAVE_CERT" # Arguments to run the daemon with
PIDFILE=/var/run/lava-slave.pid
SCRIPTNAME=/etc/init.d/lava-slave
# Exit if the package is not installed
[ -x $DAEMON ] || exit 0
-# Read configuration variable file if it is present
-[ -r /etc/default/$NAME ] && . /etc/default/$NAME
-
-# Load the VERBOSE setting and other rcS variables
-#. /lib/init/vars.sh
-
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
@@ -83,10 +86,7 @@ do_stop()
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
- # clear the old pid lockfile
- if [ -f /var/run/lava-server.pid.lock ]; then
- rm -f /var/run/lava-server.pid.lock
- fi
+ rm -f $PIDFILE.lock
return "$RETVAL"
}
@@ -94,13 +94,7 @@ do_stop()
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
- #
- # If the daemon can reload its configuration without
- # restarting (for example, when it is sent a SIGHUP),
- # then implement that here.
- #
- # on master, this is a no-op. on a worker, refresh the sshfs mount
- lava-mount-masterfs || true
+ # ask the slave to re-open the log file
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
return 0
}
@@ -125,15 +119,11 @@ case "$1" in
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
- #reload|force-reload)
- #
- # If do_reload() is not implemented then leave this commented out
- # and leave 'force-reload' as an alias for 'restart'.
- #
- #log_daemon_msg "Reloading $DESC" "$NAME"
- #do_reload
- #log_end_msg $?
- #;;
+ reload|force-reload)
+ log_daemon_msg "Reloading $DESC" "$NAME"
+ do_reload
+ log_end_msg $?
+ ;;
restart|force-reload)
#
# If the "reload" option is implemented then remove the
diff --git a/etc/logrotate.d/lava-slave-log b/etc/logrotate.d/lava-slave-log
index e8f2af9..1cfe7b3 100644
--- a/etc/logrotate.d/lava-slave-log
+++ b/etc/logrotate.d/lava-slave-log
@@ -6,4 +6,9 @@
missingok
notifempty
create 644 root root
+ postrotate
+ if /etc/init.d/lava-slave status > /dev/null ; then \
+ /etc/init.d/lava-slave reload > /dev/null; \
+ fi;
+ endscript
}
diff --git a/lava/dispatcher/daemonise.py b/lava/dispatcher/daemonise.py
index b8ddc0c..0e67777 100644
--- a/lava/dispatcher/daemonise.py
+++ b/lava/dispatcher/daemonise.py
@@ -36,13 +36,17 @@ from subprocess import Popen
child = None
-def signal_handler(sig, frame): # pylint: disable=unused-argument
+def signal_handler(signum, frame): # pylint: disable=unused-argument
global child # pylint: disable=global-statement
try:
- logging.info("Closing daemon and child %d" % child.pid) # pylint: disable=logging-not-lazy
- child.send_signal(sig)
- child = None
- sys.exit(os.EX_OK)
+ if signum == signal.SIGHUP:
+ logging.info("Forwarding a SIGHUP to the child")
+ child.send_signal(signum)
+ else:
+ logging.info("Closing daemon and child %d" % child.pid)
+ child.send_signal(signum)
+ child = None
+ sys.exit(os.EX_OK)
except Exception as e:
raise Exception('Error in signal handler: ' + str(e))
diff --git a/lava/dispatcher/lava-dispatcher-slave b/lava/dispatcher/lava-dispatcher-slave
index cca8f14..f8b2a5b 100755
--- a/lava/dispatcher/lava-dispatcher-slave
+++ b/lava/dispatcher/lava-dispatcher-slave
@@ -46,6 +46,7 @@ import traceback
import yaml
import zmq
import zmq.auth
+from zmq.utils.strtypes import b, u
# pylint: disable=no-member
# pylint: disable=too-few-public-methods
@@ -151,12 +152,11 @@ class Job(object):
"""Start the process."""
out_file = os.path.join(self.base_dir, "out")
err_file = os.path.join(self.base_dir, "err")
- env_dut_tmp_path = None
+ env_dut = os.path.join(self.base_dir, "env.dut.yaml")
# Dump the environment variables in the tmp file.
if self.env_dut:
- env_dut_file_handle, env_dut_tmp_path = tempfile.mkstemp()
- with os.fdopen(env_dut_file_handle, 'wb') as f:
+ with open(env_dut, 'w') as f:
f.write(self.env_dut)
try:
@@ -176,8 +176,8 @@ class Job(object):
args.extend(["--master-cert", self.master_cert,
"--slave-cert", self.slave_cert])
- if self.env_dut and env_dut_tmp_path:
- args.append("--env-dut-path=%s" % env_dut_tmp_path)
+ if self.env_dut:
+ args.append("--env-dut-path=%s" % env_dut)
self.proc = subprocess.Popen(
args,
@@ -217,6 +217,15 @@ def get_fqdn():
raise exc
+def send_multipart_u(sock, data):
+ """ Wrapper around send_multipart that encode data as bytes.
+
+ :param sock: The socket to use
+ :param data: Data to convert to byte strings
+ """
+ return sock.send_multipart([b(d) for d in data])
+
+
def create_zmq_context(master_uri, hostname, send_queue, encrypt,
master_cert, slave_cert):
"""Create the ZMQ context and necessary accessories.
@@ -237,7 +246,7 @@ def create_zmq_context(master_uri, hostname, send_queue, encrypt,
# Connect to the master dispatcher.
context = zmq.Context()
sock = context.socket(zmq.DEALER)
- sock.setsockopt(zmq.IDENTITY, hostname)
+ sock.setsockopt(zmq.IDENTITY, b(hostname))
sock.setsockopt(zmq.SNDHWM, send_queue)
# If needed, load certificates
@@ -266,16 +275,21 @@ def create_zmq_context(master_uri, hostname, send_queue, encrypt,
# Mask signals and create a pipe that will receive a bit for each signal
# received. Poll the pipe along with the zmq socket so that we can only be
# interrupted while reading data.
- (read_pipe, write_pipe) = os.pipe()
- flags = fcntl.fcntl(write_pipe, fcntl.F_GETFL, 0) | os.O_NONBLOCK
- fcntl.fcntl(write_pipe, fcntl.F_SETFL, flags)
- signal.set_wakeup_fd(write_pipe)
- signal.signal(signal.SIGINT, lambda x, y: None)
- signal.signal(signal.SIGTERM, lambda x, y: None)
- signal.signal(signal.SIGQUIT, lambda x, y: None)
- poller.register(read_pipe, zmq.POLLIN)
+ (pipe_r, pipe_w) = os.pipe()
+ flags = fcntl.fcntl(pipe_w, fcntl.F_GETFL, 0)
+ fcntl.fcntl(pipe_w, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+ def signal_to_pipe(signum, frame):
+ # Send the signal number on the pipe
+ os.write(pipe_w, chr(signum))
- return context, sock, poller, read_pipe, write_pipe
+ signal.signal(signal.SIGHUP, signal_to_pipe)
+ signal.signal(signal.SIGINT, signal_to_pipe)
+ signal.signal(signal.SIGTERM, signal_to_pipe)
+ signal.signal(signal.SIGQUIT, signal_to_pipe)
+ poller.register(pipe_r, zmq.POLLIN)
+
+ return context, sock, poller, pipe_r, pipe_w
def destroy_zmq_context(context, sock, read_pipe, write_pipe):
@@ -348,7 +362,7 @@ def connect_to_master(master, poller, pipe_r, sock, timeout):
msg = sock.recv_multipart()
try:
- message = msg[0]
+ message = u(msg[0])
LOG.debug("The master replied: %s", msg)
except (IndexError, TypeError):
LOG.error("Invalid message from the master: %s", msg)
@@ -362,7 +376,7 @@ def connect_to_master(master, poller, pipe_r, sock, timeout):
LOG.info("Unexpected message from the master: %s", message)
LOG.debug("Sending new %s message to the master", retry_msg)
- sock.send_multipart([retry_msg])
+ send_multipart_u(sock, [retry_msg])
return False
@@ -386,15 +400,28 @@ def listen_to_master(master, jobs, poller, pipe_r, socket_addr, master_cert,
return
if sockets.get(pipe_r) == zmq.POLLIN:
- LOG.info("Received a signal, leaving")
- sys.exit(0)
+ signum = ord(os.read(pipe_r, 1))
+ if signum == signal.SIGHUP:
+ LOG.info("SIGHUP received, restarting loggers")
+ handler = LOG.handlers[0]
+ if isinstance(handler, logging.FileHandler):
+ # Keep the filename and remove the handler
+ log_file = handler.baseFilename
+ LOG.removeHandler(handler)
+ # Re-create the handler
+ handler = logging.FileHandler(log_file, "a")
+ handler.setFormatter(logging.Formatter(FORMAT))
+ LOG.addHandler(handler)
+ else:
+ LOG.info("Received a signal, leaving")
+ sys.exit(0)
if sockets.get(sock) == zmq.POLLIN:
msg = sock.recv_multipart()
# 1: the action
try:
- action = msg[0]
+ action = u(msg[0])
except (IndexError, TypeError):
LOG.error("Invalid message from the master: %s", msg)
return
@@ -414,10 +441,10 @@ def listen_to_master(master, jobs, poller, pipe_r, socket_addr, master_cert,
elif action == "START":
try:
job_id = int(msg[1])
- job_definition = msg[2]
- device_definition = msg[3]
- env = msg[4]
- env_dut = msg[5] if len(msg) == 6 else None
+ job_definition = u(msg[2])
+ device_definition = u(msg[3])
+ env = u(msg[4])
+ env_dut = u(msg[5]) if len(msg) == 6 else None
except (IndexError, ValueError) as exc:
LOG.error("Invalid message '%s'. length=%d. %s", msg, len(msg), exc)
return
@@ -435,16 +462,16 @@ def listen_to_master(master, jobs, poller, pipe_r, socket_addr, master_cert,
if jobs[job_id].is_running:
LOG.info(
"[%d] Job has already been started", job_id)
- sock.send_multipart(["START_OK", str(job_id)])
+ send_multipart_u(sock, ["START_OK", str(job_id)])
else:
LOG.warning("[%d] Job has already ended", job_id)
- sock.send_multipart(["END", str(job_id), "0"])
+ send_multipart_u(sock, ["END", str(job_id), "0"])
else:
jobs[job_id] = Job(job_id, job_definition, device_definition,
env, socket_addr, master_cert, slave_cert,
env_dut=env_dut)
jobs[job_id].start()
- sock.send_multipart(["START_OK", str(job_id)])
+ send_multipart_u(sock, ["START_OK", str(job_id)])
# Mark the master as alive
master.received_msg()
@@ -471,7 +498,7 @@ def listen_to_master(master, jobs, poller, pipe_r, socket_addr, master_cert,
jobs[job_id] = Job(job_id, "", "", None, None, None, None)
jobs[job_id].is_running = False
# Send the END message anyway
- sock.send_multipart(["END", str(job_id), "0"])
+ send_multipart_u(sock, ["END", str(job_id), "0"])
# Mark the master as alive
master.received_msg()
@@ -501,17 +528,17 @@ def listen_to_master(master, jobs, poller, pipe_r, socket_addr, master_cert,
if job_id in jobs:
if jobs[job_id].is_running:
# The job is still running
- sock.send_multipart(["START_OK", str(job_id)])
+ send_multipart_u(sock, ["START_OK", str(job_id)])
else:
# The job has already ended
- sock.send_multipart(["END", str(job_id), "0"])
+ send_multipart_u(sock, ["END", str(job_id), "0"])
else:
# Unknown job: return END anyway
LOG.debug(
"[%d] Unknown job, sending END after STATUS", job_id)
jobs[job_id] = Job(job_id, "", "", None, None, None, None)
jobs[job_id].is_running = False
- sock.send_multipart(["END", str(job_id), "0"])
+ send_multipart_u(sock, ["END", str(job_id), "0"])
# Mark the master as alive
master.received_msg()
@@ -540,10 +567,10 @@ def check_job_status(jobs, sock):
LOG.info("[%d] Job returned non-zero", job_id)
errs = jobs[job_id].log_errors()
if errs:
- sock.send_multipart(["ERROR", str(job_id), str(errs)])
+ send_multipart_u(sock, ["ERROR", str(job_id), str(errs)])
jobs[job_id].is_running = False
- sock.send_multipart(["END", str(job_id), str(job_status)])
+ send_multipart_u(sock, ["END", str(job_id), str(job_status)])
def ping_master(master, sock, timeout):
@@ -566,7 +593,7 @@ def ping_master(master, sock, timeout):
"Sending PING to the master (last message %ss ago)",
int(now - master.last_msg))
- sock.send_multipart(["PING"])
+ send_multipart_u(sock, ["PING"])
master.last_ping = now
@@ -644,7 +671,7 @@ def main():
hello_msg = "HELLO"
LOG.debug("Greeting the master => '%s'", hello_msg)
- sock.send_multipart([hello_msg])
+ send_multipart_u(sock, [hello_msg])
while not connect_to_master(master, poller, pipe_r, sock, timeout):
pass
diff --git a/lava_dispatcher/actions/lava_test_shell.py b/lava_dispatcher/actions/lava_test_shell.py
index 6f8820c..b511794 100644
--- a/lava_dispatcher/actions/lava_test_shell.py
+++ b/lava_dispatcher/actions/lava_test_shell.py
@@ -1174,8 +1174,11 @@ class cmd_lava_test_shell(BaseAction):
logging.warning(
"Ignoring malformed parameter for signal: \"%s\". " % param)
- test_result = parse_testcase_result(data)
- self._current_test_run['test_results'].append(test_result)
+ if self._current_test_run is not None:
+ test_result = parse_testcase_result(data)
+ self._current_test_run['test_results'].append(test_result)
+ else:
+ logging.warning("LAVA Missed the testrun call, so can't perform testcase.")
def _handle_parsed_testcase(self, data):
test_result = parse_testcase_result(data,
diff --git a/lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf b/lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf
index 068e4e0..84b178a 100644
--- a/lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf
+++ b/lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf
@@ -39,6 +39,12 @@ boot_cmds_oe =
# XXX should be called # boot_oe_test_image_commands ?
boot_cmds_master =
+# The bootloader commands to boot the device into an lede-based test
+# image.
+#
+# XXX should be called # boot_lede_test_image_commands ?
+boot_cmds_lede =
+
# The device type. Settings in device-types/${DEVICE_TYPE}.conf
# override settings in this file, but are overridden by the
# devices/${DEVICE}.conf file.
diff --git a/lava_dispatcher/deployment_data.py b/lava_dispatcher/deployment_data.py
index d81503e..1ba3fe1 100644
--- a/lava_dispatcher/deployment_data.py
+++ b/lava_dispatcher/deployment_data.py
@@ -116,6 +116,19 @@ oe = deployment_data_dict({
'lava_test_results_dir': '/lava-%s',
})
+lede = deployment_data_dict({
+ 'TESTER_PS1': "linaro-test [rc=$(echo \$?)]# ",
+ 'TESTER_PS1_PATTERN': "linaro-test \[rc=(\d+)\]# ",
+ 'TESTER_PS1_INCLUDES_RC': True,
+ 'boot_cmds': 'boot_cmds_lede',
+
+ # for lava-test-shell
+ 'distro': 'lede',
+ 'lava_test_sh_cmd': '/bin/sh',
+ 'lava_test_dir': '/lava-%s',
+ 'lava_test_results_part_attr': 'root_part',
+ 'lava_test_results_dir': '/lava-%s',
+})
fedora = deployment_data_dict({
'TESTER_PS1': "linaro-test [rc=$(echo \$?)]# ",
diff --git a/lava_dispatcher/lava_test_shell/vland/lava-vland-names b/lava_dispatcher/lava_test_shell/vland/lava-vland-names
new file mode 100644
index 0000000..8a34059
--- /dev/null
+++ b/lava_dispatcher/lava_test_shell/vland/lava-vland-names
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# This file is for Vland test
+#
+# Prints the vlan names for this device
+#
+# Usage: ``lava-vland-names``
+
+echo -e ${LAVA_VLAND_NAMES}
diff --git a/lava_dispatcher/pipeline/action.py b/lava_dispatcher/pipeline/action.py
index ac0a434..83d3ca6 100644
--- a/lava_dispatcher/pipeline/action.py
+++ b/lava_dispatcher/pipeline/action.py
@@ -20,7 +20,6 @@
import re
import logging
-import os
import sys
import copy
import time
@@ -154,16 +153,6 @@ class Pipeline(object): # pylint: disable=too-many-instance-attributes
action.section = self.parent.section
else:
action.level = "%s" % (len(self.actions))
- # create a log handler just for this action.
- if self.job and self.job.parameters['output_dir']:
- log_level_dir = action.level.split('.')[0]
- yaml_filename = os.path.join(
- self.job.parameters['output_dir'], log_level_dir,
- "%s-%s.log" % (action.level, action.name)
- )
- if not os.path.exists(os.path.dirname(yaml_filename)):
- os.makedirs(os.path.dirname(yaml_filename))
- action.log_filename = yaml_filename
# Use the pipeline parameters if the function was walled without
# parameters.
@@ -323,7 +312,7 @@ class Pipeline(object): # pylint: disable=too-many-instance-attributes
msg = "Job '%s' timed out after %s seconds" % (name, int(self.job.timeout.duration))
action.logger.error(msg)
action.errors = msg
- final = self.actions[-1]
+ final = self.job.pipeline.actions[-1]
if final.name == "finalize":
final.run(connection, None)
else:
@@ -431,7 +420,6 @@ class Action(object): # pylint: disable=too-many-instance-attributes
self.__parameters__ = {}
self.__errors__ = []
self.elapsed_time = None
- self.log_filename = None
self.job = None
self.logger = logging.getLogger('dispatcher')
self.__results__ = OrderedDict()
@@ -659,6 +647,7 @@ class Action(object): # pylint: disable=too-many-instance-attributes
log = None
# nice is assumed to always exist (coreutils)
command_list.insert(0, 'nice')
+ self.logger.info("%s", ' '.join(command_list))
try:
log = subprocess.check_output(command_list, stderr=subprocess.STDOUT)
log = log.decode('utf-8')
@@ -668,10 +657,12 @@ class Action(object): # pylint: disable=too-many-instance-attributes
self.errors = exc.output.strip().decode('utf-8')
else:
self.errors = str(exc)
- self.logger.exception({
- 'command': [i.strip() for i in exc.cmd],
- 'message': str(exc),
- 'output': str(exc).split('\n')})
+ self.logger.exception(
+ '[%s] command %s\nmessage %s\noutput %s\n',
+ self.name,
+ [i.strip() for i in exc.cmd],
+ str(exc),
+ str(exc).split('\n'))
else:
if exc.output:
self.errors = exc.output.strip()
@@ -679,18 +670,18 @@ class Action(object): # pylint: disable=too-many-instance-attributes
self.errors = exc.message
else:
self.errors = str(exc)
- self.logger.exception({
- 'command': [i.strip() for i in exc.cmd],
- 'message': [i.strip() for i in exc.message],
- 'output': exc.output.split('\n')})
+ self.logger.exception(
+ "[%s] command %s\nmessage %s\noutput %s\nexit code %s",
+ self.name,
+ [i.strip() for i in exc.cmd],
+ [i.strip() for i in exc.message],
+ exc.output.split('\n'), exc.returncode)
# allow for commands which return no output
if not log and allow_silent:
- self.logger.debug({'command': command_list})
return self.errors == []
else:
- self.logger.debug({'command': command_list,
- 'output': log})
+ self.logger.debug('command output %s', log)
return log
def call_protocols(self):
@@ -776,9 +767,12 @@ class Action(object): # pylint: disable=too-many-instance-attributes
# noinspection PySetFunctionToLiteral
for attr in attrs - set([
'internal_pipeline', 'job', 'logger', 'pipeline',
+ 'default_fixupdict', 'pattern',
'parameters', 'SignalDirector', 'signal_director']):
if attr == 'timeout':
data['timeout'] = {'duration': self.timeout.duration, 'name': self.timeout.name}
+ elif attr == 'connection_timeout':
+ data['timeout'] = {'duration': self.timeout.duration, 'name': self.timeout.name}
elif attr == 'url':
data['url'] = self.url.geturl() # pylint: disable=no-member
elif attr == 'vcs':
@@ -794,6 +788,8 @@ class Action(object): # pylint: disable=too-many-instance-attributes
for protocol_attr in protocol_attrs:
if protocol_attr not in ['logger']:
data['protocols'][protocol.name][protocol_attr] = getattr(protocol, protocol_attr)
+ elif isinstance(getattr(self, attr), OrderedDict):
+ data[attr] = dict(getattr(self, attr))
else:
data[attr] = getattr(self, attr)
if 'deployment_data' in self.parameters:
@@ -829,8 +825,8 @@ class Action(object): # pylint: disable=too-many-instance-attributes
if not connection.connected:
self.logger.debug("Already disconnected")
return
- self.logger.debug("%s: Wait for prompt. %s seconds",
- self.name, int(self.connection_timeout.duration))
+ self.logger.debug("%s: Wait for prompt %s. %s seconds",
+ self.name, connection.prompt_str, int(self.connection_timeout.duration))
return connection.wait()
diff --git a/lava_dispatcher/pipeline/actions/boot/environment.py b/lava_dispatcher/pipeline/actions/boot/environment.py
index 5581e84..a56f915 100644
--- a/lava_dispatcher/pipeline/actions/boot/environment.py
+++ b/lava_dispatcher/pipeline/actions/boot/environment.py
@@ -55,6 +55,8 @@ class ExportDeviceEnvironment(Action):
for line in self.env:
connection.sendline(line, delay=self.character_delay)
- connection.sendline('. %s' % shell_file, delay=self.character_delay)
+
+ if shell_file:
+ connection.sendline('. %s' % shell_file, delay=self.character_delay)
return connection
diff --git a/lava_dispatcher/pipeline/actions/boot/fastboot.py b/lava_dispatcher/pipeline/actions/boot/fastboot.py
index 933ebe1..791b747 100644
--- a/lava_dispatcher/pipeline/actions/boot/fastboot.py
+++ b/lava_dispatcher/pipeline/actions/boot/fastboot.py
@@ -117,15 +117,19 @@ class WaitForAdbDevice(Action):
self.prompts = []
def validate(self):
- super(WaitForAdbDevice, self).validate()
- if 'adb_serial_number' not in self.job.device:
- self.errors = "device adb serial number missing"
+ if 'adb_serial_number' in self.job.device:
if self.job.device['adb_serial_number'] == '0000000000':
self.errors = "device adb serial number unset"
+ super(WaitForAdbDevice, self).validate()
def run(self, connection, args=None):
+ if 'lxc' not in self.job.device['actions']['boot']['methods']:
+ return connection
connection = super(WaitForAdbDevice, self).run(connection, args)
lxc_name = self.get_common_data('lxc', 'name')
+ if not lxc_name:
+ self.logger.debug("No LXC device requested")
+ return connection
serial_number = self.job.device['adb_serial_number']
adb_cmd = ['lxc-attach', '-n', lxc_name, '--', 'adb', 'start-server']
self.logger.debug("Starting adb daemon")
diff --git a/lava_dispatcher/pipeline/actions/boot/lxc.py b/lava_dispatcher/pipeline/actions/boot/lxc.py
index 7166ad6..116ff4d 100644
--- a/lava_dispatcher/pipeline/actions/boot/lxc.py
+++ b/lava_dispatcher/pipeline/actions/boot/lxc.py
@@ -72,9 +72,8 @@ class BootLxcAction(BootAction):
self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
self.internal_pipeline.add_action(LxcStartAction())
self.internal_pipeline.add_action(ConnectLxc())
- # Add AutoLoginAction unconditionally as this action does nothing if
- # the configuration does not contain 'auto_login'
- self.internal_pipeline.add_action(AutoLoginAction())
+ # Skip AutoLoginAction unconditionally as this action tries to parse kernel message
+ # self.internal_pipeline.add_action(AutoLoginAction())
self.internal_pipeline.add_action(ExpectShellSession())
self.internal_pipeline.add_action(ExportDeviceEnvironment())
diff --git a/lava_dispatcher/pipeline/actions/boot/qemu.py b/lava_dispatcher/pipeline/actions/boot/qemu.py
index 1e80a24..144a78c 100644
--- a/lava_dispatcher/pipeline/actions/boot/qemu.py
+++ b/lava_dispatcher/pipeline/actions/boot/qemu.py
@@ -65,9 +65,46 @@ class BootQEMU(Boot):
return False
if parameters['method'] != 'qemu':
return False
- if device['device_type'] == 'qemu':
- return True
- return False
+ if device['device_type'] != 'qemu':
+ return False
+ if parameters['method'] == 'monitor':
+ return False
+ return True
+
+
+class BootMonitorQemu(Boot):
+
+ compatibility = 4 # FIXME: change this to 5 and update test cases
+
+ def __init__(self, parent, parameters):
+ super(BootMonitorQemu, self).__init__(parent)
+ self.action = BootMonitoredQemu()
+ self.action.section = self.action_type
+ self.action.job = self.job
+ parent.add_action(self.action, parameters)
+
+ @classmethod
+ def accepts(cls, device, parameters):
+ if 'method' not in parameters:
+ return False
+ if device['device_type'] != 'qemu':
+ return False
+ if parameters['method'] != 'monitor':
+ return False
+ return True
+
+
+class BootMonitoredQemu(BootAction):
+
+ def __init__(self):
+ super(BootMonitoredQemu, self).__init__()
+ self.name = 'boot_image_monitor'
+ self.description = "boot monitored image with retry"
+ self.summary = "boot monitor with retry"
+
+ def populate(self, parameters):
+ self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
+ self.internal_pipeline.add_action(BootQemuRetry())
class BootQEMUImageAction(BootAction):
@@ -102,6 +139,71 @@ class BootQemuRetry(RetryAction):
self.internal_pipeline.add_action(CallQemuAction())
+class MonitorQemuAction(Action):
+
+ def __init__(self):
+ super(MonitorQemuAction, self).__init__()
+ self.name = "monitor-qemu"
+ self.description = "monitor qemu to boot the image"
+ self.summary = "monitor qemu to boot the image"
+ self.sub_command = []
+
+ def validate(self):
+ super(MonitorQemuAction, self).validate()
+ try:
+ boot = self.job.device['actions']['boot']['methods']['qemu']
+ qemu_binary = which(boot['parameters']['command'])
+ self.sub_command = [qemu_binary]
+ self.sub_command.extend(boot['parameters'].get('options', []))
+ except AttributeError as exc:
+ raise InfrastructureError(exc)
+ except (KeyError, TypeError):
+ self.errors = "Invalid parameters for %s" % self.name
+ substitutions = {}
+ commands = []
+ for action in self.data['download_action'].keys():
+ if action == 'offset' or action == 'available_loops' or action == 'uefi':
+ continue
+ image_arg = self.data['download_action'][action].get('image_arg', None)
+ action_arg = self.data['download_action'][action].get('file', None)
+ if not image_arg or not action_arg:
+ self.errors = "Missing image_arg for %s. " % action
+ continue
+ substitutions["{%s}" % action] = action_arg
+ commands.append(image_arg)
+ self.sub_command.extend(substitute(commands, substitutions))
+ if not self.sub_command:
+ self.errors = "No QEMU command to execute"
+
+ def run(self, connection, args=None):
+ """
+ CommandRunner expects a pexpect.spawn connection which is the return value
+ of target.device.power_on executed by boot in the old dispatcher.
+
+ In the new pipeline, the pexpect.spawn is a ShellCommand and the
+ connection is a ShellSession. CommandRunner inside the ShellSession
+ turns the ShellCommand into a runner which the ShellSession uses via ShellSession.run()
+ to run commands issued *after* the device has booted.
+ pexpect.spawn is one of the raw_connection objects for a Connection class.
+ """
+ # initialise the first Connection object, a command line shell into the running QEMU.
+ self.logger.info("Boot command: %s", ' '.join(self.sub_command))
+ shell = ShellCommand(' '.join(self.sub_command), self.timeout, logger=self.logger)
+ if shell.exitstatus:
+ raise JobError("%s command exited %d: %s" % (self.sub_command, shell.exitstatus, shell.readlines()))
+ self.logger.debug("started a shell command")
+
+ shell_connection = ShellSession(self.job, shell)
+ shell_connection = super(MonitorQemuAction, self).run(shell_connection, args)
+
+ # FIXME: the shell needs to wait for something
+
+ # FIXME: tests with multiple boots need to be handled too.
+ self.data['boot-result'] = 'failed' if self.errors else 'success'
+ # FIXME: from here, go into the new test action.
+ return shell_connection
+
+
class CallQemuAction(Action):
def __init__(self):
@@ -113,7 +215,7 @@ class CallQemuAction(Action):
def validate(self):
super(CallQemuAction, self).validate()
- if 'prompts' not in self.parameters:
+ if self.parameters['method'] == 'qemu' and 'prompts' not in self.parameters:
self.errors = "Unable to identify boot prompts from job definition."
try:
boot = self.job.device['actions']['boot']['methods']['qemu']
@@ -179,7 +281,7 @@ class CallQemuAction(Action):
self.logger.debug("started a shell command")
shell_connection = ShellSession(self.job, shell)
- if not shell_connection.prompt_str:
+ if not shell_connection.prompt_str and self.parameters['method'] == 'qemu':
shell_connection.prompt_str = self.parameters['prompts']
shell_connection = super(CallQemuAction, self).run(shell_connection, args)
diff --git a/lava_dispatcher/pipeline/actions/boot/strategies.py b/lava_dispatcher/pipeline/actions/boot/strategies.py
index 79b5754..cdc0bcb 100644
--- a/lava_dispatcher/pipeline/actions/boot/strategies.py
+++ b/lava_dispatcher/pipeline/actions/boot/strategies.py
@@ -23,7 +23,7 @@
# pylint: disable=unused-import
-from lava_dispatcher.pipeline.actions.boot.qemu import BootQEMU
+from lava_dispatcher.pipeline.actions.boot.qemu import BootQEMU, BootMonitorQemu
from lava_dispatcher.pipeline.actions.boot.u_boot import UBoot
from lava_dispatcher.pipeline.actions.boot.kexec import BootKExec
from lava_dispatcher.pipeline.actions.boot.ssh import SshLogin, Schroot
diff --git a/lava_dispatcher/pipeline/actions/boot/uefi_menu.py b/lava_dispatcher/pipeline/actions/boot/uefi_menu.py
index 56901a0..56d2ffa 100644
--- a/lava_dispatcher/pipeline/actions/boot/uefi_menu.py
+++ b/lava_dispatcher/pipeline/actions/boot/uefi_menu.py
@@ -31,11 +31,14 @@ from lava_dispatcher.pipeline.menus.menus import (
MenuReset
)
from lava_dispatcher.pipeline.logical import Boot
-from lava_dispatcher.pipeline.power import ResetDevice
+from lava_dispatcher.pipeline.power import ResetDevice, FastBootRebootAction
from lava_dispatcher.pipeline.utils.strings import substitute
from lava_dispatcher.pipeline.utils.network import dispatcher_ip
from lava_dispatcher.pipeline.actions.boot import BootAction, AutoLoginAction
from lava_dispatcher.pipeline.actions.boot.environment import ExportDeviceEnvironment
+from lava_dispatcher.pipeline.connections.lxc import ConnectLxc
+from lava_dispatcher.pipeline.actions.boot.fastboot import WaitForAdbDevice
+from lava_dispatcher.pipeline.utils.constants import DEFAULT_UEFI_LABEL_CLASS
class UefiMenu(Boot):
@@ -110,8 +113,7 @@ class UefiMenuSelector(SelectorMenuAction):
"""
params = self.job.device['actions']['boot']['methods']['uefi-menu']['parameters']
if ('item_markup' not in params or
- 'item_class' not in params or 'separator' not in params or
- 'label_class' not in params):
+ 'item_class' not in params or 'separator' not in params):
self.errors = "Missing device parameters for UEFI menu operations"
if 'commands' not in self.parameters:
self.errors = "Missing commands in action parameters"
@@ -121,13 +123,22 @@ class UefiMenuSelector(SelectorMenuAction):
self.selector.item_markup = params['item_markup']
self.selector.item_class = params['item_class']
self.selector.separator = params['separator']
- self.selector.label_class = params['label_class']
+ if 'label_class' in params:
+ self.selector.label_class = params['label_class']
+ else:
+ # label_class is problematic via jinja and yaml templating.
+ self.selector.label_class = DEFAULT_UEFI_LABEL_CLASS
self.selector.prompt = params['bootloader_prompt'] # initial prompt
self.boot_message = params['boot_message'] # final prompt
self.items = self.job.device['actions']['boot']['methods']['uefi-menu'][self.parameters['commands']]
super(UefiMenuSelector, self).validate()
def run(self, connection, args=None):
+ if self.job.device.pre_os_command:
+ self.logger.info("Running pre OS command.")
+ command = self.job.device.pre_os_command
+ if not self.run_command(command.split(' '), allow_silent=True):
+ raise InfrastructureError("%s failed" % command)
if not connection:
return connection
connection.prompt_str = self.selector.prompt
@@ -201,11 +212,23 @@ class UefiMenuAction(BootAction):
def populate(self, parameters):
self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
- self.internal_pipeline.add_action(UefiSubstituteCommands())
- self.internal_pipeline.add_action(MenuConnect())
- self.internal_pipeline.add_action(ResetDevice())
- self.internal_pipeline.add_action(UEFIMenuInterrupt())
- self.internal_pipeline.add_action(UefiMenuSelector())
- self.internal_pipeline.add_action(MenuReset())
- self.internal_pipeline.add_action(AutoLoginAction())
- self.internal_pipeline.add_action(ExportDeviceEnvironment())
+ if 'commands' in parameters and 'fastboot' in parameters['commands']:
+ self.internal_pipeline.add_action(UefiSubstituteCommands())
+ self.internal_pipeline.add_action(MenuConnect())
+ self.internal_pipeline.add_action(FastBootRebootAction())
+ self.internal_pipeline.add_action(UEFIMenuInterrupt())
+ self.internal_pipeline.add_action(UefiMenuSelector())
+ self.internal_pipeline.add_action(MenuReset())
+ self.internal_pipeline.add_action(ConnectLxc())
+ self.internal_pipeline.add_action(WaitForAdbDevice())
+ else:
+ self.internal_pipeline.add_action(UefiSubstituteCommands())
+ self.internal_pipeline.add_action(MenuConnect())
+ self.internal_pipeline.add_action(ResetDevice())
+ self.internal_pipeline.add_action(UEFIMenuInterrupt())
+ self.internal_pipeline.add_action(UefiMenuSelector())
+ self.internal_pipeline.add_action(MenuReset())
+ self.internal_pipeline.add_action(AutoLoginAction())
+ self.internal_pipeline.add_action(ExportDeviceEnvironment())
+ self.internal_pipeline.add_action(ConnectLxc())
+ self.internal_pipeline.add_action(WaitForAdbDevice())
diff --git a/lava_dispatcher/pipeline/actions/deploy/apply_overlay.py b/lava_dispatcher/pipeline/actions/deploy/apply_overlay.py
index 4a4e491..7a9f6e2 100644
--- a/lava_dispatcher/pipeline/actions/deploy/apply_overlay.py
+++ b/lava_dispatcher/pipeline/actions/deploy/apply_overlay.py
@@ -184,7 +184,6 @@ class ApplyOverlayTftp(Action):
shutil.copy(overlay_file, tftp_dir)
suffix = self.data['tftp-deploy'].get('suffix', '')
self.set_common_data('file', 'overlay', os.path.join(suffix, os.path.basename(overlay_file)))
- untar_file(overlay_file, directory)
if nfs_url:
subprocess.check_output(['umount', directory])
os.rmdir(directory) # fails if the umount fails
@@ -362,7 +361,6 @@ class ExtractRamdisk(Action):
else:
# give the file a predictable name
shutil.move(ramdisk, ramdisk_compressed_data)
- self.logger.debug(os.system("file %s" % ramdisk_compressed_data))
ramdisk_data = decompress_file(ramdisk_compressed_data, compression)
pwd = os.getcwd()
os.chdir(extracted_ramdisk)
diff --git a/lava_dispatcher/pipeline/actions/deploy/download.py b/lava_dispatcher/pipeline/actions/deploy/download.py
index a67c9f7..76037f8 100644
--- a/lava_dispatcher/pipeline/actions/deploy/download.py
+++ b/lava_dispatcher/pipeline/actions/deploy/download.py
@@ -152,14 +152,8 @@ class DownloadHandler(Action): # pylint: disable=too-many-instance-attributes
compression = self.parameters[self.key].get('compression', False)
fname, _ = self._url_to_fname_suffix(self.path, compression)
-
if os.path.exists(fname):
- nested_tmp_dir = os.path.join(self.path, self.key)
- if os.path.exists(nested_tmp_dir):
- self.logger.warning("Cleaning up existing directory: %s", nested_tmp_dir)
- shutil.rmtree(nested_tmp_dir)
- os.makedirs(nested_tmp_dir)
- fname = os.path.join(nested_tmp_dir, os.path.basename(fname))
+ os.remove(fname)
decompressor = None
if compression:
@@ -177,8 +171,8 @@ class DownloadHandler(Action): # pylint: disable=too-many-instance-attributes
if decompressor:
try:
buff = decompressor.decompress(buff)
- except zlib.error as exc:
- self.logger.exception(exc)
+ except (IOError, lzma.error, zlib.error) as exc:
+ self.logger.exception(str(exc))
raise JobError(exc)
dwnld_file.write(buff)
@@ -366,9 +360,11 @@ class HttpDownloadAction(DownloadHandler):
def validate(self):
super(HttpDownloadAction, self).validate()
try:
+ self.logger.debug("Validating that %s exists", self.url.geturl())
res = requests.head(self.url.geturl(), allow_redirects=True, timeout=HTTP_DOWNLOAD_TIMEOUT)
if res.status_code != requests.codes.OK: # pylint: disable=no-member
# try using (the slower) get for services with broken redirect support
+ self.logger.debug("Using GET because HEAD is not supported properly")
res = requests.get(
self.url.geturl(), allow_redirects=True, stream=True,
timeout=HTTP_DOWNLOAD_TIMEOUT)
diff --git a/lava_dispatcher/pipeline/actions/deploy/fastboot.py b/lava_dispatcher/pipeline/actions/deploy/fastboot.py
index 07fdcb4..dc0bf59 100644
--- a/lava_dispatcher/pipeline/actions/deploy/fastboot.py
+++ b/lava_dispatcher/pipeline/actions/deploy/fastboot.py
@@ -20,7 +20,7 @@
from lava_dispatcher.pipeline.logical import Deployment
from lava_dispatcher.pipeline.connections.serial import ConnectDevice
-from lava_dispatcher.pipeline.power import ResetDevice
+from lava_dispatcher.pipeline.power import FastBootRebootAction, PowerOn
from lava_dispatcher.pipeline.action import (
Pipeline,
JobError,
@@ -119,7 +119,7 @@ class FastbootAction(DeployAction): # pylint:disable=too-many-instance-attribut
if hasattr(self.job.device, 'power_state'):
if self.job.device.power_state in ['on', 'off']:
self.internal_pipeline.add_action(ConnectDevice())
- self.internal_pipeline.add_action(ResetDevice())
+ self.internal_pipeline.add_action(PowerOn())
self.internal_pipeline.add_action(EnterFastbootAction())
self.internal_pipeline.add_action(LxcAddDeviceAction())
image_keys = list(parameters['images'].keys())
diff --git a/lava_dispatcher/pipeline/actions/deploy/image.py b/lava_dispatcher/pipeline/actions/deploy/image.py
index 7a1a5a0..8b60765 100644
--- a/lava_dispatcher/pipeline/actions/deploy/image.py
+++ b/lava_dispatcher/pipeline/actions/deploy/image.py
@@ -34,7 +34,7 @@ from lava_dispatcher.pipeline.actions.deploy.overlay import (
from lava_dispatcher.pipeline.utils.filesystem import mkdtemp
-class DeployImagesAction(DeployAction):
+class DeployImagesAction(DeployAction): # FIXME: Rename to DeployPosixImages
def __init__(self):
super(DeployImagesAction, self).__init__()
@@ -52,6 +52,7 @@ class DeployImagesAction(DeployAction):
self.internal_pipeline.add_action(download)
# uefi option of QEMU needs a directory, not the filename
self.set_common_data('image', 'uefi_dir', uefi_path) # just the path, not the filename
+ # alternatively use the -bios option and standard image args
for image in parameters['images'].keys():
if image != 'yaml_line':
download = DownloaderAction(image, path)
@@ -65,7 +66,62 @@ class DeployImagesAction(DeployAction):
self.internal_pipeline.add_action(DeployDeviceEnvironment())
-# FIXME: may need to be renamed if it can only deal with QEMU image deployment
+class DeployMonitoredAction(DeployAction):
+
+ def __init__(self):
+ super(DeployMonitoredAction, self).__init__()
+ self.name = 'deploy-monitor'
+ self.description = "deploy images without POSIX"
+ self.summary = "deploy without requiring POSIX"
+
+ def populate(self, parameters):
+ self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
+ path = mkdtemp()
+ for image in parameters['images'].keys():
+ if image != 'yaml_line':
+ download = DownloaderAction(image, path)
+ download.max_retries = 3 # overridden by failure_retry in the parameters, if set.
+ self.internal_pipeline.add_action(download)
+
+
+class DeployMonitoredQEMU(Deployment):
+ """
+ Strategy class for a QEMU deployment not using
+ the POSIX Lava Test Shell overlays.
+ """
+ compatibility = 4
+
+ def __init__(self, parent, parameters):
+ super(DeployMonitoredQEMU, self).__init__(parent)
+ self.action = DeployMonitoredAction()
+ self.action.section = self.action_type
+ self.action.job = self.job
+ parent.add_action(self.action, parameters)
+
+ @classmethod
+ def accepts(cls, device, parameters):
+ """
+ As a classmethod, this cannot set data
+ in the instance of the class.
+ This is *not* the same as validation of the action
+ which can use instance data.
+ """
+ if device['device_type'] != 'qemu':
+ return False
+ if parameters['to'] != 'tmpfs':
+ return False
+ # lookup if the job parameters match the available device methods
+ if 'images' not in parameters:
+ # python3 compatible
+ # FIXME: too broad
+ print("Parameters %s have not been implemented yet." % list(parameters.keys())) # pylint: disable=superfluous-parens
+ return False
+ if 'type' not in parameters.keys():
+ return False
+ return True
+
+
+# FIXME: needs to be renamed to DeployPosixImages
class DeployImages(Deployment):
"""
Strategy class for an Image based Deployment.
@@ -108,4 +164,6 @@ class DeployImages(Deployment):
# FIXME: too broad
print("Parameters %s have not been implemented yet." % list(parameters.keys())) # pylint: disable=superfluous-parens
return False
+ if 'type' in parameters:
+ return False
return True
diff --git a/lava_dispatcher/pipeline/actions/deploy/lxc.py b/lava_dispatcher/pipeline/actions/deploy/lxc.py
index cdff1cc..b59e0e9 100644
--- a/lava_dispatcher/pipeline/actions/deploy/lxc.py
+++ b/lava_dispatcher/pipeline/actions/deploy/lxc.py
@@ -205,8 +205,8 @@ class LxcAddDeviceAction(Action):
if device_path:
# Wait USB_SHOW_UP_TIMEOUT seconds for usb device to show up
- self.logger.info("Wait %d seconds for usb device to show up",
- USB_SHOW_UP_TIMEOUT)
+ self.logger.info("[%s] Wait %d seconds for usb device to show up",
+ self.name, USB_SHOW_UP_TIMEOUT)
sleep(USB_SHOW_UP_TIMEOUT)
for path in device_path:
@@ -217,10 +217,15 @@ class LxcAddDeviceAction(Action):
devices = [path]
for device in devices:
+ self.logger.debug('adding %s at %s', device, path)
device = os.path.join(path, device)
lxc_cmd = ['lxc-device', '-n', lxc_name, 'add', device]
- self.run_command(lxc_cmd)
+ log = self.run_command(lxc_cmd)
+ self.logger.debug(log)
self.logger.debug("%s: devices added from %s", lxc_name,
path)
else:
- self.logger.debug("device_path is None")
+ self.logger.warning("device_path is None")
+ else:
+ self.logger.error("No device path defined for this device.")
+ return connection
diff --git a/lava_dispatcher/pipeline/actions/deploy/overlay.py b/lava_dispatcher/pipeline/actions/deploy/overlay.py
index 3dfbb84..e4dbad4 100644
--- a/lava_dispatcher/pipeline/actions/deploy/overlay.py
+++ b/lava_dispatcher/pipeline/actions/deploy/overlay.py
@@ -318,6 +318,7 @@ class VlandOverlayAction(OverlayAction):
self.params = {}
self.sysfs = []
self.tags = []
+ self.names = []
self.protocol = VlandProtocol.name
def populate(self, parameters):
@@ -343,20 +344,26 @@ class VlandOverlayAction(OverlayAction):
# same as the parameters of the protocol itself.
self.params = self.job.parameters['protocols'][self.protocol]
device_params = self.job.device['parameters']['interfaces']
+ vprotocol = [vprotocol for vprotocol in self.job.protocols if vprotocol.name == self.protocol][0]
+ # needs to be the configured interface for each vlan.
+ for key, _ in self.params.items():
+ if key == 'yaml_line' or key not in vprotocol.params:
+ continue
+ self.names.append(",".join([key, vprotocol.params[key]['iface']]))
for interface in device_params:
- self.sysfs.extend(
+ self.sysfs.append(",".join(
[
- device_params[interface]['sysfs'],
+ interface,
device_params[interface]['mac'],
- interface
- ]
+ device_params[interface]['sysfs'],
+ ])
)
for interface in device_params:
if not device_params[interface]['tags']:
# skip primary interface
continue
for tag in device_params[interface]['tags']:
- self.tags.extend([interface, tag])
+ self.tags.append(",".join([interface, tag]))
# pylint: disable=anomalous-backslash-in-string
def run(self, connection, args=None):
@@ -405,10 +412,17 @@ class VlandOverlayAction(OverlayAction):
fout.write(r'LAVA_VLAND_SELF="')
for line in self.sysfs:
fout.write(r"%s\n" % line)
+ elif foutname == 'lava-vland-names':
+ fout.write(r'LAVA_VLAND_NAMES="')
+ for line in self.names:
+ fout.write(r"%s\n" % line)
elif foutname == 'lava-vland-tags':
fout.write(r'LAVA_VLAND_TAGS="')
- for line in self.tags:
- fout.write(r"%s\n" % line)
+ if not self.tags:
+ fout.write(r"\n")
+ else:
+ for line in self.tags:
+ fout.write(r"%s\n" % line)
fout.write('"\n\n')
fout.write(fin.read())
os.fchmod(fout.fileno(), self.xmod)
diff --git a/lava_dispatcher/pipeline/actions/deploy/strategies.py b/lava_dispatcher/pipeline/actions/deploy/strategies.py
index 49e2b4c..082a7d6 100644
--- a/lava_dispatcher/pipeline/actions/deploy/strategies.py
+++ b/lava_dispatcher/pipeline/actions/deploy/strategies.py
@@ -23,7 +23,7 @@
# pylint: disable=unused-import
-from lava_dispatcher.pipeline.actions.deploy.image import DeployImages
+from lava_dispatcher.pipeline.actions.deploy.image import DeployImages, DeployMonitoredQEMU
from lava_dispatcher.pipeline.actions.deploy.tftp import Tftp
from lava_dispatcher.pipeline.actions.deploy.removable import MassStorage
from lava_dispatcher.pipeline.actions.deploy.ssh import Ssh
diff --git a/lava_dispatcher/pipeline/actions/deploy/testdef.py b/lava_dispatcher/pipeline/actions/deploy/testdef.py
index 7bde413..a54542a 100644
--- a/lava_dispatcher/pipeline/actions/deploy/testdef.py
+++ b/lava_dispatcher/pipeline/actions/deploy/testdef.py
@@ -149,8 +149,7 @@ class RepoAction(Action):
" '%s'." % repo_type)
# higher priority first
- willing.sort(key=lambda x: x.priority)
- willing.reverse()
+ willing.sort(key=lambda x: x.priority, reverse=True)
return willing[0]
def validate(self):
diff --git a/lava_dispatcher/pipeline/actions/test/monitor.py b/lava_dispatcher/pipeline/actions/test/monitor.py
new file mode 100644
index 0000000..c8606d7
--- /dev/null
+++ b/lava_dispatcher/pipeline/actions/test/monitor.py
@@ -0,0 +1,175 @@
+# Copyright (C) 2014 Linaro Limited
+#
+# Author: Tyler Baker <tyler.baker@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher 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>.
+
+import pexpect
+
+from lava_dispatcher.pipeline.action import (
+ Pipeline,
+ InfrastructureError,
+)
+from lava_dispatcher.pipeline.actions.test import (
+ TestAction,
+)
+from lava_dispatcher.pipeline.logical import (
+ LavaTest,
+ RetryAction,
+)
+
+
+class TestMonitor(LavaTest):
+ """
+ LavaTestMonitor Strategy object
+ """
+ def __init__(self, parent, parameters):
+ super(TestMonitor, self).__init__(parent)
+ self.action = TestMonitorAction()
+ self.action.job = self.job
+ self.action.section = self.action_type
+ parent.add_action(self.action, parameters)
+
+ @classmethod
+ def accepts(cls, device, parameters):
+ # TODO: Add configurable timeouts
+ required_parms = ['name', 'start',
+ 'end', 'pattern']
+ if 'monitors' in parameters:
+ for monitor in parameters['monitors']:
+ if all([x for x in required_parms
+ if x in monitor]):
+ return True
+ else:
+ return False
+
+
+class TestMonitorRetry(RetryAction):
+
+ def __init__(self):
+ super(TestMonitorRetry, self).__init__()
+ self.description = "Retry wrapper for lava-test-monitor"
+ self.summary = "Retry support for Lava Test Monitoring"
+ self.name = "lava-test-monitor-retry"
+
+ def populate(self, parameters):
+ self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
+ self.internal_pipeline.add_action(TestMonitorAction())
+
+
+class TestMonitorAction(TestAction):
+ """
+ Sets up and runs the LAVA Test Shell Definition scripts.
+ Supports a pre-command-list of operations necessary on the
+ booted image before the test shell can be started.
+ """
+
+ def __init__(self):
+ super(TestMonitorAction, self).__init__()
+ self.description = "Executing lava-test-monitor"
+ self.summary = "Lava Test Monitor"
+ self.name = "lava-test-monitor"
+ self.test_suite_name = None
+ self.report = {}
+ self.fixupdict = {}
+ self.patterns = {}
+
+ def validate(self):
+ # Extend the list of patterns when creating subclasses.
+ self.patterns.update({
+ "eof": pexpect.EOF,
+ "timeout": pexpect.TIMEOUT,
+ })
+ super(TestMonitorAction, self).validate()
+
+ def run(self, connection, args=None):
+ # Sanity test: could be a missing deployment for some actions
+ if "boot-result" not in self.data:
+ raise RuntimeError("No boot action result found")
+ connection = super(TestMonitorAction, self).run(connection, args)
+ if self.data["boot-result"] != "success":
+ self.logger.debug("Skipping test monitoring - previous boot attempt was not successful.")
+ self.results.update({self.name: "skipped"})
+ # FIXME: with predictable UID, could set each test definition metadata to "skipped"
+ return connection
+
+ if not connection:
+ raise InfrastructureError("Connection closed")
+ for monitor in self.parameters['monitors']:
+ self.test_suite_name = monitor['name']
+
+ self.fixupdict = monitor.get('fixupdict')
+
+ self.patterns.update({
+ "end": monitor['end'],
+ "test_result": monitor['pattern'],
+ })
+
+ # Find the start string before parsing any output.
+ connection.prompt_str = monitor['start']
+ connection.wait()
+ self.logger.info("ok: start string found, lava test monitoring started")
+
+ with connection.test_connection() as test_connection:
+ while self._keep_running(test_connection):
+ pass
+
+ return connection
+
+ def _keep_running(self, test_connection, timeout=120):
+ self.logger.debug("test monitoring timeout: %d seconds" % timeout)
+ retval = test_connection.expect(list(self.patterns.values()), timeout=timeout)
+ return self.check_patterns(list(self.patterns.keys())[retval], test_connection)
+
+ def check_patterns(self, event, test_connection):
+ """
+ Defines the base set of pattern responses.
+ Stores the results of testcases inside the TestAction
+ Call from subclasses before checking subclass-specific events.
+ """
+ ret_val = False
+ results = {}
+ if event == "end":
+ self.logger.info("ok: end string found, lava test monitoring stopped")
+ self.results.update({'status': 'passed'})
+ elif event == "timeout":
+ self.logger.warning("err: lava test monitoring has timed out")
+ self.errors = "lava test monitoring has timed out"
+ self.results.update({'status': 'failed'})
+ elif event == "test_result":
+ self.logger.info("ok: test case found")
+ match = test_connection.match.groupdict()
+ if 'result' in match:
+ if self.fixupdict:
+ if match['result'] in self.fixupdict:
+ match['result'] = self.fixupdict[match['result']]
+ if match['result'] not in ('pass', 'fail', 'skip', 'unknown'):
+ self.logger.error("error: bad test results: %s" % match['result'])
+ else:
+ if 'test_case_id' in match:
+ results = {
+ 'definition': self.test_suite_name.replace(' ', '-').lower(),
+ 'case': match['test_case_id'].lower(),
+ 'result': match['result']
+ }
+ if 'measurement' in match:
+ results.update({'measurement': match['measurement']})
+ if 'units' in match:
+ results.update({'units': match['units']})
+ self.logger.results(results)
+ ret_val = True
+ return ret_val
diff --git a/lava_dispatcher/pipeline/actions/test/shell.py b/lava_dispatcher/pipeline/actions/test/shell.py
index 9506ab9..5fb2862 100644
--- a/lava_dispatcher/pipeline/actions/test/shell.py
+++ b/lava_dispatcher/pipeline/actions/test/shell.py
@@ -21,6 +21,7 @@
import re
import sys
import time
+import yaml
import logging
import pexpect
from collections import OrderedDict
@@ -67,7 +68,10 @@ class TestShell(LavaTest):
@classmethod
def accepts(cls, device, parameters): # pylint: disable=unused-argument
- return True
+ if ('definition' in parameters) or ('definitions' in parameters):
+ return True
+ else:
+ return False
class TestShellRetry(RetryAction):
@@ -205,6 +209,8 @@ class TestShellAction(TestAction):
# FIXME: This should be logged whenever prompt_str is changed, by the connection object.
self.logger.debug("Setting default test shell prompt %s", connection.prompt_str)
connection.timeout = self.connection_timeout
+ # force an initial prompt - not all shells will respond without an excuse.
+ connection.sendline(connection.check_char)
self.wait(connection)
# use the string instead of self.name so that inheriting classes (like multinode)
@@ -233,7 +239,7 @@ class TestShellAction(TestAction):
while self._keep_running(test_connection, test_connection.timeout, connection.check_char):
pass
- self.logger.debug(self.report)
+ self.logger.debug(yaml.dump(self.report))
return connection
def parse_v2_case_result(self, data, fixupdict=None):
@@ -303,7 +309,6 @@ class TestShellAction(TestAction):
self.start = time.time()
self.logger.debug("Starting test definition: %s" % self.definition)
self.logger.info("Starting test lava.%s (%s)", self.definition, uuid)
- self.start = time.time()
# set the pattern for this run from pattern_dict
testdef_index = self.get_common_data('test-definition', 'testdef_index')
uuid_list = self.get_common_data('repo-action', 'uuid-list')
@@ -327,6 +332,9 @@ class TestShellAction(TestAction):
uuid = params[1]
# remove the pattern for this run from pattern_dict
self._reset_patterns()
+ # catch error in ENDRUN being handled without STARTRUN
+ if not self.start:
+ self.start = time.time()
self.logger.info("Ending use of test pattern.")
self.logger.info("Ending test lava.%s (%s), duration %.02f",
self.definition, uuid,
@@ -481,7 +489,7 @@ class TestShellAction(TestAction):
raise KeyboardInterrupt
except TypeError as exc:
# handle serial corruption which can overlap kernel messages onto test output.
- self.logger.exception(exc)
+ self.logger.exception(str(exc))
except JobError as exc:
self.logger.error("job error: handling signal %s failed: %s", name, exc)
return False
diff --git a/lava_dispatcher/pipeline/actions/test/strategies.py b/lava_dispatcher/pipeline/actions/test/strategies.py
index 67020f5..21de446 100644
--- a/lava_dispatcher/pipeline/actions/test/strategies.py
+++ b/lava_dispatcher/pipeline/actions/test/strategies.py
@@ -25,3 +25,4 @@
from lava_dispatcher.pipeline.actions.test.shell import TestShell
from lava_dispatcher.pipeline.actions.test.multinode import MultinodeTestShell
+from lava_dispatcher.pipeline.actions.test.monitor import TestMonitor
diff --git a/lava_dispatcher/pipeline/connections/lxc.py b/lava_dispatcher/pipeline/connections/lxc.py
index 3e38204..fe2ce24 100644
--- a/lava_dispatcher/pipeline/connections/lxc.py
+++ b/lava_dispatcher/pipeline/connections/lxc.py
@@ -45,6 +45,8 @@ class ConnectLxc(Action):
self.shell_class = ShellCommand
def validate(self):
+ if 'lxc' not in self.job.device['actions']['boot']['methods']:
+ return
super(ConnectLxc, self).validate()
self.errors = infrastructure_error('lxc-attach')
if 'prompts' not in self.parameters:
@@ -52,34 +54,9 @@ class ConnectLxc(Action):
def run(self, connection, args=None):
lxc_name = self.get_common_data('lxc', 'name')
-
- # Attach usb device to lxc
- if 'device_path' in list(self.job.device.keys()):
- device_path = self.job.device['device_path']
- if not isinstance(device_path, list):
- raise JobError("device_path should be a list")
-
- if device_path:
- # Wait USB_SHOW_UP_TIMEOUT seconds for usb device to show up
- self.logger.info("Wait %d seconds for usb device to show up",
- USB_SHOW_UP_TIMEOUT)
- sleep(USB_SHOW_UP_TIMEOUT)
-
- for path in device_path:
- path = os.path.realpath(path)
- if os.path.isdir(path):
- devices = os.listdir(path)
- else:
- devices = [path]
-
- for device in devices:
- device = os.path.join(path, device)
- lxc_cmd = ['lxc-device', '-n', lxc_name, 'add', device]
- self.run_command(lxc_cmd)
- self.logger.debug("%s: devices added from %s", lxc_name,
- path)
- else:
- self.logger.debug("device_path is None")
+ if not lxc_name:
+ self.logger.debug("No LXC device requested")
+ return connection
cmd = "lxc-attach -n {0}".format(lxc_name)
self.logger.info("%s Connecting to device using '%s'", self.name, cmd)
diff --git a/lava_dispatcher/pipeline/connections/serial.py b/lava_dispatcher/pipeline/connections/serial.py
index cee3a68..ee7e9bd 100644
--- a/lava_dispatcher/pipeline/connections/serial.py
+++ b/lava_dispatcher/pipeline/connections/serial.py
@@ -59,7 +59,7 @@ class ConnectDevice(Action):
self.errors = infrastructure_error(exe)
def run(self, connection, args=None):
- if connection:
+ if isinstance(connection, SimpleSession):
self.logger.debug("Already connected")
if not connection.prompt_str:
# prompt_str can be a list or str
diff --git a/lava_dispatcher/pipeline/deployment_data.py b/lava_dispatcher/pipeline/deployment_data.py
index 5825053..1ff2ff7 100644
--- a/lava_dispatcher/pipeline/deployment_data.py
+++ b/lava_dispatcher/pipeline/deployment_data.py
@@ -132,6 +132,21 @@ oe = deployment_data_dict({ # pylint: disable=invalid-name
'lava_test_dir': '/lava-%s',
'lava_test_results_part_attr': 'root_part',
'lava_test_results_dir': '/lava-%s',
+ 'lava_test_shell_file': '~/.bashrc',
+})
+
+lede = deployment_data_dict({ # pylint: disable=invalid-name
+ 'TESTER_PS1': r"linaro-test [rc=$(echo \$?)]# ",
+ 'TESTER_PS1_PATTERN': r"linaro-test \[rc=(\d+)\]# ",
+ 'TESTER_PS1_INCLUDES_RC': True,
+ 'boot_cmds': 'boot_cmds_lede',
+
+ # for lava-test-shell
+ 'distro': 'lede',
+ 'lava_test_sh_cmd': '/bin/sh',
+ 'lava_test_dir': '/tmp/lava-%s',
+ 'lava_test_results_part_attr': 'root_part',
+ 'lava_test_results_dir': '/tmp/lava-results-%s',
'lava_test_shell_file': None,
})
@@ -148,7 +163,7 @@ fedora = deployment_data_dict({ # pylint: disable=invalid-name
'lava_test_dir': '/lava-%s',
'lava_test_results_part_attr': 'root_part',
'lava_test_results_dir': '/lava-%s',
- 'lava_test_shell_file': None,
+ 'lava_test_shell_file': '~/.bashrc',
})
centos = deployment_data_dict({ # pylint: disable=invalid-name
@@ -164,7 +179,7 @@ centos = deployment_data_dict({ # pylint: disable=invalid-name
'lava_test_dir': '/lava-%s',
'lava_test_results_part_attr': 'root_part',
'lava_test_results_dir': '/lava-%s',
- 'lava_test_shell_file': None,
+ 'lava_test_shell_file': '~/.bashrc',
})
debian_installer = deployment_data_dict({ # pylint: disable=invalid-name
@@ -192,7 +207,7 @@ debian_installer = deployment_data_dict({ # pylint: disable=invalid-name
'lava_test_dir': '/lava-%s',
'lava_test_results_part_attr': 'root_part',
'lava_test_results_dir': '/lava-%s',
- 'lava_test_shell_file': None,
+ 'lava_test_shell_file': '~/.bashrc',
})
centos_installer = deployment_data_dict({ # pylint: disable=invalid-name
@@ -209,5 +224,5 @@ centos_installer = deployment_data_dict({ # pylint: disable=invalid-name
'lava_test_dir': '/lava-%s',
'lava_test_results_part_attr': 'root_part',
'lava_test_results_dir': '/lava-%s',
- 'lava_test_shell_file': None,
+ 'lava_test_shell_file': '~/.bashrc',
})
diff --git a/lava_dispatcher/pipeline/device.py b/lava_dispatcher/pipeline/device.py
index d767de3..1f68829 100644
--- a/lava_dispatcher/pipeline/device.py
+++ b/lava_dispatcher/pipeline/device.py
@@ -59,10 +59,22 @@ class PipelineDevice(dict):
return ''
@property
+ def pre_os_command(self):
+ if 'commands' in self and 'pre_os_command' in self['commands']:
+ return self['commands']['pre_os_command']
+ return None
+
+ @property
+ def pre_power_command(self):
+ if 'commands' in self and 'pre_power_command' in self['commands']:
+ return self['commands']['pre_power_command']
+ return None
+
+ @property
def power_command(self):
if 'commands' in self and 'power_on' in self['commands']:
return self['commands']['power_on']
- return self.hard_reset_command
+ return ''
@property
def connect_command(self):
diff --git a/lava_dispatcher/pipeline/device_types/hi6220-hikey.conf b/lava_dispatcher/pipeline/device_types/hi6220-hikey.conf
index 1fcf070..5389d29 100644
--- a/lava_dispatcher/pipeline/device_types/hi6220-hikey.conf
+++ b/lava_dispatcher/pipeline/device_types/hi6220-hikey.conf
@@ -2,9 +2,11 @@ actions:
deploy:
# list of deployment methods which this device supports
methods:
+ - lxc
- fastboot
boot:
# list of boot methods which this device supports.
methods:
+ - lxc
- uefi-menu
diff --git a/lava_dispatcher/pipeline/devices/hi6220-hikey-01.yaml b/lava_dispatcher/pipeline/devices/hi6220-hikey-01.yaml
index c1a9727..852570e 100644
--- a/lava_dispatcher/pipeline/devices/hi6220-hikey-01.yaml
+++ b/lava_dispatcher/pipeline/devices/hi6220-hikey-01.yaml
@@ -1,23 +1,29 @@
commands:
- connect: telnet 192.168.1.200 8001
- hard_reset: /home/stylesen/work/pdu/pdu-control-reset.sh 0 5 1 5
- soft_reset: fastboot -s usb:2-1.2 reboot
- power_off: /home/stylesen/work/pdu/pdu-control-off.sh 5
- power_on: /home/stylesen/work/pdu/pdu-control-on.sh 5
- fastboot_command: fastboot -s usb:2-1.2
+ connect: telnet localhost 7019
+ hard_reset: /usr/bin/pduclient --daemon services --hostname pdu09 --command reboot --port 08
+ soft_reset: fastboot -u -s 96B0201601000691 reboot
+ power_off: /usr/bin/pduclient --daemon services --hostname pdu09 --command off --port 08
+ power_on: /usr/bin/pduclient --daemon services --hostname pdu09 --command on --port 08
+ pre_os_command: /usr/local/lab-scripts/usb_hub_control -p 8000 -m off -u 06
+ pre_power_command: /usr/local/lab-scripts/usb_hub_control -p 8000 -m sync -u 06
+ adb_command: adb -s 96B0201601000691
+ fastboot_command: fastboot -u -s 96B0201601000691
device_type: hi6220-hikey
-adb_serial_number: 0123456789
-fastboot_serial_number: usb:2-1.2
-
+adb_serial_number: 96B0201601000691
+fastboot_serial_number: 96B0201601000691
+device_path: ['/dev/hikey01']
actions:
deploy:
methods:
+ lxc:
fastboot:
connections:
+ lxc:
serial:
boot:
connections:
+ lxc:
serial:
methods:
uefi-menu:
diff --git a/lava_dispatcher/pipeline/job.py b/lava_dispatcher/pipeline/job.py
index 5aa02b5..187a9ff 100644
--- a/lava_dispatcher/pipeline/job.py
+++ b/lava_dispatcher/pipeline/job.py
@@ -19,9 +19,10 @@
# with this program; if not, see <http://www.gnu.org/licenses>.
import logging
+import time
import yaml
-from lava_dispatcher.pipeline.action import Action, JobError
+from lava_dispatcher.pipeline.action import Action, JobError, InfrastructureError
from lava_dispatcher.pipeline.log import YAMLLogger # pylint: disable=unused-import
from lava_dispatcher.pipeline.logical import PipelineContext
from lava_dispatcher.pipeline.diagnostics import DiagnoseNetwork
@@ -63,14 +64,18 @@ class Job(object): # pylint: disable=too-many-instance-attributes
self.timeout = None
self.protocols = []
self.compatibility = 2
- # TODO: we are now able to create the logger when the job is started,
+ # We are now able to create the logger when the job is started,
# allowing the functions that are called before run() to log.
- # Do we want to do something with this?
- # Taking into account that the validate() function will be called on
- # the LAVA server when the job is submitted.
- # For the moment, we create the logger without the ZMQ handler that
- # will be added when running the job.
+ # The validate() function is no longer called on the master so we can
+ # safelly add the ZMQ handler. This way validate can log errors that
+ # test writter will see.
self.logger = logging.getLogger('dispatcher')
+ if socket_addr is not None:
+ self.logger.addZMQHandler(socket_addr, master_cert, slave_cert,
+ job_id)
+ self.logger.setMetadata("0", "validate")
+ else:
+ self.logger.addHandler(logging.StreamHandler())
def set_pipeline(self, pipeline):
self.pipeline = pipeline
@@ -107,6 +112,8 @@ class Job(object): # pylint: disable=too-many-instance-attributes
Then needs to validate the context
Finally expose the context so that actions can see it.
"""
+ self.logger.info("start: 0 validate")
+ start = time.time()
for protocol in self.protocols:
try:
protocol.configure(self.device, self)
@@ -125,7 +132,15 @@ class Job(object): # pylint: disable=too-many-instance-attributes
print(yaml.dump(self.describe())) # pylint: disable=superfluous-parens
# FIXME: validate the device config
# FIXME: pretty output of exception messages needed.
- self.pipeline.validate_actions()
+ try:
+ self.pipeline.validate_actions()
+ except (JobError, InfrastructureError) as exc:
+ self.logger.error("Invalid job definition")
+ self.logger.exception(str(exc))
+ # This should be re-raised to end the job
+ raise
+ finally:
+ self.logger.info("validate duration: %.02f", time.time() - start)
def run(self):
"""
@@ -134,13 +149,6 @@ class Job(object): # pylint: disable=too-many-instance-attributes
will have a default timeout which will use SIGALRM. So the overarching Job timeout
can only stop processing actions if the job wide timeout is exceeded.
"""
- # Add the ZMQ handler now
- if self.socket_addr is not None:
- self.logger.addZMQHandler(self.socket_addr, self.master_cert,
- self.slave_cert, self.job_id) # pylint: disable=maybe-no-member
- else:
- self.logger.addHandler(logging.StreamHandler())
-
for protocol in self.protocols:
try:
protocol.set_up()
diff --git a/lava_dispatcher/pipeline/logical.py b/lava_dispatcher/pipeline/logical.py
index 8a45d0f..b8d7665 100644
--- a/lava_dispatcher/pipeline/logical.py
+++ b/lava_dispatcher/pipeline/logical.py
@@ -205,8 +205,7 @@ class Deployment(object):
"No deployment strategy available for the given "
"device '%s'. %s" % (device['hostname'], cls))
- willing.sort(key=lambda x: x.priority)
- willing.reverse()
+ willing.sort(key=lambda x: x.priority, reverse=True)
return willing[0]
@@ -247,8 +246,7 @@ class Boot(object):
)
# higher priority first
- willing.sort(key=lambda x: x.priority)
- willing.reverse()
+ willing.sort(key=lambda x: x.priority, reverse=True)
return willing[0]
@@ -291,8 +289,7 @@ class LavaTest(object):
raise NotImplementedError(msg)
# higher priority first
- willing.sort(key=lambda x: x.priority)
- willing.reverse()
+ willing.sort(key=lambda x: x.priority, reverse=True)
return willing[0]
diff --git a/lava_dispatcher/pipeline/parser.py b/lava_dispatcher/pipeline/parser.py
index b1eed4f..b019ddf 100644
--- a/lava_dispatcher/pipeline/parser.py
+++ b/lava_dispatcher/pipeline/parser.py
@@ -45,7 +45,7 @@ import lava_dispatcher.pipeline.actions.test.strategies
import lava_dispatcher.pipeline.protocols.strategies
-def parse_action(job_data, name, device, pipeline):
+def parse_action(job_data, name, device, pipeline, test_action):
"""
If protocols are defined, each Action may need to be aware of the protocol parameters.
"""
@@ -57,9 +57,12 @@ def parse_action(job_data, name, device, pipeline):
Boot.select(device, job_data[name])(pipeline, parameters)
elif name == 'test':
LavaTest.select(device, job_data[name])(pipeline, parameters)
- elif name == 'deploy':
+ elif name == 'deploy' and test_action and 'type' not in job_data[name]:
parameters.update({'deployment_data': get_deployment_data(parameters.get('os', ''))})
Deployment.select(device, job_data[name])(pipeline, parameters)
+ elif name == 'deploy' and 'type' in job_data[name]:
+ parameters.update({'test_action': test_action})
+ Deployment.select(device, job_data[name])(pipeline, parameters)
class JobParser(object):
@@ -145,6 +148,12 @@ class JobParser(object):
pipeline = Pipeline(job=job)
self._timeouts(data, job)
+ # some special handling is needed to tell the overlay classes about the presence or absence of a test action
+ test_action = True
+ test_list = [action for action in data['actions'] if 'test' in action]
+ if test_list and 'test' not in test_list[0]:
+ test_action = False
+
# FIXME: also read permissable overrides from device config and set from job data
# FIXME: ensure that a timeout for deployment 0 does not get set as the timeout for deployment 1 if 1 is default
for action_data in data['actions']:
@@ -154,7 +163,7 @@ class JobParser(object):
action_data[name].update(self._map_context_defaults())
counts.setdefault(name, 1)
if name == 'deploy' or name == 'boot' or name == 'test':
- parse_action(action_data, name, device, pipeline)
+ parse_action(action_data, name, device, pipeline, test_action)
elif name == 'repeat':
count = action_data[name]['count'] # first list entry must be the count dict
repeats = action_data[name]['actions']
@@ -164,7 +173,7 @@ class JobParser(object):
if repeat_action == 'yaml_line':
continue
repeating[repeat_action]['repeat-count'] = c_iter
- parse_action(repeating, repeat_action, device, pipeline)
+ parse_action(repeating, repeat_action, device, pipeline, test_action)
else:
# May only end up being used for submit as other actions all need strategy method objects
diff --git a/lava_dispatcher/pipeline/power.py b/lava_dispatcher/pipeline/power.py
index ff06170..6edf927 100644
--- a/lava_dispatcher/pipeline/power.py
+++ b/lava_dispatcher/pipeline/power.py
@@ -74,7 +74,7 @@ class RebootDevice(Action):
if self.job.device.power_state is 'on' and self.job.device.soft_reset_command is not '':
command = self.job.device['commands']['soft_reset']
if not self.run_command(command.split(' '), allow_silent=True):
- raise InfrastructureError("%s command failed" % command)
+ raise InfrastructureError("Command '%s' failed" % command)
self.results = {"success": self.job.device.power_state}
else:
connection = super(RebootDevice, self).run(connection, args)
@@ -150,16 +150,48 @@ class PowerOn(Action):
def run(self, connection, args=None):
connection = super(PowerOn, self).run(connection, args)
if self.job.device.power_state is 'off':
+ if self.job.device.pre_power_command:
+ command = self.job.device.pre_power_command
+ self.logger.info("Running pre power command")
+ if not self.run_command(command.split(' '), allow_silent=True):
+ raise InfrastructureError("%s failed" % command)
command = self.job.device.power_command
if not command:
return connection
if not self.run_command(command.split(' '), allow_silent=True):
- raise InfrastructureError("%s command failed" % command)
+ raise InfrastructureError("Command '%s' failed" % command)
self.results = {'success': self.name}
self.job.device.power_state = 'on'
return connection
+class FastBootRebootAction(Action):
+ """
+ This action calls fastboot reboot
+ """
+ def __init__(self):
+ super(FastBootRebootAction, self).__init__()
+ self.name = "reboot-fastboot"
+ self.summary = "attempt to fastboot soft reboot"
+ self.description = "soft reboot using fastboot"
+ self.command = ''
+
+ def validate(self):
+ super(FastBootRebootAction, self).validate()
+ if 'fastboot_serial_number' not in self.job.device:
+ self.errors = "device fastboot serial number missing"
+ if self.job.device['fastboot_serial_number'] == '0000000000':
+ self.errors = "device fastboot serial number unset"
+
+ def run(self, connection, args=None):
+ if self.job.device.power_state is 'on' and self.job.device.soft_reset_command is not '':
+ command = self.job.device['commands']['soft_reset']
+ if not self.run_command(command.split(' '), allow_silent=True):
+ raise InfrastructureError("Command '%s' failed" % command)
+ self.results = {"success": self.job.device.power_state}
+ return connection
+
+
# FIXME: Unused action, but can give fine grained control.
class LxcStop(Action):
"""
@@ -233,7 +265,7 @@ class PowerOff(Action):
if self.job.device.power_state is 'on': # allow for '' and skip
command = self.job.device['commands']['power_off']
if not self.run_command(command.split(' '), allow_silent=True):
- raise InfrastructureError("%s command failed" % command)
+ raise InfrastructureError("Command '%s' failed" % command)
self.results = {'status': 'success'}
self.job.device.power_state = 'off'
return connection
@@ -274,9 +306,7 @@ class FinalizeAction(Action):
elif self.job.pipeline.errors:
self.results = {'status': "Incomplete"}
self.errors = "Incomplete"
- self.logger.error({
- 'Status': 'Incomplete',
- 'Errors': self.job.pipeline.errors})
+ self.logger.error('Status: Incomplete\nErrors %s', self.job.pipeline.errors)
else:
self.results = {'success': "Complete"}
self.logger.info("Status: Complete")
diff --git a/lava_dispatcher/pipeline/protocols/vland.py b/lava_dispatcher/pipeline/protocols/vland.py
index c0a6939..62a9405 100644
--- a/lava_dispatcher/pipeline/protocols/vland.py
+++ b/lava_dispatcher/pipeline/protocols/vland.py
@@ -277,7 +277,7 @@ class VlandProtocol(Protocol):
self.logger.debug({"lookup_switch": msg})
response = self._call_vland(msg)
if not response or response == '':
- return None
+ raise JobError("Switch_id for switch name: %s not found", switch_name)
reply = json.loads(response)
return reply['data']
@@ -293,7 +293,7 @@ class VlandProtocol(Protocol):
self.logger.debug({"lookup_port_id": msg})
response = self._call_vland(msg)
if not response or response == '':
- return None
+ raise JobError("Port_id for port: %s not found", port)
reply = json.loads(response)
return reply['data']
@@ -403,6 +403,7 @@ class VlandProtocol(Protocol):
if set(device_info['tags']) & set(self.params[vlan_name]['tags']) == set(self.params[vlan_name]['tags']):
self.params[vlan_name]['switch'] = device_info['switch']
self.params[vlan_name]['port'] = device_info['port']
+ self.params[vlan_name]['iface'] = iface
self.nodes_seen.append(' '.join([device_info['switch'], str(device_info['port'])]))
break
self.logger.debug("[%s] vland params: %s", device['hostname'], self.params)
@@ -443,7 +444,8 @@ class VlandProtocol(Protocol):
params = self.params[friendly_name]
switch_id = self._lookup_switch_id(params['switch'])
port_id = self._lookup_port_id(switch_id, params['port'])
- self.logger.info("Setting switch %s port %s to vlan %s", params['switch'], params['port'], friendly_name)
+ self.logger.info("Setting switch %s port %s to vlan %s on %s",
+ params['switch'], params['port'], friendly_name, params['iface'])
self._set_port_onto_vlan(self.vlans[friendly_name], port_id)
self.ports.append(port_id)
diff --git a/lava_dispatcher/pipeline/test/pipeline_refs/fastboot.yaml b/lava_dispatcher/pipeline/test/pipeline_refs/fastboot.yaml
index 2c3132d..3ab268a 100644
--- a/lava_dispatcher/pipeline/test/pipeline_refs/fastboot.yaml
+++ b/lava_dispatcher/pipeline/test/pipeline_refs/fastboot.yaml
@@ -22,7 +22,6 @@
pipeline:
- {class: actions.boot.lxc.LxcStartAction, name: boot-lxc}
- {class: connections.lxc.ConnectLxc, name: connect-lxc}
- - {class: actions.boot.AutoLoginAction, name: auto-login-action}
- {class: shell.ExpectShellSession, name: expect-shell-connection}
- {class: actions.boot.environment.ExportDeviceEnvironment, name: export-device-env}
- class: actions.deploy.fastboot.FastbootAction
diff --git a/lava_dispatcher/pipeline/test/pipeline_refs/lxc.yaml b/lava_dispatcher/pipeline/test/pipeline_refs/lxc.yaml
index e3d43a9..9165a9c 100644
--- a/lava_dispatcher/pipeline/test/pipeline_refs/lxc.yaml
+++ b/lava_dispatcher/pipeline/test/pipeline_refs/lxc.yaml
@@ -22,7 +22,6 @@
pipeline:
- {class: actions.boot.lxc.LxcStartAction, name: boot-lxc}
- {class: connections.lxc.ConnectLxc, name: connect-lxc}
- - {class: actions.boot.AutoLoginAction, name: auto-login-action}
- {class: shell.ExpectShellSession, name: expect-shell-connection}
- {class: actions.boot.environment.ExportDeviceEnvironment, name: export-device-env}
- class: actions.test.shell.TestShellRetry
diff --git a/lava_dispatcher/pipeline/test/pipeline_refs/mustang-uefi.yaml b/lava_dispatcher/pipeline/test/pipeline_refs/mustang-uefi.yaml
index 61f05d9..960cc30 100644
--- a/lava_dispatcher/pipeline/test/pipeline_refs/mustang-uefi.yaml
+++ b/lava_dispatcher/pipeline/test/pipeline_refs/mustang-uefi.yaml
@@ -58,6 +58,8 @@
- {class: menus.menus.MenuReset, name: menu-reset}
- {class: actions.boot.AutoLoginAction, name: auto-login-action}
- {class: actions.boot.environment.ExportDeviceEnvironment, name: export-device-env}
+ - {class: connections.lxc.ConnectLxc, name: connect-lxc}
+ - {class: actions.boot.fastboot.WaitForAdbDevice, name: wait-for-adb-device}
- class: actions.test.shell.TestShellRetry
name: lava-test-retry
pipeline:
diff --git a/lava_dispatcher/pipeline/test/sample_jobs/hi6220-hikey.yaml b/lava_dispatcher/pipeline/test/sample_jobs/hi6220-hikey.yaml
new file mode 100644
index 0000000..02c8e46
--- /dev/null
+++ b/lava_dispatcher/pipeline/test/sample_jobs/hi6220-hikey.yaml
@@ -0,0 +1,84 @@
+device_type: hi6220-hikey
+job_name: lxc-hi6220-hikey
+timeouts:
+ job:
+ minutes: 60
+ action:
+ minutes: 15
+ connection:
+ minutes: 2
+priority: medium
+visibility: public
+
+metadata:
+ source: https://git.linaro.org/lava-team/refactoring.git
+ path: hi6220-hikey.yaml
+
+protocols:
+ lava-lxc:
+ name: lxc-hikey-test
+ template: debian
+ distribution: debian
+ release: jessie
+ arch: amd64
+
+actions:
+- deploy:
+ namespace: tlxc
+ timeout:
+ minutes: 5
+ to: lxc
+ packages:
+ - android-tools-adb
+ - android-tools-fastboot
+ os: debian
+
+- boot:
+ namespace: tlxc
+ prompts:
+ - 'root@(.*):/#'
+ - 'shell@hikey'
+ timeout:
+ minutes: 5
+ method: lxc
+
+- deploy:
+ timeout:
+ minutes: 15
+ to: fastboot
+ namespace: droid
+ connection: lxc
+ images:
+ ptable:
+ url: http://builds.96boards.org/snapshots/reference-platform/components/uefi/latest/release/hikey/ptable-aosp-8g.img
+ boot:
+ url: http://people.linaro.org/~senthil.kumaran/hi6220-hikey-images-8gb/boot_fat.uefi.img
+ cache:
+ url: http://people.linaro.org/~senthil.kumaran/hi6220-hikey-images-8gb/cache.img
+ userdata:
+ url: http://people.linaro.org/~senthil.kumaran/hi6220-hikey-images-8gb/userdata.img
+ system:
+ url: http://people.linaro.org/~senthil.kumaran/hi6220-hikey-images-8gb/system.img
+ os: debian
+
+- boot:
+ namespace: droid
+ connection: serial
+ prompts:
+ - 'root@(.*):/#'
+ - 'shell@hikey'
+ timeout:
+ minutes: 15
+ method: uefi-menu
+ commands: fastboot
+
+- test:
+ namespace: tlxc
+ connection: lxc
+ timeout:
+ minutes: 10
+ definitions:
+ - repository: git://git.linaro.org/people/senthil.kumaran/test-definitions.git
+ from: git
+ path: debian/get-adb-serial.yaml
+ name: get-adb-serial
diff --git a/lava_dispatcher/pipeline/test/sample_jobs/luvos-monitor-qemu.yaml b/lava_dispatcher/pipeline/test/sample_jobs/luvos-monitor-qemu.yaml
new file mode 100644
index 0000000..4fcbe87
--- /dev/null
+++ b/lava_dispatcher/pipeline/test/sample_jobs/luvos-monitor-qemu.yaml
@@ -0,0 +1,70 @@
+# Zephyr JOB definition for QEMU
+device_type: qemu
+job_name: luvos-qemu-monitor
+
+timeouts:
+ job:
+ minutes: 6
+ action:
+ minutes: 5
+ actions:
+ auto-login-action:
+ seconds: 300
+ lava-test-monitor:
+ seconds: 300
+ lava-test-shell:
+ seconds: 300
+ lava-test-retry:
+ seconds: 300
+ connections:
+ lava-test-retry:
+ seconds: 300
+ lava-test-monitor:
+ seconds: 300
+ lava-test-shell:
+ seconds: 300
+ bootloader-action:
+ seconds: 300
+ bootloader-retry:
+ seconds: 300
+
+priority: medium
+visibility: public
+context:
+ arch: aarch64
+
+actions:
+- deploy:
+ timeout:
+ minutes: 3
+ to: tmpfs
+ type: monitor
+ images:
+ hdimg:
+ image_arg: -drive if=none,file={hdimg},id=hd0,format=raw -device virtio-blk-device,drive=hd0
+ url: http://ironhide.bounceme.net/luvos/luv-live-image.img
+ bios:
+ image_arg: -bios {bios}
+ url: http://ironhide.bounceme.net/luvos/QEMU_EFI.fd
+
+- boot:
+ method: monitor
+ timeout:
+ minutes: 2
+
+- test:
+ monitors:
+ - name: booting
+ start: Booting Linux on
+ end: Unpacking initramfs
+ pattern: '\[\+\] (?P<test_case_id>.*)\.\.\. [0-9]? ?(?P<result>(passed|skipped|failures))'
+ - name: luvos
+ start: Running tests...
+ end: '(0-9)* testsuites and (0-9)*'
+ pattern: '\[\+\] (?P<test_case_id>.*)\.\.\. [0-9]? ?(?P<result>(passed|skipped|failures))'
+ fixupdict:
+ passed: pass
+ failures: fail
+ skipped: skip
+
+
diff --git a/lava_dispatcher/pipeline/test/sample_jobs/qemu-monitor.yaml b/lava_dispatcher/pipeline/test/sample_jobs/qemu-monitor.yaml
new file mode 100644
index 0000000..145a09b
--- /dev/null
+++ b/lava_dispatcher/pipeline/test/sample_jobs/qemu-monitor.yaml
@@ -0,0 +1,39 @@
+# Non-POSIX test action support
+
+device_type: qemu
+
+job_name: qemu-monitoring
+timeouts:
+ job:
+ minutes: 15
+ action:
+ minutes: 5
+priority: medium
+visibility: public
+context:
+ arch: amd64
+
+actions:
+ - deploy:
+ to: tmpfs
+ type: monitor
+ images:
+ rootfs:
+ url: http://images.validation.linaro.org/kvm-debian-wheezy.img.gz
+ image_arg: -drive format=raw,file={rootfs}
+ compression: gz
+
+ - boot:
+ method: monitor
+ media: tmpfs
+ failure_retry: 2
+
+ - test:
+ monitors:
+ - name: Test AES128
+ start: tc_start()
+ end: PROJECT EXECUTION
+ pattern: (?P<result>(PASS|FAIL))\s-\s(?P<test_case_id>\w+)
+ fixupdict:
+ PASS: pass
+ FAIL: fail
diff --git a/lava_dispatcher/pipeline/test/test_basic.py b/lava_dispatcher/pipeline/test/test_basic.py
index e9bbfc7..c3cc65c 100644
--- a/lava_dispatcher/pipeline/test/test_basic.py
+++ b/lava_dispatcher/pipeline/test/test_basic.py
@@ -428,6 +428,5 @@ class TestStrategySelector(unittest.TestCase):
def test_willing(self):
willing = [TestStrategySelector.First(), TestStrategySelector.Third(), TestStrategySelector.Second()]
- willing.sort(key=lambda x: x.priority)
- willing.reverse()
+ willing.sort(key=lambda x: x.priority, reverse=True)
self.assertIsInstance(willing[0], TestStrategySelector.Third)
diff --git a/lava_dispatcher/pipeline/test/test_fastboot.py b/lava_dispatcher/pipeline/test/test_fastboot.py
index e669afd..d94e82c 100644
--- a/lava_dispatcher/pipeline/test/test_fastboot.py
+++ b/lava_dispatcher/pipeline/test/test_fastboot.py
@@ -30,6 +30,7 @@ from lava_dispatcher.pipeline.action import JobError
from lava_dispatcher.pipeline.test.test_basic import pipeline_reference
from lava_dispatcher.pipeline.actions.deploy import DeployAction
from lava_dispatcher.pipeline.actions.boot.fastboot import BootAction
+from lava_dispatcher.pipeline.power import FastBootRebootAction
class Factory(object): # pylint: disable=too-few-public-methods
@@ -47,14 +48,23 @@ class Factory(object): # pylint: disable=too-few-public-methods
output_dir=output_dir)
return job
+ def create_hikey_job(self, filename, output_dir='/tmp/'): # pylint: disable=no-self-use
+ device = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/hi6220-hikey-01.yaml'))
+ fastboot_yaml = os.path.join(os.path.dirname(__file__), filename)
+ with open(fastboot_yaml) as sample_job_data:
+ parser = JobParser()
+ job = parser.parse(sample_job_data, device, 4212, None, None, None,
+ output_dir=output_dir)
+ return job
+
class TestFastbootDeploy(unittest.TestCase): # pylint: disable=too-many-public-methods
def setUp(self):
super(TestFastbootDeploy, self).setUp()
- factory = Factory()
- self.job = factory.create_fastboot_job('sample_jobs/fastboot.yaml',
- mkdtemp())
+ self.factory = Factory()
+ self.job = self.factory.create_fastboot_job('sample_jobs/fastboot.yaml',
+ mkdtemp())
def test_deploy_job(self):
self.assertEqual(self.job.pipeline.job, self.job)
@@ -67,6 +77,17 @@ class TestFastbootDeploy(unittest.TestCase): # pylint: disable=too-many-public-
description_ref = pipeline_reference('fastboot.yaml')
self.assertEqual(description_ref, self.job.pipeline.describe(False))
+ def test_fastboot_lxc(self):
+ job = self.factory.create_hikey_job('sample_jobs/hi6220-hikey.yaml',
+ mkdtemp())
+ uefi_menu = [action for action in job.pipeline.actions if action.name == 'uefi-menu-action'][0]
+ self.assertIn('commands', uefi_menu.parameters)
+ self.assertIn('fastboot', uefi_menu.parameters['commands'])
+ self.assertEqual(
+ job.device.pre_power_command,
+ '/usr/local/lab-scripts/usb_hub_control -p 8000 -m sync -u 06')
+ self.assertIn(FastBootRebootAction, [obj.__class__ for obj in uefi_menu.internal_pipeline.actions])
+
@unittest.skipIf(infrastructure_error('lxc-create'),
'lxc-create not installed')
def test_validate(self):
diff --git a/lava_dispatcher/pipeline/test/test_kvm.py b/lava_dispatcher/pipeline/test/test_kvm.py
index 1ec1e51..b99441d 100644
--- a/lava_dispatcher/pipeline/test/test_kvm.py
+++ b/lava_dispatcher/pipeline/test/test_kvm.py
@@ -621,3 +621,17 @@ class TestKvmUefi(unittest.TestCase): # pylint: disable=too-many-public-methods
self.job.validate()
self.assertIn('-L', execute.sub_command)
self.assertIn(uefi_dir, execute.sub_command)
+
+
+class TestMonitor(unittest.TestCase): # pylint: disable=too-many-public-methods
+
+ def setUp(self):
+ super(TestMonitor, self).setUp()
+ factory = Factory()
+ self.job = factory.create_kvm_job('sample_jobs/qemu-monitor.yaml', mkdtemp())
+
+ def test_qemu_monitor(self):
+ self.assertIsNotNone(self.job)
+ self.assertIsNotNone(self.job.pipeline)
+ self.assertIsNotNone(self.job.pipeline.actions)
+ self.job.validate()
diff --git a/lava_dispatcher/pipeline/test/test_utils.py b/lava_dispatcher/pipeline/test/test_utils.py
index 7ebb8f3..c05161f 100644
--- a/lava_dispatcher/pipeline/test/test_utils.py
+++ b/lava_dispatcher/pipeline/test/test_utils.py
@@ -70,6 +70,20 @@ class TestGit(unittest.TestCase): # pylint: disable=too-many-public-methods
'GIT_COMMITTER_NAME': 'Foo Bar',
'GIT_COMMITTER_EMAIL': 'foo@example.com'})
+ subprocess.check_output(['git', 'checkout', '-b', 'testing'])
+ with open('third.txt', 'w') as datafile:
+ datafile.write("333")
+ subprocess.check_output(['git', 'add', 'third.txt'])
+ subprocess.check_output(['git', 'commit', 'third.txt', '-m', 'Third commit'],
+ env={'GIT_COMMITTER_DATE': 'Thu Sep 1 10:14:29 CEST 2016',
+ 'GIT_AUTHOR_DATE': 'Thu Sep 1 10:14:29 CEST 2016',
+ 'GIT_AUTHOR_NAME': 'Foo Bar',
+ 'GIT_AUTHOR_EMAIL': 'foo@example.com',
+ 'GIT_COMMITTER_NAME': 'Foo Bar',
+ 'GIT_COMMITTER_EMAIL': 'foo@example.com'})
+
+ subprocess.check_output(['git', 'checkout', 'master'])
+
# Go into the tempdir
os.chdir('..')
@@ -107,6 +121,10 @@ class TestGit(unittest.TestCase): # pylint: disable=too-many-public-methods
git = vcs.GitHelper('git')
self.assertRaises(InfrastructureError, git.clone, 'foo.bar', 'badhash')
+ def test_branch(self):
+ git = vcs.GitHelper('git')
+ self.assertEqual(git.clone('git.clone1', 'testing'), 'f2589a1b7f0cfc30ad6303433ba4d5db1a542c2d')
+
@unittest.skipIf(infrastructure_error('bzr'), "bzr not installed")
class TestBzr(unittest.TestCase): # pylint: disable=too-many-public-methods
diff --git a/lava_dispatcher/pipeline/test/test_vland.py b/lava_dispatcher/pipeline/test/test_vland.py
index 1012e19..2fdae51 100644
--- a/lava_dispatcher/pipeline/test/test_vland.py
+++ b/lava_dispatcher/pipeline/test/test_vland.py
@@ -154,9 +154,7 @@ class TestVland(unittest.TestCase): # pylint: disable=too-many-public-methods
bbb2['parameters']['interfaces']['eth1']['port'] = '4'
self.assertEqual(
vprotocol.params, {
- 'vlan_one': {
- 'switch': '192.168.0.1', 'port': 7, 'tags': ['100M', 'RJ45', '10M']
- }
+ 'vlan_one': {'switch': '192.168.0.1', 'iface': 'eth1', 'port': 7, 'tags': ['100M', 'RJ45', '10M']}
}
)
# already configured the vland protocol in the same job
@@ -164,7 +162,7 @@ class TestVland(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertEqual(
vprotocol.params, {
'vlan_one': {
- 'switch': '192.168.0.1', 'port': 7, 'tags': ['100M', 'RJ45', '10M']}
+ 'switch': '192.168.0.1', 'iface': 'eth1', 'port': 7, 'tags': ['100M', 'RJ45', '10M']}
}
)
self.assertTrue(vprotocol.valid)
@@ -201,6 +199,15 @@ class TestVland(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertIn(vlan_name, vprotocol.params)
self.assertIn('switch', vprotocol.params[vlan_name])
self.assertIn('port', vprotocol.params[vlan_name])
+ self.assertIn('iface', vprotocol.params[vlan_name])
+ params = job.parameters['protocols'][vprotocol.name]
+ names = []
+ for key, value in params.items():
+ if key == 'yaml_line':
+ continue
+ names.append(",".join([key, vprotocol.params[key]['iface']]))
+ # this device only has one interface with interface tags
+ self.assertEqual(names, ['vlan_one,eth1'])
def test_job_no_tags(self):
with open(self.filename) as yaml_data:
diff --git a/lava_dispatcher/pipeline/utils/compression.py b/lava_dispatcher/pipeline/utils/compression.py
index 0f42175..e9d9b42 100644
--- a/lava_dispatcher/pipeline/utils/compression.py
+++ b/lava_dispatcher/pipeline/utils/compression.py
@@ -49,7 +49,7 @@ def compress_file(infile, compression):
log = subprocess.check_output(cmd, shell=True)
os.chdir(pwd)
return "%s.%s" % (infile, compression)
- except OSError as exc:
+ except (OSError, subprocess.CalledProcessError) as exc:
raise RuntimeError('unable to compress file %s: %s' % (infile, exc))
@@ -67,7 +67,7 @@ def decompress_file(infile, compression):
# safe to use shell=True here, no external arguments
log = subprocess.check_output(cmd, shell=True)
return outfile
- except OSError as exc:
+ except (OSError, subprocess.CalledProcessError) as exc:
raise RuntimeError('unable to decompress file %s: %s' % (infile, exc))
diff --git a/lava_dispatcher/pipeline/utils/constants.py b/lava_dispatcher/pipeline/utils/constants.py
index 8ec92f9..18dc809 100644
--- a/lava_dispatcher/pipeline/utils/constants.py
+++ b/lava_dispatcher/pipeline/utils/constants.py
@@ -140,3 +140,6 @@ DEFAULT_V1_FIXUP = {'PASS': 'pass', 'FAIL': 'fail', 'SKIP': 'skip', 'UNKNOWN': '
# Message for notifying completion of secondary deployment
SECONDARY_DEPLOYMENT_MSG = "Secondary media deployment complete"
+
+# fallback UEFI menu label class
+DEFAULT_UEFI_LABEL_CLASS = 'a-zA-Z0-9\s\:'
diff --git a/lava_dispatcher/pipeline/utils/vcs.py b/lava_dispatcher/pipeline/utils/vcs.py
index f91879b..e144806 100644
--- a/lava_dispatcher/pipeline/utils/vcs.py
+++ b/lava_dispatcher/pipeline/utils/vcs.py
@@ -107,19 +107,20 @@ class GitHelper(VCSHelper):
def clone(self, dest_path, revision=None):
logger = logging.getLogger('dispatcher')
try:
- logger.debug("Running '%s clone %s'", self.binary, self.url)
- subprocess.check_output([self.binary, 'clone', self.url, dest_path],
+ logger.debug("Running '%s clone %s %s'", self.binary, self.url,
+ dest_path)
+ subprocess.check_output([self.binary, 'clone', self.url,
+ dest_path],
stderr=subprocess.STDOUT)
if revision is not None:
- logger.debug("Running '%s checkout %s", self.binary, str(revision))
- subprocess.check_output([self.binary, '--git-dir',
- os.path.join(dest_path, '.git'),
+ logger.debug("Running '%s checkout %s", self.binary,
+ str(revision))
+ subprocess.check_output([self.binary, '-C', dest_path,
'checkout', str(revision)],
stderr=subprocess.STDOUT)
- commit_id = subprocess.check_output([self.binary, '--git-dir',
- os.path.join(dest_path, '.git'),
+ commit_id = subprocess.check_output([self.binary, '-C', dest_path,
'log', '-1', '--pretty=%H'],
stderr=subprocess.STDOUT).strip()
except subprocess.CalledProcessError as exc:
diff --git a/man/lava-slave.rst b/man/lava-slave.rst
index 10a6d96..48b3df9 100644
--- a/man/lava-slave.rst
+++ b/man/lava-slave.rst
@@ -14,6 +14,22 @@ Usage
lava-slave [--master tcp://localhost:5556]
[--socket-addr tcp://localhost:5555] [--level=DEBUG]
+Options
+*******
+
+Options can be passed by editing /etc/lava-dispatcher/lava-slave
+
+Encryption
+**********
+
+Some LAVA instances require the ZMQ connection to the master to be
+encrypted. For more information on configuring lava-slave to use
+encryption support, see the lava-server documentation on your
+local instance or at:
+https://validation.linaro.org/static/docs/v2/pipeline-server.html#zmq-curve
+
+You will need to contact the admin of the instance to obtain the
+certificate of the master to which this slave should connect.
Systemd support
###############