Index: .pylintrc ================================================================== --- .pylintrc +++ .pylintrc @@ -1,1 +1,3 @@ -good-names: i,j,k,f,r,ex +[default] +good-names: i,j,k,f,r,p,ex + Index: vws ================================================================== --- vws +++ vws @@ -1,19 +1,23 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # -*- encoding: utf-8 -*- """ vws - script to control QEMU/KVM virtual workstations """ -# pylint: disable=bad-builtin -from ConfigParser import ConfigParser +# pylint: disable=too-many-lines +from configparser import ConfigParser from argparse import ArgumentParser, Namespace import fcntl -import socket, select +import socket +import select import errno import re -import os, sys, time, os.path +import os +import os.path +import sys +import time import pwd -VERSION = 0.7 +VERSION = 0.8 def find_vm(name): """ Search and return VM directory """ search_path = [os.path.join(pwd.getpwuid(os.getuid()).pw_dir, "VWs"), config.get("directories", "SharedVMs"), config.get("directories", "AutostartVMs")] @@ -36,12 +40,11 @@ sock.connect(monitor_path) except IOError as ex: if ex.errno == errno.ECONNREFUSED: # virtal machine is not running return None - else: - raise ex + raise ex readfd, dummy_w, dummy_x = select.select([sock], [], [], 0.1) if sock in readfd: dummy_greeting = sock.recv(1024) return sock @@ -50,14 +53,14 @@ fcntl.flock(sock, fcntl.LOCK_EX) try: # There can be stray (qemu) prompt in the socket. Try to drain # it try: - sock.recv(64,socket.MSG_DONTWAIT) - except socket.error as e: - if e.errno != errno.EAGAIN and e.errno!=errno.EWOULDBLOCK: - raise e + sock.recv(64, socket.MSG_DONTWAIT) + except socket.error as ex: + if ex.errno != errno.EAGAIN and ex.errno != errno.EWOULDBLOCK: + raise ex sock.send(command + "\n") answer = "" while not answer.endswith("(qemu) "): chunk = sock.recv(1024) if chunk == '': @@ -80,11 +83,11 @@ if url.startswith('*:'): url = socket.getfqdn()+url[1:] if url is None: if output.rstrip().endswith('(qemu)'): return spiceurl(sock) - print >>sys.stderr, "ERROR parsing 'info spice' output:«",output,"»" + print("ERROR parsing 'info spice' output:«%s»" % output, file=sys.stderr) return None return "spice://" + url.rstrip('\r') @@ -105,20 +108,20 @@ def parse_arp(iface): """ Returns map which maps mac addresses to IPs for specified interface" """ addr_map = {} - pipe = os.popen(config.get("tools","arp")+" -n -i "+iface, "r") + pipe = os.popen("%s -n -u %s" % (config.get("tools", "arp"), iface), "r") for line in pipe: - data = line.split() - mac=data[2] + data = line.split() + mac = data[2] if mac == "HWAddress": continue if mac == iface: # Foind line with (incomplete) entry continue - addr_map[data[2]]=data[0] + addr_map[data[2]] = data[0] return addr_map def validate_size(size): """ Checks if size argument has proper format """ return re.match('\\d+[KMG]', size) is not None @@ -140,170 +143,190 @@ answer = send_command(sock, "info block") return re.search(": /tmp", answer) is not None def read_netinfo(filename): """ Reads network information from start script """ - with open(filename,"r") as f: - for line in f: - match=re.search("-net nic,macaddr=(\\S+) -net ([^, ]+)",line) - if match: - f={"mac":match.group(1)} - if match.group(2) == "user": - f["iface"]="user" - elif match.group(2) == "bridge": - f["iface"]=re.search("br=(\\S+)",line).group(1) - else: - f["iface"]="unknown"; - return f - return {"iface":"unknown","mac":"?","ip":"?"} + with open(filename, "r") as f: + for line in f: + match = re.search("-net nic,macaddr=(\\S+) -net ([^, ]+)", line) + if match: + f = {"mac":match.group(1)} + if match.group(2) == "user": + f["iface"] = "user" + elif match.group(2) == "bridge": + f["iface"] = re.search("br=(\\S+)", line).group(1) + else: + f["iface"] = "unknown" + return f + return {"iface":"unknown", "mac":"?", "ip":"?"} def get_netinfo(sock): """ Gets network information from the running VM """ answer = send_command(sock, "info network") - match=re.search("bridge\\.0:.*,br=(\\S+).*macaddr=(\\S+)", answer, re.S) + match = re.search("bridge\\.0:.*,br=(\\S+).*macaddr=(\\S+)", answer, re.S) if match: return {"iface":match.group(1), "mac":match.group(2)} - else: - match = re.search("user.0:.*net=([^,]+).*\n.*macaddr=(\\S+)",answer) - if match: - return {"iface":"user", "ip":match.group(1), - "mac":match.group(2)} - else: - print >>sys.stderr,answer - return {"iface":"unknown","ip":"?","mac":"?","card":"?"} + match = re.search("user.0:.*net=([^,]+).*\n.*macaddr=(\\S+)", answer) + if match: + return {"iface":"user", "ip":match.group(1), + "mac":match.group(2)} + print(answer, file=sys.stderr) + return {"iface":"unknown", "ip":"?", "mac":"?", "card":"?"} # # command implementation # def cmd_spiceuri(options): """ vws spiceuri """ - print spiceurl(options.sock) + print(spiceurl(options.sock)) + +def fix_bridge_name(filename): + """ + Checks if bridge listed in network configuration + exists on this machine, and replaces it with default one + from config, if not so + """ + f = open(filename, "r+") + data = f.read() + idx0 = data.find("-net bridge,br=") + if idx != -1: + idx = data.find("=", idx0) + idx += 1 + idx2 = data.find(" ", idx) + bridgename = data[idx:idx2] + if not bridgename in list_bridges(): + net = config.get("create options", "net") + if net == "user": + data = data[:idx0] + "-net user" + data[idx2:] + else: + data = data[:idx] + net + data[idx2:] + f.seek(0) + f.write(data) + f.truncate() + f.close() + +def check_for_snapshot(vmdir): + """ + Checks if there is snapshot of running vm which should be restored + Returns either id of this snapshot or None + """ + + nxt = 0 + with os.popen("qemu-img info \"%s\"" % + (get_drives(vmdir)[0]), "r") as f: + for line in f: + if line == 'Snapshot list:\n': + nxt = 2 + elif nxt == 2 and line.startswith('ID'): + nxt = 1 + elif nxt == 1: + nxt = 0 + snapshot_id = line[:line.index(' ')] + return snapshot_id + return None - +def make_start_cmdline(options): + """ + Append options passed from vws commandline to start script + commandline + """ + arg = "" + if options.cdrom: + arg = " -cdrom " + os.path.abspath(options.cdrom[0]) + if options.snapshot: + arg = arg+" -snapshot" + if options.args: + arg = arg + " " + "".join(options.args) + if options.password: + os.environ["SPICE_PASSWORD"] = options.password[0] + return arg def cmd_start(options): """ vws start """ if not "DISPLAY" in os.environ: # If cannot start GUI just don't do it. options.gui = False if options.stopped: - arg = "" - if options.cdrom: - arg = " -cdrom " + os.path.abspath(options.cdrom[0]) - if options.snapshot: - arg = arg+" -snapshot" - if options.args: - arg = arg + " " + "".join(options.args) - if options.password: - os.environ["SPICE_PASSWORD"]=options.password[0] - print arg + arg = make_start_cmdline(options) cwd = os.getcwd() os.chdir(options.dir) # Check for snapshot - nxt = 0 - snapshot_id = None - with os.popen("qemu-img info \"%s\"" % - (get_drives(options.dir)[0]), "r") as f: - for line in f: - if line == 'Snapshot list:\n': - nxt = 2 - elif nxt == 2 and line.startswith('ID'): - nxt = 1 - elif nxt == 1: - nxt = 0 - snapshot_id = line[:line.index(' ')] - arg = arg + " -loadvm " + snapshot_id - break + snapshot_id = check_for_snapshot(options.dir) + if snapshot_id is not None: + arg = arg + " -loadvm " + snapshot_id # Check for correct brige name try: os.stat("monitor") except OSError: # We cannot find monitor socket. So this machine might be # never run on this host - f=open("start","r+") - data=f.read() - idx=data.find("-net bridge,br=") - if idx != -1: - idx=data.find("=",idx) - idx+=1 - idx2=data.find(" ",idx) - bridgename=data[idx:idx2] - if not bridgename in list_bridges(): - net = config.get("create options","net") - if net== "user": - data=data[:idx0]+"-net user"+data[idx2:] - else: - data=data[:idx]+net+data[idx2:] - f.seek(0) - f.write(data) - f.truncate() - f.close() - - + fix_bridge_name("start") os.system("./start%s" % arg) os.chdir(cwd) time.sleep(2) options.sock = connect_vm(options.dir) if snapshot_id: send_command(options.sock, "delvm " + snapshot_id) else: if options.snapshot or options.args or options.password: - print >>sys.stderr, ("Cannot change qemu options. " + - "VM is already running") + print("Cannot change qemu options. " + + "VM is already running", file=sys.stderr) if options.cdrom: options.file = options.cdrom[0] options.id = None cmd_cdrom(options) uri = spiceurl(options.sock) if options.gui: os.system((config.get('tools', 'viewer') + "&") % uri) elif not options.stopped: - print >>sys.stderr, "VM already running use uri %s" % uri + print("VM already running use uri %s" % uri, file=sys.stderr) def cmd_stop(options): """ vws stop """ if snapshot_mode(options.sock) or options.hard: try: - send_command(options.sock, 'quit') - except IOError as e: - # Expect IOError here - if e.message.find("EOF from monitor"): - print "monitor socket closed" - else: - raise e - else: - print send_command(options.sock, 'system_powerdown') + send_command(options.sock, 'quit') + except IOError as ex: + # Expect IOError here + if str(ex).find("EOF from monitor"): + print("monitor socket closed") + else: + raise ex + else: + print(send_command(options.sock, 'system_powerdown')) def cmd_monitor(options): """ vws monitor """ try: - print "(qemu) ", + print("(qemu) ", end="") sys.stdout.flush() while True: readfd, dummy_w, dummy_x = select.select([sys.stdin, options.sock], [], []) if sys.stdin in readfd: cmd = sys.stdin.readline() # Check for eof - if len(cmd) == 0: + if cmd == "": break answer = send_command(options.sock, cmd.rstrip()) idx = answer.index('\n') - print answer[idx+1:], + print(answer[idx+1:], end="") sys.stdout.flush() elif options.sock in readfd: - print "UNSOLICITED MESSAGE %" + options.sock.readline().rstrip() + print("UNSOLICITED MESSAGE %" + + options.sock.readline().rstrip()) except KeyboardInterrupt: - print "Keyboard interrupt" + print("Keyboard interrupt") def cmd_reset(options): """ vws reset """ - print send_command(options.sock, 'system_reset') + print(send_command(options.sock, 'system_reset')) + def cmd_save(options): """ vws save """ answer = send_command(options.sock, 'savevm') if re.search("Error", answer): - print >>sys.stderr, answer + print(answer, file=sys.stderr) sys.exit(1) else: send_command(options.sock, 'quit') def cmd_cdrom(options): @@ -315,22 +338,22 @@ devlist): if re.search("cd", dev): options.id = dev break if options.id is None: - print >>sys.stderr, "No CDROM device found among:\n" + devlist + print("No CDROM device found among:\n" + devlist, file=sys.stderr) return 1 if options.file == "": - - print >>sys.stderr, "Please specify either --eject or iso image" + print("Please specify either --eject or iso image", file=sys.stderr) return 1 if options.file is None: answer = send_command(options.sock, "eject " + options.id) else: answer = send_command(options.sock, "change %s %s" % (options.id, os.path.abspath(options.file))) - print answer + print(answer) + return 0 def find_usb(options, devices): """ Search for pattern or address given in options in the given list of devices. List should be produced by get_host_devices() or @@ -340,19 +363,19 @@ for dev in devices: if re.search(options.pattern, dev[1]): options.address = dev[0] break elif not hasattr("address", options): - print >>sys.stderr, ("Address or search pattern for device " + - "is not specified") + print("Address or search pattern for device " + + "is not specified", file=sys.stderr) options.sock.close() sys.exit(1) - else: - return options.address + return options.address def get_host_devices(): """ Parses output of lsusb into list of tuples "address" "descr" """ + # pylint: disable=global-statement, invalid-name global config f = os.popen(config.get('tools', "lsusb"), "r") lst = [] for dev in f: match = re.match('Bus (\\d+) Device (\\d+): (.*)$', dev) @@ -375,142 +398,168 @@ def cmd_usb_insert(options): """ vws usb insert """ address = find_usb(options, get_host_devices()) answer = send_command(options.sock, "usb_add host:%s" % address) - print answer + print(answer) + -def cmd_usb_list(dummy_options): +def cmd_usb_list(_): """ vws usb list - just list all host devices """ for addr, descr in get_host_devices(): - print addr, ": ", descr + print("%s: %s" % (addr, descr)) def cmd_usb_attached(options): """ vws usb attached - list devices assigned to given vm """ for dev in get_vm_devices(options.sock): - print "Address %s : %s" % (dev[0], dev[1]) + print("Address %s : %s" % (dev[0], dev[1])) def cmd_usb_remove(options): """ vws usb remove """ address = find_usb(options, get_vm_devices(options.sock)) answer = send_command(options.sock, "usb_del %s" % address) - print answer + print(answer) + +def make_vm_listing(vmname, dirname, vmtype): + """ makes dict with vm properties for short index """ + f = {"name":vmname, "type":vmtype} + sock = connect_vm(dirname) + if sock is None: + f.update({"state":"stopped", "uri":"-", "ip":"-"}) + f.update(read_netinfo(os.path.join(dirname, "start"))) + else: + uri = spiceurl(sock) + if uri is None: + f.update({"state":"problem", "uri":"None", "ip":"-"}) + f.update(read_netinfo(os.path.join(dirname, "start"))) + else: + f["uri"] = uri[uri.rindex(":")+1:] + f.update(get_netinfo(sock)) + sock.shutdown(socket.SHUT_RDWR) + sock.close() + f["state"] = "running" + return f -def cmd_list(options): - """ vws list """ +def matches(name, patterns): + """ checks if name matches one of patterns """ import fnmatch - count = 0 - search_path = [("private",os.path.join(pwd.getpwuid(os.getuid()).pw_dir, "VWs")), - ("shared",config.get("directories", "SharedVMs")), - ("autostart",config.get("directories", "AutostartVMs"))] + for pattern in patterns: + if fnmatch.fnmatch(name, pattern): + return True + return False - maxlen = 0 - vms = [] +def add_ip_address(listing): + """ Adds IP addresses from ARP into VM listing """ bridges = set() - for (vmtype,dirname) in search_path: - if not os.access(dirname + "/.", os.X_OK): - continue - for vmname in os.listdir(dirname): - if os.access(dirname + "/" + vmname + "/start", os.X_OK): - matches=False - for p in options.pattern: - if fnmatch.fnmatch(vmname,p): - matches=True - break - if not matches: - continue - count += 1 - f = {"name":vmname} - if maxlen < len(vmname): - maxlen = len(vmname) - if options.state: - f["type"]=vmtype - sock = connect_vm(dirname + "/" + vmname) - if sock is None: - f.update({"state":"stopped","uri":"-","ip":"-"}) - f.update(read_netinfo(dirname + "/" + vmname + "/start")) - else: - uri=spiceurl(sock) - if uri is None: - f.update({"state":"problem","uri":"None","ip":"-"}) - f.update(read_netinfo(dirname + "/" + vmname + "/start")) - else: - f["uri"]=uri[uri.rindex(":")+1:] - f.update(get_netinfo(sock)) - if "ip" not in f: - bridges.add(f["iface"]) - sock.shutdown(socket.SHUT_RDWR) - sock.close() - f["state"] = "running" - vms.append(f) - arp_data={} + for vminfo in listing: + if "ip" not in vminfo: + bridges.add(vminfo["iface"]) + arp_data = {} for bridge in bridges: arp_data.update(parse_arp(bridge)) - for f in sorted(vms,key=lambda x: x["name"]): + for vminfo in listing: + if "mac" in vminfo and not "ip" in vminfo: + if vminfo["mac"] in arp_data: + vminfo["ip"] = arp_data[vminfo["mac"]] + else: + vminfo["ip"] = "-" +def all_vms(): + """ + Returns list of tuples vmname, vmtype, directory + for all vms + """ + search_path = [("private", os.path.join(pwd.getpwuid(os.getuid()).pw_dir, + "VWs")), + ("shared", config.get("directories", "SharedVMs")), + ("autostart", config.get("directories", "AutostartVMs"))] + vmlist = [] + for (vmtype, dirname) in search_path: + if not os.access(dirname, os.X_OK): + continue + for vmname in os.listdir(dirname): + if not os.access(os.path.join(dirname, vmname, "start"), os.X_OK): + continue + vmlist.append((vmname, vmtype, os.path.join(dirname, vmname))) + return vmlist +def cmd_list(options): + """ vws list """ + count = 0 + maxlen = 0 + vms = [] + for vmname, vmtype, dirname in all_vms(): + if not matches(vmname, options.pattern): + continue + count += 1 + if maxlen < len(vmname): + maxlen = len(vmname) + if options.state: + vms.append(make_vm_listing(vmname, dirname, vmtype)) + else: + vms.append({"name":vmname}) + if options.state: + add_ip_address(vms) + for f in sorted(vms, key=lambda x: x["name"]): if "state" in f: - if "mac" in f and not "ip" in f: - if f["mac"] in arp_data: - f["ip"] = arp_data[f["mac"]] - else: - f["ip"] = "-" f["name"] = f["name"].ljust(maxlen) - print "%(name)s %(state)s %(type)-9s %(uri)-4s %(iface)-5s %(mac)s %(ip)s " % f + print(("%(name)s %(state)s %(type)-9s %(uri)-4s %(iface)-5s " + + "%(mac)s %(ip)s ") % f) else: - print f["name"] + print(f["name"]) if not count: sys.exit(1) def cmd_screenshot(options): """ vws screenshot """ from os.path import abspath filename = abspath(options.filename) - print send_command(options.sock, "screendump " + filename) + print(send_command(options.sock, "screendump " + filename)) def cmd_record(options): """ vws record """ from os.path import abspath filename = abspath(options.filename) - print send_command(options.sock, "wavcapture " + filename) + print(send_command(options.sock, "wavcapture " + filename)) def cmd_stoprecord(options): """ vws stoprecord """ answer = send_command(options.sock, "info capture") match = re.search('\\[(\\d+)\\]: ', answer) if not match: - print >>sys.stderr, "No sound recording in progress" + print("No sound recording in progress", file=sys.stderr) sys.exit(1) else: - print send_command(options.sock, "stopcapture " + match.group(1)) + print(send_command(options.sock, "stopcapture " + match.group(1))) def cmd_sendkey(options): """ vws sendkey """ - print send_command(options.sock, "sendkey " + options.keyspec) + print(send_command(options.sock, "sendkey " + options.keyspec)) -def cmd_version(dummy_options): +def cmd_version(_): """ vws cersion """ - print VERSION + print(VERSION) def cmd_snapshot(options): """ vws snapshot - create snapshot """ if not options.stopped: - print >>sys.stderr, "Cannot make snapshot of running VW" + print("Cannot make snapshot of running VW", file=sys.stderr) sys.exit(1) drives = get_drives(options.dir) os.chdir(options.dir) newnames = {} for i in drives: name, ext = os.path.splitext(i) newnames[i] = name + "." + options.snapname + ext if os.path.exists(newnames[i]): - print >>sys.stderr, "Snapshot %s already exists", options.snapname + print("Snapshot %s already exists", options.snapname, + file=sys.stderr) return 1 for i in drives: os.rename(i, newnames[i]) os.system("qemu-img create -f qcow2 -b \"%s\" \"%s\"" % (newnames[i], i)) - os.chmod(i,0664) + os.chmod(i, 0o664) return 0 def cmd_snapshots(options): """ vws snapshots - list existing snapshots """ os.chdir(options.dir) @@ -526,12 +575,12 @@ elif line[0] == '\n': lst.append(info) info = {} lst.append(info) for snap in lst: - print "%-30s %+8s %+8s" % (snap["image"], snap["virtual size"], - snap["disk size"]) + print("%-30s %+8s %+8s" % (snap["image"], snap["virtual size"], + snap["disk size"])) def get_backing(drive): """ find if partucular virtual drive has backing file and returns its name @@ -547,24 +596,24 @@ """ reverts to last snapshot: Removes latest snapshot images and creates new ones instead number of snapshots in the stack is not changed by this command """ if not options.stopped: - print >>sys.stderr, "Cannot revert running VW to snapshot" + print("Cannot revert running VW to snapshot", file=sys.stderr) sys.exit(1) os.chdir(options.dir) for drive in get_drives(options.dir): # Check if first has backing file backing = get_backing(drive) if not backing: - print >>sys.stderr, "Drive %s has no snapshots" % drive + print("Drive %s has no snapshots" % drive, file=sys.stderr) continue # Unlink current image os.unlink(drive) # create new image with same backing file os.system('qemu-img create -f qcow2 -b "%s" "%s"' % (backing, drive)) - os.chmod(drive,0664) + os.chmod(drive, 0o664) def cmd_commit(options): """ Commits last snapshot changes into it's backing file There would be one snapshot less for virtual machine @@ -583,104 +632,91 @@ found = 1 os.system('qemu-img commit "%s"'%drive) os.unlink(drive) os.rename(backing, drive) if not found: - print >>sys.stderr, "No snapshots exist for this VM" + print("No snapshots exist for this VM", file=sys.stderr) sys.exit(1) else: if snapshot_mode(options.sock): send_command(options.sock, "commit") else: - print >>sys.stderr, "VM is not running in snapshot mode" + print("VM is not running in snapshot mode", file=sys.stderr) sys.exit(1) -def cmd_autostart(options): +def cmd_autostart(_): """ Starts all VMs which are in the autostart directory """ - dirname = config.get("directories", "AutostartVMs") - userinfo=pwd.getpwnam(config.get("permissions","autostart_user")) + dirname = config.get("directories", "AutostartVMs") + userinfo = pwd.getpwnam(config.get("permissions", "autostart_user")) if not userinfo: - print >>sys.stderr, ("User %s doesn't exists %s" % - config.get("permissions","autostart_user")) + print("User %s doesn't exists %s" % + config.get("permissions", "autostart_user"), file=sys.stderr) sys.exit(1) os.setgid(userinfo.pw_gid) os.setuid(userinfo.pw_uid) - if not os.access(dirname,os.R_OK): - return + if not os.access(dirname, os.R_OK): + return for name in os.listdir(dirname): - if not os.access(os.path.join(dirname,name,"start"), os.X_OK): + if not os.access(os.path.join(dirname, name, "start"), os.X_OK): continue - machine_dir = os.path.join(dirname,name) + machine_dir = os.path.join(dirname, name) sock = connect_vm(machine_dir) if sock: # Machine already running sock.shutdown(socket.SHUT_RDWR) sock.close() continue - start_opts = Namespace(machine = name, - command = 'start', - dir = machine_dir, - snapshot = False, - stopped = True, - password = None, - args = "", - gui = False, - cdrom = None) + start_opts = Namespace(machine=name, command='start', + dir=machine_dir, snapshot=False, + stopped=True, password=None, + args="", gui=False, cdrom=None) try: cmd_start(start_opts) - print name," ", + print(name+" ", end="") finally: # pylint: disable=no-member - if hasattr(start_opts,"sock") and start_opts.sock: + if hasattr(start_opts, "sock") and start_opts.sock: start_opts.sock.shutdown(socket.SHUT_RDWR) - print "" + print("") def cmd_shutdown(options): """ Search for all running machines and stops all of them """ - dirlist=[config.get("directories","AutostartVMs"), - config.get("directories","SharedVms")] - if os.getresuid()[1] == 0: + dirlist = [config.get("directories", "AutostartVMs"), + config.get("directories", "SharedVms")] + if os.getresuid()[1] == 0: import grp dirlist += map(lambda x: os.path.expanduser("~"+x)+"/VWs", - grp.getgrnam(config.get("permissions","vm_group")).gr_mem) + grp.getgrnam(config.get("permissions", + "vm_group")).gr_mem) else: dirlist.append(os.path.expanduser("~")+"/VWs") count = 1 #Fake positive values for there is not postcondition loop in the python - forced_finish=time.time()+options.timeout + forced_finish = time.time() + options.timeout while count > 0: count = 0 if time.time() < forced_finish: command = "system_powerdown" else: command = "quit" - - for dirname in dirlist: - if not os.access(dirname, os.X_OK): - continue - for vm in os.listdir(dirname): - if not os.access(os.path.join(dirname,vm,"start"),os.X_OK): - # Not a VM - continue - sock=connect_vm(os.path.join(dirname,vm)) - if not sock: - # not running - continue - count += 1 - try: - send_command(sock,command) - except IOError: - #When hard_stopping,socket might be closed by way - pass - sock.shutdown(socket.SHUT_RDWR) - sock.close() + for _, _, dirname in all_vms(): + sock = connect_vm(dirname) + if not sock: + # not running + continue + count += 1 + try: + send_command(sock, command) + except IOError: + #When hard_stopping,socket might be closed by way + sock.shutdown(socket.SHUT_RDWR) + sock.close() if not options.wait: return - if count>0: + if count > 0: time.sleep(10) - TEMPLATE = """#!/bin/sh # Get machine name from current directory name NAME=$(basename $(pwd)) # if remote access is enabled, then there should be @@ -720,14 +756,15 @@ -daemonize -pidfile pid {extraargs} chgrp {group} monitor pid chmod 0660 monitor pid """ +BADSIZE = "Invalid size of %s specifed %s. Should have K, M or G suffix" +NOACCEL = "KVM acceleration disabled due to " + def cmd_create(parsed_args): """ vws create - create new VM """ - BADSIZE = "Invalid size of %s specifed %s. Should have K, M or G suffix" - global TEMPLATE if not parsed_args.image and not validate_size(parsed_args.size): raise ValueError(BADSIZE % ("disk", parsed_args.size)) if not validate_size(parsed_args.mem): raise ValueError(BADSIZE % ("memory", parsed_args.size)) drivename = "drive0.qcow2" @@ -736,24 +773,23 @@ "memory":"1024M", "vga":'qxl', "drive":"-drive media=disk,index=0,if={interface},file={image}", "cdrom":"-drive media=cdrom,index=2,if=ide", "sound":"-soundhw hda", - "group":config.get("permissions","vm_group"), + "group":config.get("permissions", "vm_group"), "usb":"-usb", "rtc":"", "extraargs":"${1:+\"$@\"}"} macaddr = ":".join(["%02x" % ord(x) for x in chr(0x52) + os.urandom(5)]) if parsed_args.shared: machinedir = os.path.join(config.get("directories", "SharedVMs"), parsed_args.machine) - dirmode = 0775 + dirmode = 0o775 else: machinedir = os.path.join(pwd.getpwuid(os.getuid()).pw_dir, "VWs", parsed_args.machine) - dirmode = 0775 - + dirmode = 0o775 if parsed_args.net != 'user': bridges = list_bridges() if not parsed_args.net in bridges: raise ValueError("No such bridge %s. Available ones %s" % (parsed_args.net, ", ".join(bridges))) @@ -761,16 +797,15 @@ (macaddr, parsed_args.net)) else: options["net"] = "-net nic,macaddr=%s -net user" % (macaddr,) options["qemubinary"] = 'qemu-system-' + parsed_args.arch options["vga"] = parsed_args.vga - NOACCEL = "KVM acceleration disabled due to " if not parsed_args.arch in ('i386', 'x86_64'): - print >>sys.stderr, NOACCEL + "target architecture" + print(NOACCEL + "target architecture", file=sys.stderr) options.accel = '' elif not os.access("/dev/kvm", os.W_OK): - print >>sys.stderr, NOACCEL + "unavailability on the host system" + print(NOACCEL + "unavailability on the host system", file=sys.stderr) options.accel = '' if not parsed_args.usb: options["usb"] = '' @@ -784,63 +819,63 @@ options["rtc"] = "-rtc base=localtime,clock=host \\\n" if os.path.exists(machinedir): if os.path.exists(os.path.join(machinedir, "start")): raise OSError("Virtual Worstation %s already exists" % - parsed_args.machine) + parsed_args.machine) else: raise OSError("Cannot create VW directory, " + - "something on the way") + "something on the way") # Creating directory for VM os.makedirs(machinedir, dirmode) - parsed_args.dir=machinedir + parsed_args.dir = machinedir if parsed_args.shared: import grp - gid=grp.getgrnam(config.get("permissions","vm_group")).gr_gid - uid=os.getuid() - os.chown(machinedir,uid,gid) - if config.getboolean("permissions","setgid_vm"): - os.chmod(machinedir,02775) + gid = grp.getgrnam(config.get("permissions", "vm_group")).gr_gid + uid = os.getuid() + os.chown(machinedir, uid, gid) + if config.getboolean("permissions", "setgid_vm"): + os.chmod(machinedir, 0o2775) driveopts = {"interface":parsed_args.diskif, "image":drivename} if parsed_args.install: install_image = os.path.abspath(parsed_args.install) if parsed_args.image: # Copying image file - print >>sys.stderr, ("Copying %s to %s" % - (parsed_args.image, - os.path.join(machinedir, drivename))) + print("Copying %s to %s" % + (parsed_args.image, os.path.join(machinedir, drivename)), + file=sys.stderr) os.system("qemu-img convert -O qcow2 -p %s %s" % (parsed_args.image, os.path.join(machinedir, drivename))) os.chdir(machinedir) else: - print >>sys.stderr, "Creating new image file of %s" % parsed_args.size + print("Creating new image file of %s" % parsed_args.size, + file=sys.stderr) os.chdir(machinedir) os.system("qemu-img create -f qcow2 %s %s" % (drivename, parsed_args.size)) - os.chmod(drivename,0664) - # pylint: disable=star-args + os.chmod(drivename, 0o664) options["drive"] = options["drive"].format(**driveopts) - + if hasattr(parsed_args, "debug") and parsed_args.debug: - print repr(driveopts), repr(options["drive"]) - print repr(options) + print(repr(driveopts), repr(options["drive"])) + print(repr(options)) with open("start", "w") as script: script.write(TEMPLATE.format(**options)) os.chmod('start', dirmode) # If installation media is specified vws start for new vm if parsed_args.install: - start_opts = Namespace(machine=parsed_args.machine,password=None, + start_opts = Namespace(machine=parsed_args.machine, password=None, command='start', cdrom=[install_image], dir=machinedir, stopped=True, snapshot=False, args="", gui=True) try: cmd_start(start_opts) finally: # pylint: disable=no-member - if hasattr(start_opts,"sock"): + if hasattr(start_opts, "sock"): start_opts.sock.shutdown(socket.SHUT_RDWR) # # Utility functions for arg parsing # @@ -853,192 +888,207 @@ return parser # # prepare defaults for config # -arch = os.uname()[4] -if re.match("i[3-9]86", arch): - arch = "i386" -elif arch.startswith("arm"): - arch = "arm" - -config = ConfigParser({'SharedVMs':'/var/cache/vws/shared', - 'AutoStartVMs':'/var/cache/vws/autostart'}) -config.add_section('directories') -config.add_section('create options') -for option, value in [('net', 'user'), ('size', '20G'), ('mem', '1G'), - ('diskif', 'virtio'), ('sound', 'hda'), ('arch', arch), - ('vga', 'qxl')]: - config.set('create options', option, value) -config.add_section('tools') -config.set('tools', 'viewer', 'remote-viewer %s') -config.set('tools', 'bridge_list', '/sbin/brctl show') -config.set('tools', 'lsusb', 'lsusb') -config.set('tools', 'arp', '/usr/sbin/arp') -config.add_section('permissions') -config.set('permissions','vm_group','kvm') -config.set('permissions','autostart_user','root') -config.set('permissions','setgid_vm','yes') -# Read configration files -if os.getuid() != 0: - config.read(['/etc/vws.conf',os.path.join(pwd.getpwuid(os.getuid()).pw_dir + '.vwsrc')]) -else: - config.read(['/etc/vws.conf']) -# Parse argument -args = ArgumentParser(description="Manage Virtual Workstations") -cmds = args.add_subparsers(dest='command', help="sub-command help") -p = cmds.add_parser("list", help="List existing VWs", - description="List existing VWs") -p.add_argument("--state", action='store_const', const=True, default=False, - dest='state', help='Show state of the machine') -p.add_argument("--usb", action='store_const', const=True, default=False, - dest='usb', help='Show connected USB devices') -p.add_argument("pattern", nargs='*',default='*',help="Name patterns") -p = cmds.add_parser("version", help="show vws version") -p = cmds.add_parser("autostart",help="Autostart all VMs marked as autostartable") -p = cmds.add_parser("shutdown",help="shut down all running VMs") -p.add_argument("--wait",help="wait until all machines would be shoutdown", - action="store_const", const=True, default=False, dest="wait") -p.add_argument("--timeout",type=int,default=90, - help="how long to way for VMs shutdown before forcing it off") -# Power management -p = new_command(cmds, 'start', help='Start VW and connect to console', - description="Start VW if not running and connect " + - "to the console") -p.add_argument('--no-gui', dest='gui', action='store_const', const=False, - default=True, help='do not open console window') -p.add_argument('--cdrom', metavar='filename.iso', dest='cdrom', nargs=1, - help='connect specified iso image to VMs cdrom on start') -p.add_argument('--args', metavar='string', dest='args', nargs=1, default="", - help="Specify extra QEMU options") -p.add_argument("--snapshot", action='store_const', const=True, default=False, - help="Run without modifying disk image") -p.add_argument("--password", metavar='string', dest='password',nargs=1, - default=None, help="Set password for remote spice connection") -p = new_command(cmds, 'stop', help='Shut down virtual machine', - description="Terminate the VW, gracefully or ungracefully") -p.add_argument('--hard', help='Power off immediately', action='store_const', - dest='hard', const=True, default=False) -new_command(cmds, 'save', help='Save VW state and stop emulation', - description="Save VW state and stop emulation") -new_command(cmds, 'reset', help='Reboot a guest OS', - description="Reboot othe guuest OS") -# Removable devices management -p = new_command(cmds, 'cdrom', help='manage CDROM Drive', - description='"insert" an ISO image into emulated CD-ROM ' + - "or eject it") -p.add_argument('--id', type=str, default=None, - help='Identifier of CDROM drive if VM has more than one') -p.add_argument('file', nargs="?", default='', - help='ISO image or special file to connect to drive') -p.add_argument('--eject', dest='file', action='store_const', const=None) -usb = cmds.add_parser('usb', help='manage USB devices' - ).add_subparsers(dest='subcommand', - help='manage USB devices') -p = new_command(usb, 'insert', help='attach device to the virtual machine') -p.add_argument('pattern', help='Pattern of device name to look up in lsusb') -p.add_argument('--address', type=str, dest='address', nargs=1, - help='exact address bus:device') -p = new_command(usb, 'remove', help='detach connected usb device') -p.add_argument('pattern', help='Pattern of device name to look up in lsusb') -p.add_argument('--address', type=str, dest='address', nargs=1, - help='exact address bus:device') -p = new_command(usb, 'attached', help='list devices attached to vm') -usb.add_parser('list', help='list devices available in the host system') -# Snapshot management -p = new_command(cmds, 'snapshot', help='Create new snapshot') -p.add_argument('snapname', help='snapshot name') -p = new_command(cmds, 'revert', help='Revert to snapshot') -p.add_argument('snapname', help='name of snapshot to revert to') -p = new_command(cmds, 'commit', - help='Commit snapshot changes into backing file') -p = new_command(cmds, 'snapshots', help='List existing snapshots') -# Screenshoits and recording -p = new_command(cmds, 'screenshot', help='take a screenshot') -p.add_argument('filename', help='PPM image filename to write screenshot to') -p = new_command(cmds, 'record', help='Record audio output from VM') -p.add_argument('filename', help='wav file to record autdio to') -new_command(cmds, 'stoprecord', help='stop recording audio') -p = new_command(cmds, 'sendkey', help='Send a keystroke to VM') -p.add_argument('keyspec', help='key specification like ctrl-alt-delete') -# Create new VM -p = new_command(cmds, 'create', help="Create new VW") -p.add_argument("--no-usb", help="Disable USB controller", action='store_const', - const=False, default=True, dest="usb") -p.add_argument("--size", metavar='size', help="Size of primary disk images", - dest="size", default=config.get('create options', 'size')) -p.add_argument("--arch", metavar='cputype', help="Emulated architecture", - dest="arch", default=config.get('create options', 'arch')) -p.add_argument("--no-sound", help="Disable sound card", action='store_const', - const=None, default=config.get('create options', 'sound'), - dest="sound") -p.add_argument('--localtime', action='store_const',const=True,default=False, - help="Show system clock as local time, not UTC to guest OS", - dest='localtime') -p.add_argument("--sound", metavar='cardtype', help="Specify sound card type", - dest='sound', default=config.get('create options', 'sound')) -p.add_argument("--vga", metavar='cardtype', - help="specify video card type (cirrus,std,vmware,qxl) default " + - config.get('create options', 'vga',), dest="vga", - default=config.get('create options', 'vga')) -p.add_argument("--net", help="Network - 'user' or bridge name", - dest='net', default=config.get('create options', 'net')) -p.add_argument("--mem", metavar='size', help="Size of memory", - dest="mem", default=config.get('create options', 'mem')) -p.add_argument("--diskif", metavar='interface-type', - help="Disk interface (virtio, scsi, ide)", - choices=['virtio', 'scsi', 'ide'], - dest="diskif", default=config.get('create options', 'diskif')) -p.add_argument('--shared', help='Create shared VW instead of private one', - action='store_const', const=True, dest='shared', default=False) -p.add_argument('--image', metavar='filename', dest='image', default=None, - help='Existing disk image to import') -p.add_argument('--install', metavar='filename.iso', dest='install', - help='ISO image to install OS from', default=None) -# Miscellenia -p = new_command(cmds, 'monitor', help='connect stdin/stdout to monitor of VM') -p = new_command(cmds, 'spiceuri', help='Output spice URI of machine') - -parsed_args = args.parse_args(sys.argv[1:]) - -os.umask(002) -# Create command is totally different, so it is handled separately -if parsed_args.command == 'create': - try: - cmd_create(parsed_args) - except Exception as e: - print >>sys.stderr,e.message - if hasattr(parsed_args,"dir"): - import shutil - shutil.rmtree(parsed_args.dir) - sys.exit(1) - sys.exit(0) - -funcname = "cmd_" + parsed_args.command -if hasattr(parsed_args, "subcommand"): - funcname += "_" + parsed_args.subcommand -try: - func = globals()[funcname] -except KeyError: - print >>sys.stderr, "Operation %s is not implemented" % funcname - sys.exit(3) - -parsed_args.stopped = False -stopped_vm_commands = ['start', 'snapshot', 'revert', 'commit', 'snapshots'] -if hasattr(parsed_args, 'machine'): - parsed_args.dir = find_vm(parsed_args.machine) - parsed_args.sock = connect_vm(parsed_args.dir) - if parsed_args.sock is None: - if not parsed_args.command in stopped_vm_commands: - print >>sys.stderr, ("Virtual machine %s is not running." % - parsed_args.machine) - sys.exit(1) - else: - parsed_args.stopped = True - -try: - func(parsed_args) -finally: - if hasattr(parsed_args, 'sock') and parsed_args.sock is not None: - parsed_args.sock.shutdown(socket.SHUT_RDWR) - parsed_args.sock.close() - + +config = ConfigParser(interpolation=None) # pylint: disable=invalid-name +def config_defaults(conf): + """ Set default values for config options """ + arch = os.uname()[4] + if re.match("i[3-9]86", arch): + arch = "i386" + elif arch.startswith("arm"): + arch = "arm" + conf.read_dict({'directories':{'SharedVMs':'/var/cache/vws/shared', + 'AutoStartVMs':'/var/cache/vws/autostart'}, + 'create options':{'net':'user', 'size':'20G', 'mem':'1G', + 'diskif':'virtio', 'sound':'hda', + 'arch':arch, 'vga':'qxl'}, + 'tools':{'viewer':'remote-viewer %s', + 'bridge_list':'/sbin/brctl show', + 'lsusb':'lsusb', + 'arp':'/usr/sbin/arp'}, + 'permissions':{'vm_group':'kvm', + 'autostart_user':'root', + 'setgid_vm':'yes'}}) + +def read_config(conf): + """ Read configration files """ + if os.getuid() != 0: + conf.read(['/etc/vws.conf', + os.path.join(pwd.getpwuid(os.getuid()).pw_dir, '.vwsrc')]) + else: + conf.read(['/etc/vws.conf']) +def main(): + """ Parse an arguments and execute everything """ + global config + config_defaults(config) + read_config(config) + # Parse argument + args = ArgumentParser(description="Manage Virtual Workstations") + cmds = args.add_subparsers(dest='command', help="sub-command help") + p = cmds.add_parser("list", help="List existing VWs", + description="List existing VWs") + p.add_argument("-l", "--state", const=True, default=False, dest='state', + action='store_const', help='Show state of the machine') + p.add_argument("--usb", action='store_const', const=True, default=False, + dest='usb', help='Show connected USB devices') + p.add_argument("pattern", nargs='*', default='*', help="Name patterns") + p = cmds.add_parser("version", help="show vws version") + p = cmds.add_parser("autostart", + help="Autostart all VMs marked as autostartable") + p = cmds.add_parser("shutdown", help="shut down all running VMs") + p.add_argument("--wait", help="wait until all machines would be shoutdown", + action="store_const", const=True, default=False, dest="wait") + p.add_argument("--timeout", type=int, default=90, + help="how long to way for VMs shutdown before forcing it off") + # Power management + p = new_command(cmds, 'start', help='Start VW and connect to console', + description="Start VW if not running and connect " + + "to the console") + p.add_argument('--no-gui', dest='gui', action='store_const', const=False, + default=True, help='do not open console window') + p.add_argument('--cdrom', metavar='filename.iso', dest='cdrom', nargs=1, + help='connect specified iso image to VMs cdrom on start') + p.add_argument('--args', metavar='string', dest='args', nargs=1, default="", + help="Specify extra QEMU options") + p.add_argument("--snapshot", action='store_const', const=True, default=False, + help="Run without modifying disk image") + p.add_argument("--password", metavar='string', dest='password', nargs=1, + default=None, help="Set password for remote spice connection") + p = new_command(cmds, 'stop', help='Shut down virtual machine', + description="Terminate the VW, gracefully or ungracefully") + p.add_argument('--hard', help='Power off immediately', action='store_const', + dest='hard', const=True, default=False) + new_command(cmds, 'save', help='Save VW state and stop emulation', + description="Save VW state and stop emulation") + new_command(cmds, 'reset', help='Reboot a guest OS', + description="Reboot othe guuest OS") + # Removable devices management + p = new_command(cmds, 'cdrom', help='manage CDROM Drive', + description='"insert" an ISO image into emulated CD-ROM ' + + "or eject it") + p.add_argument('--id', type=str, default=None, + help='Identifier of CDROM drive if VM has more than one') + p.add_argument('file', nargs="?", default='', + help='ISO image or special file to connect to drive') + p.add_argument('--eject', dest='file', action='store_const', const=None) + usb = cmds.add_parser('usb', help='manage USB devices' + ).add_subparsers(dest='subcommand', + help='manage USB devices') + p = new_command(usb, 'insert', help='attach device to the virtual machine') + p.add_argument('pattern', help='Pattern of device name to look up in lsusb') + p.add_argument('--address', type=str, dest='address', nargs=1, + help='exact address bus:device') + p = new_command(usb, 'remove', help='detach connected usb device') + p.add_argument('pattern', help='Pattern of device name to look up in lsusb') + p.add_argument('--address', type=str, dest='address', nargs=1, + help='exact address bus:device') + p = new_command(usb, 'attached', help='list devices attached to vm') + usb.add_parser('list', help='list devices available in the host system') + # Snapshot management + p = new_command(cmds, 'snapshot', help='Create new snapshot') + p.add_argument('snapname', help='snapshot name') + p = new_command(cmds, 'revert', help='Revert to snapshot') + p.add_argument('snapname', help='name of snapshot to revert to') + p = new_command(cmds, 'commit', + help='Commit snapshot changes into backing file') + p = new_command(cmds, 'snapshots', help='List existing snapshots') + # Screenshoits and recording + p = new_command(cmds, 'screenshot', help='take a screenshot') + p.add_argument('filename', help='PPM image filename to write screenshot to') + p = new_command(cmds, 'record', help='Record audio output from VM') + p.add_argument('filename', help='wav file to record autdio to') + new_command(cmds, 'stoprecord', help='stop recording audio') + p = new_command(cmds, 'sendkey', help='Send a keystroke to VM') + p.add_argument('keyspec', help='key specification like ctrl-alt-delete') + # Create new VM + p = new_command(cmds, 'create', help="Create new VW") + p.add_argument("--no-usb", help="Disable USB controller", action='store_const', + const=False, default=True, dest="usb") + p.add_argument("--size", metavar='size', help="Size of primary disk images", + dest="size", default=config.get('create options', 'size')) + p.add_argument("--arch", metavar='cputype', help="Emulated architecture", + dest="arch", default=config.get('create options', 'arch')) + p.add_argument("--no-sound", help="Disable sound card", action='store_const', + const=None, default=config.get('create options', 'sound'), + dest="sound") + p.add_argument('--localtime', action='store_const', + help="Show system clock as local time, not UTC to guest OS", + const=True, default=False, dest='localtime') + p.add_argument("--sound", metavar='cardtype', dest='sound', + help="Specify sound card type", + default=config.get('create options', 'sound')) + p.add_argument("--vga", metavar='cardtype', + help="specify video card type (cirrus,std,vmware,qxl) " + + "default " + config.get('create options', 'vga',), + dest="vga", default=config.get('create options', 'vga')) + p.add_argument("--net", help="Network - 'user' or bridge name", + dest='net', default=config.get('create options', 'net')) + p.add_argument("--mem", metavar='size', help="Size of memory", + dest="mem", default=config.get('create options', 'mem')) + p.add_argument("--diskif", metavar='interface-type', + help="Disk interface (virtio, scsi, ide)", + choices=['virtio', 'scsi', 'ide'], dest="diskif", + default=config.get('create options', 'diskif')) + p.add_argument('--shared', help='Create shared VW instead of private one', + action='store_const', const=True, dest='shared', + default=False) + p.add_argument('--image', metavar='filename', dest='image', default=None, + help='Existing disk image to import') + p.add_argument('--install', metavar='filename.iso', dest='install', + help='ISO image to install OS from', default=None) + # Miscellenia + p = new_command(cmds, 'monitor', + help='connect stdin/stdout to monitor of VM') + p = new_command(cmds, + 'spiceuri', help='Output spice URI of machine') + + parsed_args = args.parse_args(sys.argv[1:]) + + os.umask(0o002) + # Create command is totally different, so it is handled separately + if parsed_args.command == 'create': + try: + cmd_create(parsed_args) + except Exception as ex: + print(str(ex), file=sys.stderr) + if hasattr(parsed_args, "dir"): + import shutil + shutil.rmtree(parsed_args.dir) + sys.exit(1) + sys.exit(0) + + if parsed_args.command is None: + args.print_help() + sys.exit(0) + funcname = "cmd_" + parsed_args.command + if hasattr(parsed_args, "subcommand"): + funcname += "_" + parsed_args.subcommand + try: + func = globals()[funcname] + except KeyError: + print("Operation %s is not implemented" % funcname, file=sys.stderr) + sys.exit(3) + + parsed_args.stopped = False + stopped_vm_commands = ['start', 'snapshot', 'revert', 'commit', 'snapshots'] + if hasattr(parsed_args, 'machine'): + parsed_args.dir = find_vm(parsed_args.machine) + parsed_args.sock = connect_vm(parsed_args.dir) + if parsed_args.sock is None: + if not parsed_args.command in stopped_vm_commands: + print("Virtual machine %s is not running." % parsed_args.machine, + file=sys.stderr) + sys.exit(1) + else: + parsed_args.stopped = True + + try: + func(parsed_args) + finally: + if hasattr(parsed_args, 'sock') and parsed_args.sock is not None: + parsed_args.sock.shutdown(socket.SHUT_RDWR) + parsed_args.sock.close() + +main()