From 7dff43c51670e615dc6d325f09063c733b5360c8 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Mon, 21 Sep 2020 16:43:19 +0300 Subject: feat: add script to start qemu-system, run ssh against it --- qemu.py | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100755 qemu.py diff --git a/qemu.py b/qemu.py new file mode 100755 index 0000000..311727a --- /dev/null +++ b/qemu.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 + + +import os +import shlex +import shutil +import signal +import subprocess +import tempfile +import time + + +CLOUD_INIT_META_TEMPLATE = """\ +# Amazon EC2 style metadata +local-hostname: {hostname} +""" + +CLOUD_INIT_USER_TEMPLATE = """\ +#cloud-config +ssh_authorized_keys: +- {pubkey} +""" + + +class Qemu: + def __init__(self, name, base_image, disk_size, pubkey): + self._name = name + self._dirname = tempfile.mkdtemp() + self._image = self._copy_image(base_image, disk_size) + self._pubkey = pubkey + self._memory = 512 + self._vcpus = 1 + self._username = None + self._port = None + self._pid = None + + def _join(self, *names): + return os.path.join(self._dirname, *names) + + def set_disk_size(self, size): + self._disk_size = str(size * 1024 * 1024) + + def set_memory(self, memory): + self._memory = memory + + def set_vcpus(self, vcpus): + self._vcpus = vcpus + + def set_username(self, username): + self._username = username + + def _copy_image(self, base_image, size): + image = self._join("qemu.img") + shutil.copy(base_image, image) + if size is not None: + subprocess.check_call(["qemu-img", "resize", "-q", image, str(size)]) + return image + + def _cloud_init_iso(self): + iso = self._join("cloud-init.iso") + + config = self._join("cloud-init") + os.mkdir(config) + + meta = os.path.join(config, "meta-data") + write_file(meta, CLOUD_INIT_META_TEMPLATE.format(hostname=self._name)) + + user = os.path.join(config, "user-data") + write_file(user, CLOUD_INIT_USER_TEMPLATE.format(pubkey=self._pubkey)) + + if os.path.exists(iso): + os.remove(iso) + subprocess.check_call( + [ + "genisoimage", + "-quiet", + "-joliet", + "-rock", + "-output", + iso, + "-volid", + "cidata", + config, + ] + ) + + return iso + + def start_qemu(self): + iso = self._cloud_init_iso() + + self._port = 2222 # FIXME + pid_file = self._join("qemu.pid") + out_file = self._join("qemu.out") + err_file = self._join("qemu.err") + + argv = [ + "/usr/sbin/daemonize", + "-c", + self._dirname, + "-p", + pid_file, + "-e", + err_file, + "-o", + out_file, + "/usr/bin/qemu-system-x86_64", + "-name", + self._name, + "-m", + str(self._memory), + "-cpu", + "host", + "-smp", + f"cpus={self._vcpus}", + "-drive", + f"file={self._image},format=qcow2,cache=none,if=virtio", + "-drive", + f"file={iso},if=ide,media=cdrom", + "-device", + "virtio-net,netdev=user.0", + "-netdev", + f"user,id=user.0,hostfwd=tcp::{self._port}-:22", + "-nographic", + "-enable-kvm", + ] + + subprocess.check_call(argv, stdout=None, stderr=None) + self._pid = int(wait_for(lambda: got_pid(pid_file), 1)) + + def stop_qemu(self): + if self._pid is not None: + os.kill(self._pid, signal.SIGTERM) + + def wait_for_ssh(self): + return wait_for(lambda: self.ssh(["hostname"]), 60) + + def ssh(self, argv): + assert self._username is not None + known = self._join("known.hosts") + p = subprocess.Popen( + [ + "ssh", + "-p", + str(self._port), + "-l", + self._username, + f"-ouserknownhostsfile={known}", + f"-ostricthostkeychecking=accept-new", + "localhost", + "--", + ] + + [shlex.quote(x) for x in argv], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + output, _ = p.communicate() + if output: + return output, p.returncode + time.sleep(5) + + +def read_file(filename): + with open(filename, "r") as f: + return f.read() + + +def write_file(filename, content): + with open(filename, "w") as f: + f.write(content) + + +def wait_for(func, timeout): + start = time.time() + while time.time() < start + timeout: + val = func() + if val is not None: + return val + time.sleep(0.1) + + +def got_pid(pid_file): + if os.path.exists(pid_file): + pid = read_file(pid_file) + if pid: + return pid.strip() + + +vmname = "scap-dev" +pubkey = read_file("/home/liw/.ssh/liw-openpgp.pub") +base_image = "/home/liw/tmp/debian-10-openstack-amd64.qcow2" +username = "debian" +memory = 1024 +vcpus = 1 + +qemu = Qemu(vmname, base_image, 100 * 1024 ** 3, pubkey) +qemu.set_memory(memory) +qemu.set_vcpus(vcpus) +qemu.set_username(username) +print("starting qemu") +qemu.start_qemu() +print("waiting for ssh") +if qemu.wait_for_ssh(): + print("got ssh") + + out, exit = qemu.ssh(["id"]) + print(out.decode("UTF8")) + + out, exit = qemu.ssh(["find", "-ls"]) + print(out.decode("UTF8")) + + qemu.stop_qemu() +else: + print("did not get ssh, boo") -- cgit v1.2.1