diff --git a/.travis.yml b/.travis.yml index 5dcbea2..e453fa9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,20 @@ sudo: false + language: python python: - '2.7' - '3.4' - '3.5' - '3.6' +virtualenv: + system_site_packages: true + +addons: + apt: + packages: + - python-libvirt + - python3-libvirt + install: pip install tox-travis + script: tox diff --git a/README.md b/README.md index de1af34..3dcc81d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ Virsh Patcher ![](https://travis-ci.org/PassthroughPOST/virsh-patcher.svg?branch=master) =================== -Simple utility to apply common changes to libvirtd domain xml files. +Simple utility to apply common changes to libvirtd guests. -Can either edit the xml file directly, or apply the patch via a hack around `virsh edit`. +Changes will not be applied if the guest is running. Supported Fixes @@ -36,16 +36,19 @@ $ virshpatcher --error43 --hugepages --host-passthrough win10-guest ``` $ virshpatcher --help -usage: virshpatcher [--error43] [--hugepages] [--host-passthrough] - [--patch PATCH] [--help] - [FILE] +usage: virshpatcher [--connect URI] [--error43] [--hugepages] + [--host-passthrough] [--patch PATCH] [--help] + [--vendor-id ab1234567890] [--random-vendor-id] + [DOMAIN [DOMAIN ...]] libvirtd xml patcher positional arguments: - FILE XML file to edit, or libvirtd domain. + DOMAIN optional arguments: + --connect URI, -c URI + hypervisor connection URI --error43 Add fixes for 'error43' with nvidia devices. --hugepages Make guest use hugepages. --host-passthrough Make guest CPU model `host_passthrough`. @@ -61,7 +64,6 @@ optional arguments: Future Improvements ----------------------- - * Use libvirt API instead of `virsh edit` hack. * Add ability to connect PCI devices to guest (By name/pattern/id?) * Interactive (?) * More tests. diff --git a/setup.py b/setup.py index 2a0c749..de73e50 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup_requires=['pytest-runner'], tests_require=['pytest'], classifiers=[], - install_requires=[], + install_requires=['libvirt-python'], entry_points={ 'console_scripts': [ 'virshpatcher=virshpatcher.main:main', diff --git a/tox.ini b/tox.ini index 4b92c8d..4ecfbd4 100644 --- a/tox.ini +++ b/tox.ini @@ -4,4 +4,5 @@ envlist = py27,py34,py35,py36 [testenv] deps= pytest + libvirt-python commands=py.test diff --git a/virshpatcher/main.py b/virshpatcher/main.py index 7684e8e..ea0eb2a 100644 --- a/virshpatcher/main.py +++ b/virshpatcher/main.py @@ -7,11 +7,24 @@ import subprocess from xml.etree import ElementTree as ET +import libvirt + +RUNNING = { + libvirt.VIR_DOMAIN_RUNNING, + libvirt.VIR_DOMAIN_PAUSED, + libvirt.VIR_DOMAIN_PMSUSPENDED, +} parser = argparse.ArgumentParser( add_help=False, description='libvirtd xml patcher') +parser.add_argument( + '--connect', '-c', + metavar='URI', + help='hypervisor connection URI', + default='qemu:///system') + parser.add_argument( '--error43', help='Add fixes for \'error43\' with nvidia devices.', @@ -66,35 +79,49 @@ def main(): patchers.append(inst) parser.add_argument( - 'file', - help='XML file to edit, or libvirtd domain.', - nargs='?', - metavar='FILE') + 'domains', + metavar='DOMAIN', + nargs='*') args, remainder = parser.parse_known_args(sys.argv[1:]) + + if args.help: parser.print_help() exit(1) - if not args.file: - err("No filename or libvirtd domain specified.") + if not args.domains: + err("No domains provided.") + exit(1) + + if not patchers: + err("No patches provided.") exit(1) - if os.path.exists(args.file): - tree = ET.parse(args.file) + warning = False + + connection = libvirt.open(args.connect) + + for domain in args.domains: + + dom = connection.lookupByName(domain) + state = dom.info() + + if state[0] in RUNNING: + warning = True + err("Warning: Domain '{}' is running. Not applying changes. ".format( + domain)) + continue + + print("Patching: {}".format(domain)) + tree = ET.fromstring(dom.XMLDesc(0)) + for p in patchers: + print("> Applying: {}".format(p.__class__.__name__)) p.patch(tree, args) - tree.write(args.file) - - else: - if args.file != sys.argv[-1]: - err("Libvirt domain must be the last argument provided.") - - os.environ['EDITOR'] = " ".join(sys.argv[:-1]) - cmd = ['virsh', 'edit', args.file] - try: - ret = subprocess.run(cmd, check=True) - except Exception as e: - err("Unable to edit domain {!r}".format(args.file)) - err(e) - exit(1) + + xml_str = ET.tostring(tree).decode() + connection.defineXML(xml_str) + + if warning: + exit(1) diff --git a/virshpatcher/patcher.py b/virshpatcher/patcher.py index ea5d650..b09608a 100644 --- a/virshpatcher/patcher.py +++ b/virshpatcher/patcher.py @@ -12,7 +12,7 @@ def add_arguments(self, parser): def patch(self, tree, args): for patch_set in self.nodes: - node = tree.getroot() + node = tree for tag in patch_set: creator = getattr( @@ -28,6 +28,8 @@ def patch(self, tree, args): new = node.find(tag) if new is None: + if isinstance(node, ET.ElementTree): + node = node.getroot() new = ET.SubElement(node, *creator(args)) patcher(args, new)