Add (gnu tests) and (gnu build marionette).
* gnu/build/marionette.scm, gnu/tests.scm: New files. * gnu/local.mk (GNU_SYSTEM_MODULES): Add them. * gnu/system/vm.scm (common-qemu-options): Remove '-serial stdio'.
This commit is contained in:
parent
b2fef041fc
commit
957afcae3c
|
@ -0,0 +1,206 @@
|
||||||
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
|
;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
|
||||||
|
;;;
|
||||||
|
;;; This file is part of GNU Guix.
|
||||||
|
;;;
|
||||||
|
;;; GNU Guix 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 3 of the License, or (at
|
||||||
|
;;; your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
(define-module (gnu build marionette)
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
|
#:use-module (srfi srfi-26)
|
||||||
|
#:use-module (rnrs io ports)
|
||||||
|
#:use-module (ice-9 match)
|
||||||
|
#:export (marionette?
|
||||||
|
make-marionette
|
||||||
|
marionette-eval
|
||||||
|
marionette-control
|
||||||
|
%qwerty-us-keystrokes
|
||||||
|
marionette-type))
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;;;
|
||||||
|
;;; Instrumentation tools for QEMU virtual machines (VMs). A "marionette" is
|
||||||
|
;;; essentially a VM (a QEMU instance) with its monitor connected to a
|
||||||
|
;;; Unix-domain socket, and with a REPL inside the guest listening on a
|
||||||
|
;;; virtual console, which is itself connected to the host via a Unix-domain
|
||||||
|
;;; socket--these are the marionette's strings, connecting it to the almighty
|
||||||
|
;;; puppeteer.
|
||||||
|
;;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-record-type <marionette>
|
||||||
|
(marionette command pid monitor repl)
|
||||||
|
marionette?
|
||||||
|
(command marionette-command) ;list of strings
|
||||||
|
(pid marionette-pid) ;integer
|
||||||
|
(monitor marionette-monitor) ;port
|
||||||
|
(repl marionette-repl)) ;port
|
||||||
|
|
||||||
|
(define* (wait-for-monitor-prompt port #:key (quiet? #t))
|
||||||
|
"Read from PORT until we have seen all of QEMU's monitor prompt. When
|
||||||
|
QUIET? is false, the monitor's output is written to the current output port."
|
||||||
|
(define full-prompt
|
||||||
|
(string->list "(qemu) "))
|
||||||
|
|
||||||
|
(let loop ((prompt full-prompt)
|
||||||
|
(matches '())
|
||||||
|
(prefix '()))
|
||||||
|
(match prompt
|
||||||
|
(()
|
||||||
|
;; It's useful to set QUIET? so we don't display the echo of our own
|
||||||
|
;; commands.
|
||||||
|
(unless quiet?
|
||||||
|
(for-each (lambda (line)
|
||||||
|
(format #t "qemu monitor: ~a~%" line))
|
||||||
|
(string-tokenize (list->string (reverse prefix))
|
||||||
|
(char-set-complement (char-set #\newline))))))
|
||||||
|
((chr rest ...)
|
||||||
|
(let ((read (read-char port)))
|
||||||
|
(cond ((eqv? read chr)
|
||||||
|
(loop rest (cons read matches) prefix))
|
||||||
|
((eof-object? read)
|
||||||
|
(error "EOF while waiting for QEMU monitor prompt"
|
||||||
|
(list->string (reverse prefix))))
|
||||||
|
(else
|
||||||
|
(loop full-prompt
|
||||||
|
'()
|
||||||
|
(cons read (append matches prefix))))))))))
|
||||||
|
|
||||||
|
(define* (make-marionette command
|
||||||
|
#:key (socket-directory "/tmp") (timeout 20))
|
||||||
|
"Return a QEMU marionette--i.e., a virtual machine with open connections to the
|
||||||
|
QEMU monitor and to the guest's backdoor REPL."
|
||||||
|
(define (file->sockaddr file)
|
||||||
|
(make-socket-address AF_UNIX
|
||||||
|
(string-append socket-directory "/" file)))
|
||||||
|
|
||||||
|
(define extra-options
|
||||||
|
(list "-nographic"
|
||||||
|
"-monitor" (string-append "unix:" socket-directory "/monitor")
|
||||||
|
"-chardev" (string-append "socket,id=repl,path=" socket-directory
|
||||||
|
"/repl")
|
||||||
|
"-device" "virtio-serial"
|
||||||
|
"-device" "virtconsole,chardev=repl"))
|
||||||
|
|
||||||
|
(let ((monitor (socket AF_UNIX SOCK_STREAM 0))
|
||||||
|
(repl (socket AF_UNIX SOCK_STREAM 0)))
|
||||||
|
(bind monitor (file->sockaddr "monitor"))
|
||||||
|
(listen monitor 1)
|
||||||
|
(bind repl (file->sockaddr "repl"))
|
||||||
|
(listen repl 1)
|
||||||
|
|
||||||
|
(match (primitive-fork)
|
||||||
|
(0
|
||||||
|
(catch #t
|
||||||
|
(lambda ()
|
||||||
|
(close monitor)
|
||||||
|
(close repl)
|
||||||
|
(match command
|
||||||
|
((program . args)
|
||||||
|
(apply execl program program
|
||||||
|
(append args extra-options)))))
|
||||||
|
(lambda (key . args)
|
||||||
|
(print-exception (current-error-port)
|
||||||
|
(stack-ref (make-stack #t) 1)
|
||||||
|
key args)
|
||||||
|
(primitive-exit 1))))
|
||||||
|
(pid
|
||||||
|
(format #t "QEMU runs as PID ~a~%" pid)
|
||||||
|
(sigaction SIGALRM
|
||||||
|
(lambda (signum)
|
||||||
|
(display "time is up!\n") ;FIXME: break
|
||||||
|
#t))
|
||||||
|
(alarm timeout)
|
||||||
|
|
||||||
|
(match (accept monitor)
|
||||||
|
((monitor-conn . _)
|
||||||
|
(display "connected to QEMU's monitor\n")
|
||||||
|
(close-port monitor)
|
||||||
|
(wait-for-monitor-prompt monitor-conn)
|
||||||
|
(display "read QEMU monitor prompt\n")
|
||||||
|
(match (accept repl)
|
||||||
|
((repl-conn . addr)
|
||||||
|
(display "connected to guest REPL\n")
|
||||||
|
(close-port repl)
|
||||||
|
(match (read repl-conn)
|
||||||
|
('ready
|
||||||
|
(alarm 0)
|
||||||
|
(sigaction SIGALRM SIG_DFL)
|
||||||
|
(display "marionette is ready\n")
|
||||||
|
(marionette (append command extra-options) pid
|
||||||
|
monitor-conn repl-conn)))))))))))
|
||||||
|
|
||||||
|
(define (marionette-eval exp marionette)
|
||||||
|
"Evaluate EXP in MARIONETTE's backdoor REPL. Return the result."
|
||||||
|
(match marionette
|
||||||
|
(($ <marionette> command pid monitor repl)
|
||||||
|
(write exp repl)
|
||||||
|
(newline repl)
|
||||||
|
(read repl))))
|
||||||
|
|
||||||
|
(define (marionette-control command marionette)
|
||||||
|
"Run COMMAND in the QEMU monitor of MARIONETTE. COMMAND is a string such as
|
||||||
|
\"sendkey ctrl-alt-f1\" or \"screendump foo.ppm\" (info \"(qemu-doc)
|
||||||
|
pcsys_monitor\")."
|
||||||
|
(match marionette
|
||||||
|
(($ <marionette> _ _ monitor)
|
||||||
|
(display command monitor)
|
||||||
|
(newline monitor)
|
||||||
|
(wait-for-monitor-prompt monitor))))
|
||||||
|
|
||||||
|
(define %qwerty-us-keystrokes
|
||||||
|
;; Maps "special" characters to their keystrokes.
|
||||||
|
'((#\newline . "ret")
|
||||||
|
(#\space . "spc")
|
||||||
|
(#\- . "minus")
|
||||||
|
(#\+ . "shift-equal")
|
||||||
|
(#\* . "shift-8")
|
||||||
|
(#\= . "equal")
|
||||||
|
(#\? . "shift-slash")
|
||||||
|
(#\[ . "bracket_left")
|
||||||
|
(#\] . "bracket_right")
|
||||||
|
(#\( . "shift-9")
|
||||||
|
(#\) . "shift-0")
|
||||||
|
(#\/ . "slash")
|
||||||
|
(#\< . "less")
|
||||||
|
(#\> . "shift-less")
|
||||||
|
(#\. . "dot")
|
||||||
|
(#\, . "comma")
|
||||||
|
(#\; . "semicolon")
|
||||||
|
(#\bs . "backspace")
|
||||||
|
(#\tab . "tab")))
|
||||||
|
|
||||||
|
(define* (string->keystroke-commands str
|
||||||
|
#:optional
|
||||||
|
(keystrokes
|
||||||
|
%qwerty-us-keystrokes))
|
||||||
|
"Return a list of QEMU monitor commands to send the keystrokes corresponding
|
||||||
|
to STR. KEYSTROKES is an alist specifying a mapping from characters to
|
||||||
|
keystrokes."
|
||||||
|
(string-fold-right (lambda (chr result)
|
||||||
|
(cons (string-append "sendkey "
|
||||||
|
(or (assoc-ref keystrokes chr)
|
||||||
|
(string chr)))
|
||||||
|
result))
|
||||||
|
'()
|
||||||
|
str))
|
||||||
|
|
||||||
|
(define* (marionette-type str marionette
|
||||||
|
#:key (keystrokes %qwerty-us-keystrokes))
|
||||||
|
"Type STR on MARIONETTE's keyboard, using the KEYSTROKES alist to map characters
|
||||||
|
to actual keystrokes."
|
||||||
|
(for-each (cut marionette-control <> marionette)
|
||||||
|
(string->keystroke-commands str keystrokes)))
|
||||||
|
|
||||||
|
;;; marionette.scm ends here
|
|
@ -398,7 +398,10 @@ GNU_SYSTEM_MODULES = \
|
||||||
gnu/build/linux-container.scm \
|
gnu/build/linux-container.scm \
|
||||||
gnu/build/linux-initrd.scm \
|
gnu/build/linux-initrd.scm \
|
||||||
gnu/build/linux-modules.scm \
|
gnu/build/linux-modules.scm \
|
||||||
gnu/build/vm.scm
|
gnu/build/marionette.scm \
|
||||||
|
gnu/build/vm.scm \
|
||||||
|
\
|
||||||
|
gnu/tests.scm
|
||||||
|
|
||||||
|
|
||||||
patchdir = $(guilemoduledir)/gnu/packages/patches
|
patchdir = $(guilemoduledir)/gnu/packages/patches
|
||||||
|
|
|
@ -468,7 +468,7 @@ with '-virtfs' options for the host file systems listed in SHARED-FS."
|
||||||
" -no-reboot -net nic,model=virtio \
|
" -no-reboot -net nic,model=virtio \
|
||||||
" #$@(map virtfs-option shared-fs) " \
|
" #$@(map virtfs-option shared-fs) " \
|
||||||
-net user \
|
-net user \
|
||||||
-serial stdio -vga std \
|
-vga std \
|
||||||
-drive file=" #$image
|
-drive file=" #$image
|
||||||
",if=virtio,cache=writeback,werror=report,readonly \
|
",if=virtio,cache=writeback,werror=report,readonly \
|
||||||
-m 256"))
|
-m 256"))
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
|
;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
|
||||||
|
;;;
|
||||||
|
;;; This file is part of GNU Guix.
|
||||||
|
;;;
|
||||||
|
;;; GNU Guix 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 3 of the License, or (at
|
||||||
|
;;; your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
(define-module (gnu tests)
|
||||||
|
#:use-module (guix gexp)
|
||||||
|
#:use-module (gnu system)
|
||||||
|
#:use-module (gnu services)
|
||||||
|
#:use-module (gnu services shepherd)
|
||||||
|
#:export (backdoor-service-type
|
||||||
|
marionette-operating-system))
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;;;
|
||||||
|
;;; This module provides the infrastructure to run operating system tests.
|
||||||
|
;;; The most important part of that is tools to instrument the OS under test,
|
||||||
|
;;; essentially allowing to run in a virtual machine controlled by the host
|
||||||
|
;;; system--hence the name "marionette".
|
||||||
|
;;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define (marionette-shepherd-service imported-modules)
|
||||||
|
"Return the Shepherd service for the marionette REPL"
|
||||||
|
(define device
|
||||||
|
"/dev/hvc0")
|
||||||
|
|
||||||
|
(list (shepherd-service
|
||||||
|
(provision '(marionette))
|
||||||
|
(requirement '(udev)) ;so that DEVICE is available
|
||||||
|
(modules '((ice-9 match)
|
||||||
|
(srfi srfi-9 gnu)
|
||||||
|
(guix build syscalls)
|
||||||
|
(rnrs bytevectors)))
|
||||||
|
(imported-modules `((guix build syscalls)
|
||||||
|
,@imported-modules))
|
||||||
|
(start
|
||||||
|
#~(lambda ()
|
||||||
|
(define (clear-echo termios)
|
||||||
|
(set-field termios (termios-local-flags)
|
||||||
|
(logand (lognot (local-flags ECHO))
|
||||||
|
(termios-local-flags termios))))
|
||||||
|
|
||||||
|
(define (self-quoting? x)
|
||||||
|
(letrec-syntax ((one-of (syntax-rules ()
|
||||||
|
((_) #f)
|
||||||
|
((_ pred rest ...)
|
||||||
|
(or (pred x)
|
||||||
|
(one-of rest ...))))))
|
||||||
|
(one-of symbol? string? pair? null? vector?
|
||||||
|
bytevector? number? boolean?)))
|
||||||
|
|
||||||
|
(match (primitive-fork)
|
||||||
|
(0
|
||||||
|
(dynamic-wind
|
||||||
|
(const #t)
|
||||||
|
(lambda ()
|
||||||
|
(let* ((repl (open-file #$device "r+0"))
|
||||||
|
(termios (tcgetattr (fileno repl)))
|
||||||
|
(console (open-file "/dev/console" "r+0")))
|
||||||
|
;; Don't echo input back.
|
||||||
|
(tcsetattr (fileno repl) (tcsetattr-action TCSANOW)
|
||||||
|
(clear-echo termios))
|
||||||
|
|
||||||
|
;; Redirect output to the console.
|
||||||
|
(close-fdes 1)
|
||||||
|
(close-fdes 2)
|
||||||
|
(dup2 (fileno console) 1)
|
||||||
|
(dup2 (fileno console) 2)
|
||||||
|
(close-port console)
|
||||||
|
|
||||||
|
(display 'ready repl)
|
||||||
|
(let loop ()
|
||||||
|
(newline repl)
|
||||||
|
|
||||||
|
(match (read repl)
|
||||||
|
((? eof-object?)
|
||||||
|
(primitive-exit 0))
|
||||||
|
(expr
|
||||||
|
(catch #t
|
||||||
|
(lambda ()
|
||||||
|
(let ((result (primitive-eval expr)))
|
||||||
|
(write (if (self-quoting? result)
|
||||||
|
result
|
||||||
|
(object->string result))
|
||||||
|
repl)))
|
||||||
|
(lambda (key . args)
|
||||||
|
(print-exception (current-error-port)
|
||||||
|
(stack-ref (make-stack #t) 1)
|
||||||
|
key args)
|
||||||
|
(write #f repl)))))
|
||||||
|
(loop))))
|
||||||
|
(lambda ()
|
||||||
|
(primitive-exit 1))))
|
||||||
|
(pid
|
||||||
|
pid))))
|
||||||
|
(stop #~(make-kill-destructor)))))
|
||||||
|
|
||||||
|
(define marionette-service-type
|
||||||
|
;; This is the type of the "marionette" service, allowing a guest system to
|
||||||
|
;; be manipulated from the host. This marionette REPL is essentially a
|
||||||
|
;; universal marionette.
|
||||||
|
(service-type (name 'marionette-repl)
|
||||||
|
(extensions
|
||||||
|
(list (service-extension shepherd-root-service-type
|
||||||
|
marionette-shepherd-service)))))
|
||||||
|
|
||||||
|
(define* (marionette-operating-system os
|
||||||
|
#:key (imported-modules '()))
|
||||||
|
"Return a marionetteed variant of OS such that OS can be used as a marionette
|
||||||
|
in a virtual machine--i.e., controlled from the host system."
|
||||||
|
(operating-system
|
||||||
|
(inherit os)
|
||||||
|
(services (cons (service marionette-service-type imported-modules)
|
||||||
|
(operating-system-user-services os)))))
|
||||||
|
|
||||||
|
;;; tests.scm ends here
|
Loading…
Reference in New Issue