diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2016-08-22 13:48:51 -0700 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2016-08-22 13:48:51 -0700 |
| commit | 6eb48645a439657056220674433dc2e99ab2d8eb (patch) | |
| tree | 70cb9c80f673e745ea76280ef5a92e4bf69e731e /vm | |
| parent | 96cc1ccc795c04887cb8b35c731c0e68be674eab (diff) | |
vm/qemu: support 9p host-based image
If "image" is set to "9p" in config file,
qemu VM will create a minimalistic image based
on readonly-mapped host filesystem.
The main things that we need are working sshd and ssh-keygen.
/tmp, /etc/, /var, /root are remounted as tmpfs.
Diffstat (limited to 'vm')
| -rw-r--r-- | vm/qemu/qemu.go | 96 |
1 files changed, 87 insertions, 9 deletions
diff --git a/vm/qemu/qemu.go b/vm/qemu/qemu.go index 8633f4f2f..bc8fffbb6 100644 --- a/vm/qemu/qemu.go +++ b/vm/qemu/qemu.go @@ -5,6 +5,7 @@ package qemu import ( "fmt" + "io/ioutil" "math/rand" "net" "os" @@ -67,6 +68,18 @@ func ctorImpl(cfg *vm.Config) (vm.Instance, error) { return nil, err } + if cfg.Image == "9p" { + inst.cfg.Sshkey = filepath.Join(inst.cfg.Workdir, "key") + keygen := exec.Command("ssh-keygen", "-t", "rsa", "-b", "2048", "-N", "", "-C", "", "-f", inst.cfg.Sshkey) + if out, err := keygen.CombinedOutput(); err != nil { + return nil, fmt.Errorf("failed to execute ssh-keygen: %v\n%s", err, out) + } + initFile := filepath.Join(cfg.Workdir, "init.sh") + if err := ioutil.WriteFile(initFile, []byte(strings.Replace(initScript, "{{KEY}}", inst.cfg.Sshkey, -1)), 0777); err != nil { + return nil, fmt.Errorf("failed to create init file: %v", err) + } + } + var err error inst.rpipe, inst.wpipe, err = os.Pipe() if err != nil { @@ -88,11 +101,17 @@ func validateConfig(cfg *vm.Config) error { if cfg.Bin == "" { cfg.Bin = "qemu-system-x86_64" } - if _, err := os.Stat(cfg.Image); err != nil { - return fmt.Errorf("image file '%v' does not exist: %v", cfg.Image, err) - } - if _, err := os.Stat(cfg.Sshkey); err != nil { - return fmt.Errorf("ssh key '%v' does not exist: %v", cfg.Sshkey, err) + if cfg.Image == "9p" { + if cfg.Kernel == "" { + return fmt.Errorf("9p image requires kernel") + } + } else { + if _, err := os.Stat(cfg.Image); err != nil { + return fmt.Errorf("image file '%v' does not exist: %v", cfg.Image, err) + } + if _, err := os.Stat(cfg.Sshkey); err != nil { + return fmt.Errorf("ssh key '%v' does not exist: %v", cfg.Sshkey, err) + } } if cfg.Cpu <= 0 || cfg.Cpu > 1024 { return fmt.Errorf("bad qemu cpu: %v, want [1-1024]", cfg.Cpu) @@ -131,8 +150,6 @@ func (inst *instance) Boot() error { } // TODO: ignores inst.cfg.Cpu args := []string{ - "-hda", inst.cfg.Image, - "-snapshot", "-m", strconv.Itoa(inst.cfg.Mem), "-net", "nic", "-net", fmt.Sprintf("user,host=%v,hostfwd=tcp::%v-:22", hostAddr, inst.port), @@ -145,15 +162,33 @@ func (inst *instance) Boot() error { "-usb", "-usbdevice", "mouse", "-usbdevice", "tablet", "-soundhw", "all", } + if inst.cfg.Image == "9p" { + args = append(args, + "-fsdev", "local,id=fsdev0,path=/,security_model=none,readonly", + "-device", "virtio-9p-pci,fsdev=fsdev0,mount_tag=/dev/root", + ) + } else { + args = append(args, + "-hda", inst.cfg.Image, + "-snapshot", + ) + } if inst.cfg.Initrd != "" { args = append(args, "-initrd", inst.cfg.Initrd, ) } if inst.cfg.Kernel != "" { + cmdline := "console=ttyS0 oops=panic panic_on_warn=1 panic=-1 ftrace_dump_on_oops=orig_cpu debug earlyprintk=serial slub_debug=UZ " + if inst.cfg.Image == "9p" { + cmdline += "root=/dev/root rootfstype=9p rootflags=trans=virtio,version=9p2000.L,cache=loose " + cmdline += "init=" + filepath.Join(inst.cfg.Workdir, "init.sh") + " " + } else { + cmdline += "root=/dev/sda " + } args = append(args, "-kernel", inst.cfg.Kernel, - "-append", "console=ttyS0 root=/dev/sda debug earlyprintk=serial slub_debug=UZ "+inst.cfg.Cmdline, + "-append", cmdline+inst.cfg.Cmdline, ) } qemu := exec.Command(inst.cfg.Bin, args...) @@ -249,7 +284,11 @@ func (inst *instance) Forward(port int) (string, error) { } func (inst *instance) Copy(hostSrc string) (string, error) { - vmDst := filepath.Join("/", filepath.Base(hostSrc)) + basePath := "/" + if inst.cfg.Image == "9p" { + basePath = "/tmp" + } + vmDst := filepath.Join(basePath, filepath.Base(hostSrc)) args := append(inst.sshArgs("-P"), hostSrc, "root@localhost:"+vmDst) cmd := exec.Command("scp", args...) if err := cmd.Start(); err != nil { @@ -322,6 +361,7 @@ func (inst *instance) sshArgs(portArg string) []string { return []string{ "-i", inst.cfg.Sshkey, portArg, strconv.Itoa(inst.port), + "-F", "/dev/null", "-o", "ConnectionAttempts=10", "-o", "ConnectTimeout=10", "-o", "BatchMode=yes", @@ -331,3 +371,41 @@ func (inst *instance) sshArgs(portArg string) []string { "-o", "LogLevel=error", } } + +const initScript = `#! /bin/bash +set -eux +mount -t proc none /proc +mount -t sysfs none /sys +mount -t debugfs nodev /sys/kernel/debug/ +mount -t tmpfs none /tmp +mount -t tmpfs none /var +mount -t tmpfs none /etc +mount -t tmpfs none /root +touch /etc/fstab +echo "root::0:0:root:/root:/bin/bash" > /etc/passwd +mkdir -p /etc/ssh +cp {{KEY}}.pub /root/ +chmod 0700 /root +chmod 0600 /root/key.pub +mkdir -p /var/run/sshd/ +chmod 700 /var/run/sshd +cat > /etc/ssh/sshd_config <<EOF + Port 22 + Protocol 2 + UsePrivilegeSeparation no + HostKey {{KEY}} + PermitRootLogin yes + AuthenticationMethods publickey + ChallengeResponseAuthentication no + AuthorizedKeysFile /root/key.pub + IgnoreUserKnownHosts yes + AllowUsers root + LogLevel INFO + TCPKeepAlive yes + RSAAuthentication yes + PubkeyAuthentication yes +EOF +/sbin/dhclient eth0 +/usr/sbin/sshd -e -D +/sbin/halt -f +` |
