z-hypervisor/zhypervisor/clients/qmachine.py

195 lines
6.1 KiB
Python

import os
import logging
import subprocess
from time import sleep
from threading import Thread
from zhypervisor.util import TapDevice, Machine
from zhypervisor.util import ZDisk
class QMachine(Machine):
machine_type = "q"
def __init__(self, spec):
Machine.__init__(self, spec)
self.proc = None
self.tap = TapDevice()
self.block_respawns = False
# TODO validate specs
def get_status(self):
"""
Return string "stopped" or "running" depending on machine status
@TODO machine status consts
"""
return "stopped" if self.proc is None else "running"
def start_machine(self):
"""
If needed, launch the machine.
"""
if self.proc:
raise Exception("Machine already running!")
else:
qemu_args = self.get_args(tap=str(self.tap))
logging.info("spawning qemu with: {}".format(' '.join(qemu_args)))
sleep(1) # anti-spin
self.proc = subprocess.Popen(qemu_args, preexec_fn=lambda: os.setpgrp(), stdin=subprocess.PIPE)
# TODO handle stdout/err - stream to logs?
Thread(target=self.wait_on_exit, args=[self.proc]).start()
def wait_on_exit(self, proc):
"""
Listener used by above start_machine to restart the machine if the machine exits
"""
proc.wait()
logging.info("qemu process has exited")
self.proc = None
if not self.block_respawns and self.spec.properties.get("respawn", False):
self.start_machine()
def stop_machine(self):
"""
Send the powerdown signal to the running machine
"""
if self.proc:
logging.info("stopping machine %s", self.spec.machine_id)
self.proc.stdin.write(b"system_powerdown\n")
self.proc.stdin.flush()
self.proc.wait()
self.proc = None
def kill_machine(self):
"""
Forcefully kill the running machine
"""
print("Terminating {}".format(self.proc))
if self.proc:
self.proc.terminate()
self.proc.wait()
self.proc = None
def get_args(self, tap):
"""
Assemble the full argv array that will be executed for this machine
"""
argv = ['qemu-system-x86_64']
argv += self.get_args_system()
argv += self.get_args_drives()
argv += self.get_args_network(tap)
return argv
def get_args_system(self):
"""
Return system-related args:
- Qemu meta args
- CPU core settings
- Mem amnt
- Boot device
"""
args = ["-monitor", "stdio", "-machine", "accel=kvm", "-smp"]
args.append("cpus={}".format(self.spec.properties.get("cores", 1))) # why doesn't this work: ,cores={}
args.append("-m")
args.append(str(self.spec.properties.get("mem", 256)))
args.append("-boot")
args.append("cd")
if self.spec.properties.get("vnc", False):
args.append("-vnc")
assert type(self.spec.properties.get("vnc")) == int, "VNC port should be an integer"
args.append(":{}".format(self.spec.properties.get("vnc")))
return args
def get_args_network(self, tap_name):
"""
Return network related qemu args
"""
args = []
for iface in self.spec.properties.get("netifaces"):
iface_type = iface.get("type")
iface_args = {"type": iface_type}
if iface_type == "tap":
if "ifname" in iface:
iface_args["ifname"] = iface.get("ifname")
iface_args["script"] = "/root/zhypervisor/testenv/bin/zd_ifup" # TODO don't hard code
iface_args["downscript"] = "no"
else:
iface_args.update(iface)
args.append("-net")
args.append(QMachine.format_args(iface_args))
return args
# return ['-net', 'nic,vlan=0,model=e1000,macaddr=82:25:60:41:D5:97',
# '-net', 'tap,ifname={},script=if_up.sh,downscript=no'.format(tap_name)]
def get_args_drives(self):
"""
Inspect props.drives expecting a format like: {"file": "/tmp/ubuntu.qcow2", "index": 0, "if": "virtio"}
"""
args = []
for attached_drive in self.spec.properties.get("drives", []):
args.append("-drive")
disk_ob = self.spec.master.disks[attached_drive["disk"]]
drive_args = {"file": disk_ob.get_path()}
for option in ["if", "index", "media"]:
if option in attached_drive:
drive_args[option] = attached_drive[option]
args.append(QMachine.format_args((drive_args)))
return args
@staticmethod
def format_args(d):
"""
Given a dictionary like: {"file": "/dev/zd0", "index": 0, "if", "virtio"}
Return a string like: file=/dev/zd0,index=0,if=virtio
"""
args = []
for item, value in d.items():
if item == "type":
args.insert(0, value)
else:
args.append("{}={}".format(item, value))
if not args:
return None
return ','.join(args)
class QDisk(ZDisk):
def init(self):
"""
Create a QEMU-formatted virtual disk
"""
disk_path = self.get_path()
assert not os.path.exists(disk_path), "Disk already exists!"
img_args = ["qemu-img", "create", "-f", self.properties["fmt"], disk_path, "{}M".format(int(self.properties["size"]))]
logging.info("Creating disk with: %s", str(img_args))
subprocess.check_call(img_args)
def validate(self):
assert self.disk_id.endswith(".bin"), "QDisks names must end with '.bin'"
def delete(self):
os.unlink(self.get_path())
class IsoDisk(ZDisk):
pass
# TODO make this do more nothing
def validate(self):
assert self.disk_id.endswith(".iso"), "IsoDisk names must end with '.iso'"
def init(self):
assert os.path.exists(self.get_path()), "ISO must already exist!"
def delete(self):
pass