virt-xml: Initial commit, basic set of tests

This commit is contained in:
Cole Robinson 2014-01-19 10:37:14 -05:00
parent ffa9bb77b3
commit 748ff1c4cc
41 changed files with 1063 additions and 47 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ po/virt-manager.pot
/man/virt-image.1
/man/virt-convert.1
/man/virt-image.5
/man/virt-xml.1
/virt-manager.spec
/virtcli/cli.cfg

77
man/virt-xml.pod Normal file
View File

@ -0,0 +1,77 @@
=pod
=head1 NAME
virt-xml - XXX
=head1 SYNOPSIS
B<virt-xml> XXX
=head1 DESCRIPTION
B<virt-xml> XXX
=head1 OPTIONS
=over 4
=item -h, --help
Show the help message and exit
=back
=head2 BAR SECTION
=over 2
=item FOO
FOO
=item BAR
BAR
=back
=head2 Miscellaneous Options
=over 2
=item -q, --quiet
Avoid verbose output.
=item -d, --debug
Print debugging information
=item --dry-run
Proceed through the conversion process, but don't convert disks or actually
write any converted files.
=back
=head1 EXAMPLES
XXX
=head1 BUGS
Please see http://virt-manager.org/page/BugReporting
=head1 COPYRIGHT
Copyright (C) Red Hat, Inc, and various contributors.
This is free software. You may redistribute copies of it under the terms
of the GNU General Public License C<http://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.
=head1 SEE ALSO
L<virt-install(1)>, the project website C<http://virt-manager.org>
=cut

View File

@ -32,7 +32,7 @@ def _generate_potfiles_in():
return ret
scripts = ["virt-manager", "virt-install",
"virt-clone", "virt-image", "virt-convert"]
"virt-clone", "virt-image", "virt-convert", "virt-xml"]
potfiles = "\n".join(scripts) + "\n\n"
potfiles += "\n".join(find("virtManager", "*.py")) + "\n\n"
@ -145,7 +145,7 @@ class my_build(build):
def _make_bin_wrappers(self):
cmds = ["virt-manager", "virt-install", "virt-clone",
"virt-image", "virt-convert"]
"virt-image", "virt-convert", "virt-xml"]
if not os.path.exists("build"):
os.mkdir("build")
@ -559,7 +559,7 @@ class CheckPylint(Command):
def run(self):
files = ["setup.py", "virt-install", "virt-clone", "virt-image",
"virt-convert", "virt-manager",
"virt-convert", "virt-xml", "virt-manager",
"virtcli", "virtinst", "virtconv", "virtManager",
"tests"]
@ -590,7 +590,8 @@ setup(
"build/virt-clone",
"build/virt-install",
"build/virt-image",
"build/virt-convert"]),
"build/virt-convert",
"build/virt-xml"]),
data_files=[
("share/virt-manager/", [
@ -599,6 +600,7 @@ setup(
"virt-clone",
"virt-image",
"virt-convert",
"virt-xml",
]),
("share/glib-2.0/schemas",
["data/org.virt-manager.virt-manager.gschema.xml"]),
@ -609,7 +611,8 @@ setup(
"man/virt-install.1",
"man/virt-clone.1",
"man/virt-image.1",
"man/virt-convert.1"
"man/virt-convert.1",
"man/virt-xml.1"
]),
("share/man/man5", ["man/virt-image.5"]),

View File

@ -70,6 +70,7 @@ virtinstall = _import("virtinstall", "virt-install")
virtimage = _import("virtimage", "virt-image")
virtclone = _import("virtclone", "virt-clone")
virtconvert = _import("virtconvert", "virt-convert")
virtxml = _import("virtxml", "virt-xml")
# Variable used to store a local iso or dir path to check for a distro
# Specified via 'python setup.py test_urls --path"

View File

@ -0,0 +1,30 @@
--- Original XML
+++ Altered XML
@@ -265,21 +265,25 @@
<vendor id="0x04b3"/>
<product id="0x4485"/>
</source>
+ <driver name="vfio"/>
</hostdev>
<hostdev mode="subsystem" type="usb" managed="yes">
<source>
<address bus="3" device="2"/>
</source>
+ <driver name="vfio"/>
</hostdev>
<hostdev mode="subsystem" type="pci" managed="yes">
<source>
<address domain="0x0000" bus="0x00" slot="0x19" function="0x0"/>
</source>
+ <driver name="vfio"/>
</hostdev>
<hostdev mode="subsystem" type="pci" managed="yes">
<source>
<address domain="0x0003" bus="0x00" slot="0x19" function="0x0"/>
</source>
+ <driver name="vfio"/>
</hostdev>
<redirdev bus="usb" type="tcp">
<source mode="connect" host="localhost" service="4000"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -258,7 +258,7 @@
<model type="vmvga" vram="9216" heads="1"/>
</video>
<video>
- <model type="cirrus" vram="10240" heads="3"/>
+ <model type="qxl" vram="10240" heads="3"/>
</video>
<hostdev mode="subsystem" type="usb" managed="yes">
<source>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -253,7 +253,7 @@
</graphics>
<sound model="sb16"/>
<sound model="es1370"/>
- <sound model="ich6"/>
+ <sound model="pcspk"/>
<video>
<model type="vmvga" vram="9216" heads="1"/>
</video>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -65,7 +65,7 @@
<address type="drive" controller="0" bus="0" target="0" unit="1"/>
</disk>
<disk type="file" device="disk">
- <source file="/tmp/foobar"/>
+ <source file="/dev/null"/>
<target dev="hda" bus="ide"/>
<iotune>
<read_bytes_sec>5242880</read_bytes_sec>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -159,7 +159,7 @@
<interface type="ethernet">
<mac address="00:11:7f:33:44:55"/>
<script path="/etc/qemu-ifup"/>
- <target dev="nic02"/>
+ <target dev="nic55"/>
</interface>
<interface type="direct">
<mac address="f0:11:22:33:44:5f"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -253,7 +253,7 @@
</graphics>
<sound model="sb16"/>
<sound model="es1370"/>
- <sound model="ich6"/>
+ <sound model="pcspk"/>
<video>
<model type="vmvga" vram="9216" heads="1"/>
</video>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,17 @@
--- Original XML
+++ Altered XML
@@ -15,8 +15,10 @@
</numatune>
<os>
<type arch="i686">hvm</type>
- <loader>/usr/lib/xen/boot/hvmloader</loader>
- <boot dev="hd"/>
+ <loader>foo.bar</loader>
+ <boot dev="network"/>
+ <bios useserial="yes"/>
+ <init>/bin/bash</init>
</os>
<features>
<acpi/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -214,7 +214,7 @@
<console type="pty">
<target type="uml" port="1"/>
</console>
- <channel type="pipe">
+ <channel type="null">
<source path="/tmp/guestfwd"/>
<target type="guestfwd" address="10.0.2.1" port="4600"/>
</channel>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,19 @@
--- Original XML
+++ Altered XML
@@ -44,10 +44,11 @@
<feature policy="require" name="xtpr"/>
<feature policy="require" name="acpi"/>
</cpu>
- <clock offset="utc">
- <timer name="rtc" tickpolicy="catchup"/>
+ <clock offset="localtime">
+ <timer name="rtc" tickpolicy="merge"/>
<timer name="pit" tickpolicy="delay"/>
- <timer name="hpet" present="no"/>
+ <timer name="hpet" present="yes"/>
+ <timer name="kvmclock" present="no"/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -209,7 +209,7 @@
<target port="2"/>
</parallel>
<console type="pty">
- <target type="virtio" port="0"/>
+ <target type="serial" port="0"/>
</console>
<console type="pty">
<target type="uml" port="1"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -98,7 +98,7 @@
<source file="/tmp/foobar4"/>
<target dev="xvdc" bus="xen"/>
</disk>
- <controller type="scsi" index="0" model="virtio-scsi"/>
+ <controller type="scsi" index="2" model="lsilogic"/>
<controller type="usb" index="0"/>
<controller type="fdc" index="0"/>
<controller type="ide" index="0"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,24 @@
--- Original XML
+++ Altered XML
@@ -28,9 +28,9 @@
</hyperv>
</features>
<cpu mode="custom" match="exact">
- <model fallback="allow">core2duo</model>
+ <model fallback="allow">pentium2</model>
<vendor>Intel</vendor>
- <feature policy="require" name="pbe"/>
+ <feature policy="forbid" name="pbe"/>
<feature policy="require" name="tm2"/>
<feature policy="require" name="est"/>
<feature policy="require" name="ss"/>
@@ -43,6 +43,7 @@
<feature policy="require" name="ds_cpl"/>
<feature policy="require" name="xtpr"/>
<feature policy="require" name="acpi"/>
+ <feature name="x2apic" policy="force"/>
</cpu>
<clock offset="utc">
<timer name="rtc" tickpolicy="catchup"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,16 @@
--- Original XML
+++ Altered XML
@@ -55,9 +55,10 @@
<devices>
<emulator>/usr/lib/xen/bin/qemu-dm</emulator>
<disk type="block" device="floppy">
- <source dev="/dev/null"/>
+ <source dev="/dev/zero" startupPolicy="optional"/>
<target dev="fda" bus="fdc"/>
<address type="drive" controller="0" bus="0" target="0" unit="0"/>
+ <readonly/>
</disk>
<disk type="dir" device="floppy">
<source dir="/tmp"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,17 @@
--- Original XML
+++ Altered XML
@@ -19,10 +19,9 @@
<boot dev="hd"/>
</os>
<features>
- <acpi/>
- <apic eoi="off"/>
+ <apic eoi="on"/>
<hyperv>
- <relaxed state="on"/>
+ <relaxed state="off"/>
<vapic state="on"/>
<spinlocks state="on" retries="12287"/>
</hyperv>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,18 @@
--- Original XML
+++ Altered XML
@@ -105,10 +105,10 @@
<controller type="virtio-serial" index="1"/>
<controller type="virtio-serial" index="0"/>
<controller type="ccid" index="0"/>
- <filesystem type="mount" accessmode="passthrough">
+ <filesystem type="mount" accessmode="mapped">
<driver type="handle"/>
- <source dir="/foo/bar"/>
- <target dir="/bar/baz"/>
+ <source dir="/1/2/3"/>
+ <target dir="/4/5/6"/>
</filesystem>
<filesystem type="template" accessmode="passthrough">
<source name="template_fedora"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -238,7 +238,7 @@
</backend>
</tpm>
<input type="mouse" bus="ps2"/>
- <graphics type="sdl" display=":3.4" xauth="/testdir/.Xauthority" fullscreen="yes"/>
+ <graphics type="sdl" display=":3.4" xauth="/testdir/.Xauthority" fullscreen="yes" tlsPort="5902" keymap="ja"/>
<graphics type="vnc" port="-1" autoport="yes"/>
<graphics type="vnc" port="-1" autoport="yes" listen="1.2.3.4" keymap="fi">
<listen type="address" address="1.2.3.4"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,17 @@
--- Original XML
+++ Altered XML
@@ -262,9 +262,10 @@
</video>
<hostdev mode="subsystem" type="usb" managed="yes">
<source>
- <vendor id="0x04b3"/>
- <product id="0x4485"/>
+ <vendor id="0x0781"/>
+ <product id="0x5151"/>
</source>
+ <driver name="vfio"/>
</hostdev>
<hostdev mode="subsystem" type="usb" managed="yes">
<source>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -293,7 +293,7 @@
<usbdev allow="no"/>
</redirfilter>
<watchdog model="ib700" action="poweroff"/>
- <memballoon model="virtio"/>
+ <memballoon model="none"/>
<rng model="virtio">
<rate bytes="1234" period="2000"/>
<backend model="egd" type="tcp">
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,19 @@
--- Original XML
+++ Altered XML
@@ -124,11 +124,10 @@
<source dir="/foo/bar"/>
<target dir="/bar/baz"/>
</filesystem>
- <interface type="network">
- <mac address="22:22:33:54:32:10"/>
- <source network="default"/>
+ <interface type="bridge">
+ <source bridge="br0"/>
<target dev="testnet0"/>
- <model type="e1000"/>
+ <model type="virtio"/>
</interface>
<interface type="network">
<mac address="22:11:11:11:11:11"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -11,7 +11,7 @@
</memoryBacking>
<vcpu placement="static" cpuset="1-2,5-9,11,13-14">9</vcpu>
<numatune>
- <memory mode="interleave" placement="auto"/>
+ <memory mode="strict" placement="auto" nodeset="1-5,7"/>
</numatune>
<os>
<type arch="i686">hvm</type>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,15 @@
--- Original XML
+++ Altered XML
@@ -195,8 +195,8 @@
<protocol type="telnet"/>
<target port="1"/>
</serial>
- <parallel type="file">
- <source path="/tmp/foo.log"/>
+ <parallel type="unix">
+ <source path="/some/other/log"/>
<target port="0"/>
</parallel>
<parallel type="unix">
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,15 @@
--- Original XML
+++ Altered XML
@@ -281,8 +281,8 @@
<address domain="0x0003" bus="0x00" slot="0x19" function="0x0"/>
</source>
</hostdev>
- <redirdev bus="usb" type="tcp">
- <source mode="connect" host="localhost" service="4000"/>
+ <redirdev bus="usb" type="spicevmc">
+ <source mode="connect" host="example.com" service="12345"/>
<protocol type="raw"/>
</redirdev>
<redirdev bus="usb" type="spicevmc">
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -295,7 +295,7 @@
<watchdog model="ib700" action="poweroff"/>
<memballoon model="virtio"/>
<rng model="virtio">
- <rate bytes="1234" period="2000"/>
+ <rate bytes="3333" period="4444"/>
<backend model="egd" type="tcp">
<source mode="connect" host="1.2.3.4" service="1234"/>
<protocol type="raw"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,12 @@
--- Original XML
+++ Altered XML
@@ -302,4 +302,7 @@
</backend>
</rng>
</devices>
+ <seclabel relabel="yes">
+ <label>foo,bar,baz</label>
+ </seclabel>
</domain>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -187,7 +187,7 @@
<protocol type="raw"/>
<address type="ccid" controller="0" slot="3"/>
</smartcard>
- <serial type="null">
+ <serial type="pty">
<target port="0"/>
</serial>
<serial type="tcp">
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -170,7 +170,7 @@
<target dev="testnet6"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x07" function="0x0"/>
</interface>
- <smartcard mode="host">
+ <smartcard mode="host" type="spicevmc">
<address type="ccid" controller="0" slot="0"/>
</smartcard>
<smartcard mode="host-certificates">
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -251,7 +251,7 @@
<channel name="inputs" mode="secure"/>
<channel name="record" mode="insecure"/>
</graphics>
- <sound model="sb16"/>
+ <sound model="pcspk"/>
<sound model="es1370"/>
<sound model="ich6"/>
<video>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -234,7 +234,7 @@
<input type="tablet" bus="usb"/>
<tpm model="tpm-tis">
<backend type="passthrough">
- <device path="/dev/tzz"/>
+ <device path="/dev/tpm"/>
</backend>
</tpm>
<input type="mouse" bus="ps2"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,21 @@
--- Original XML
+++ Altered XML
@@ -9,7 +9,7 @@
<memoryBacking>
<hugepages/>
</memoryBacking>
- <vcpu placement="static" cpuset="1-2,5-9,11,13-14">9</vcpu>
+ <vcpu placement="static" cpuset="1-2,5-9,11,13-14" current="10">20</vcpu>
<numatune>
<memory mode="interleave" placement="auto"/>
</numatune>
@@ -43,6 +43,7 @@
<feature policy="require" name="ds_cpl"/>
<feature policy="require" name="xtpr"/>
<feature policy="require" name="acpi"/>
+ <topology sockets="4" cores="5" threads="1"/>
</cpu>
<clock offset="utc">
<timer name="rtc" tickpolicy="catchup"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -255,7 +255,7 @@
<sound model="es1370"/>
<sound model="ich6"/>
<video>
- <model type="vmvga" vram="9216" heads="1"/>
+ <model type="cirrus" vram="9216" heads="1"/>
</video>
<video>
<model type="cirrus" vram="10240" heads="3"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,13 @@
--- Original XML
+++ Altered XML
@@ -292,7 +292,7 @@
<usbdev class="0x08" vendor="0x15E1" product="0x2007" version="1.10" allow="yes"/>
<usbdev allow="no"/>
</redirfilter>
- <watchdog model="ib700" action="poweroff"/>
+ <watchdog model="ib700" action="reset"/>
<memballoon model="virtio"/>
<rng model="virtio">
<rate bytes="1234" period="2000"/>
Domain 'test-many-devices' defined successfully.

View File

@ -0,0 +1,17 @@
<domain type="test" id="1">
<name>test</name>
<uuid>4a64cc71-19c4-2fd0-2323-3050941ea3c3</uuid>
<memory unit="KiB">8388608</memory>
<currentMemory unit="KiB">2097152</currentMemory>
<vcpu placement="static">7</vcpu>
<os>
<type arch="i686">hvm</type>
<boot dev="hd"/>
</os>
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
</devices>
</domain>

View File

@ -30,9 +30,12 @@ import StringIO
import virtinst.cli
from virtinst import support
from tests import virtinstall, virtimage, virtclone, virtconvert
from tests import virtinstall, virtimage, virtclone, virtconvert, virtxml
from tests import utils
# Enable this to refresh test output
REGENERATE_OUTPUT = False
os.environ["VIRTCONV_TEST_NO_DISK_CONVERSION"] = "1"
os.environ["LANG"] = "en_US.UTF-8"
@ -185,6 +188,8 @@ class Command(object):
ret = virtimage.main(conn=conn)
elif app.count("virt-convert"):
ret = virtconvert.main()
elif app.count("virt-xml"):
ret = virtxml.main()
except SystemExit, sys_e:
ret = sys_e.code
@ -244,7 +249,7 @@ class Command(object):
if filename:
# Generate test files that don't exist yet
if not os.path.exists(filename):
if REGENERATE_OUTPUT or not os.path.exists(filename):
file(filename, "w").write(output)
utils.diff_compare(output, filename)
@ -260,23 +265,40 @@ class PromptCheck(object):
"""
Individual question/response pair for automated --prompt tests
"""
def __init__(self, prompt, response=None):
def __init__(self, prompt, response=None, num_lines=1):
self.prompt = prompt
self.response = response
if self.response:
self.response = self.response % test_files
self.num_lines = num_lines
self._output = None
def check(self, proc):
out = proc.stdout.readline()
timeout = 3
def _set_output():
self._output = ""
for ignore in range(self.num_lines):
self._output += proc.stdout.readline()
if not out.count(self.prompt):
out += "\nContent didn't contain prompt '%s'" % (self.prompt)
return False, out
import threading
thread = threading.Thread(target=_set_output)
thread.start()
thread.join(timeout)
if thread.isAlive():
proc.terminate()
return False, self._output + "\nProcess hung on readline()"
if not self._output.count(self.prompt):
self._output += ("\nContent didn't contain prompt '%s'" %
(self.prompt))
return False, self._output
if self.response:
proc.stdin.write(self.response + "\n")
return True, out
return True, self._output
class PromptTest(Command):
@ -725,6 +747,54 @@ c.add_invalid("--hvm --boot kernel=%(TREEDIR)s/pxeboot/vmlinuz,initrd=%(TREEDIR)
vixml = App("virt-xml")
c = vixml.add_category("misc", "")
c.add_valid("--help") # basic --help test
c.add_valid("--soundhw=? --tpm=?") # basic introspection test
c.add_invalid("--domain test --edit --hostdev driver_name=vfio") # Guest has no hostdev to edit
c.add_invalid("--domain test --edit --cpu host-passthrough --boot hd,network") # Specified more than 1 option
c.add_invalid("--domain test --edit") # specified no edit option
c.add_invalid("--domain test --edit 2 --cpu host-passthrough") # specifing --edit number where it doesn't make sense
c.add_invalid("--domain test-many-devices --edit 5 --tpm /dev/tpm") # device edit out of range
c.add_compare("--domain test --print-xml --edit --vcpus 7", "virtxml-print-xml") # test --print-xml
c = vixml.add_category("simple edit diff", "--domain test-many-devices --edit --print-diff --define")
c.add_compare("--vcpus 10,maxvcpus=20,cores=5,sockets=4,threads=1", "virtxml-edit-simple-vcpus")
c.add_compare("--cpu model=pentium2,+x2apic,forbid=pbe", "virtxml-edit-simple-cpu")
c.add_compare("--numatune 1-5,7,mode=strict", "virtxml-edit-simple-numatune")
c.add_compare("--boot loader=foo.bar,network,useserial=on,init=/bin/bash", "virtxml-edit-simple-boot")
c.add_compare("--security label=foo,bar,baz,relabel=on", "virtxml-edit-simple-security")
c.add_compare("--features eoi=on,hyperv_relaxed=off,acpi=", "virtxml-edit-simple-features")
c.add_compare("--clock offset=localtime,hpet_present=yes,kvmclock_present=no,rtc_tickpolicy=merge", "virtxml-edit-simple-clock")
c.add_compare("--disk /dev/zero,perms=ro,startup_policy=optional", "virtxml-edit-simple-disk")
c.add_compare("--network source=br0,type=bridge,model=virtio,mac=", "virtxml-edit-simple-network")
c.add_compare("--graphics tlsport=5902,keymap=ja", "virtxml-edit-simple-graphics")
c.add_compare("--controller index=2,model=lsilogic", "virtxml-edit-simple-controller")
c.add_compare("--smartcard type=spicevmc", "virtxml-edit-simple-smartcard")
c.add_compare("--redirdev type=spicevmc,server=example.com:12345", "virtxml-edit-simple-redirdev")
c.add_compare("--tpm path=/dev/tpm", "virtxml-edit-simple-tpm")
c.add_compare("--rng rate_bytes=3333,rate_period=4444", "virtxml-edit-simple-rng")
c.add_compare("--watchdog action=reset", "virtxml-edit-simple-watchdog")
c.add_compare("--memballoon model=none", "virtxml-edit-simple-memballoon")
c.add_compare("--serial pty", "virtxml-edit-simple-serial")
c.add_compare("--parallel unix,path=/some/other/log", "virtxml-edit-simple-parallel")
c.add_compare("--channel null", "virtxml-edit-simple-channel")
c.add_compare("--console target_type=serial", "virtxml-edit-simple-console")
c.add_compare("--filesystem /1/2/3,/4/5/6,mode=mapped", "virtxml-edit-simple-filesystem")
c.add_compare("--video cirrus", "virtxml-edit-simple-video")
c.add_compare("--soundhw pcspk", "virtxml-edit-simple-soundhw")
c.add_compare("--host-device 0x0781:0x5151,driver_name=vfio", "virtxml-edit-simple-host-device")
c = vixml.add_category("edit selection", "--domain test-many-devices --print-diff --define")
c.add_invalid("--edit target=vvv --disk /dev/null") # no match found
c.add_compare("--edit 3 --soundhw pcspk", "virtxml-edit-pos-num")
c.add_compare("--edit -1 --video qxl", "virtxml-edit-neg-num")
c.add_compare("--edit all --host-device driver_name=vfio", "virtxml-edit-all")
c.add_compare("--edit ich6 --soundhw pcspk", "virtxml-edit-select-sound-model")
c.add_compare("--edit target=hda --disk /dev/null", "virtxml-edit-select-disk-target")
c.add_compare("--edit mac=00:11:7f:33:44:55 --network target=nic55", "virtxml-edit-select-network-mac")
vimag = App("virt-image")
c = vimag.add_category("graphics", "--name test-image --boot 0 %(IMAGE_XML)s")
c.add_valid("--sdl") # SDL
@ -887,6 +957,10 @@ p7.add("'/root' must be a file or a device")
p7.add("use as the cloned disk", "%(MANAGEDNEW1)s")
promptlist.append(p7)
p8 = PromptTest("virt-xml --connect %(TESTURI)s --confirm --domain test "
"--edit --cpu host-passthrough")
p8.add("Define 'test' with the changed XML", "yes", num_lines=12)
promptlist.append(p8)
#########################
@ -949,6 +1023,7 @@ _cmdlist += vinst.cmds
_cmdlist += vclon.cmds
_cmdlist += vimag.cmds
_cmdlist += vconv.cmds
_cmdlist += vixml.cmds
for _cmd in _cmdlist:
newidx += 1

View File

@ -71,13 +71,26 @@
yeah boii &lt; &gt; yeahfoo
</description>
<features>
<acpi/><apic/>
<acpi/>
<apic eoi='off'/>
<hyperv>
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' retries='12287'/>
</hyperv>
</features>
<clock offset="utc"/>
<clock offset="utc">
<timer name="rtc" tickpolicy="catchup"/>
<timer name="pit" tickpolicy="delay"/>
<timer name="hpet" present="no"/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<vcpu cpuset="1,2,5-9,11,13-14">9</vcpu>
<numatune>
<memory mode='interleave' placement='auto'/>
</numatune>
<cpu match="exact">
<model>core2duo</model>
<vendor>Intel</vendor>
@ -148,14 +161,13 @@
</interface>
<interface type='network'>
<source network='idontexistfool'/>
<mac address='22:11:11:11:11:11'/>
</interface>
<interface type='network'>
<source network='dupmac'/>
<mac address='22:22:33:54:32:11'/>
</interface>
<interface type='user'>
<mac address='22:11:11:11:11:11'/>
</interface>
<interface type='user'/>
<interface type='bridge'>
<source bridge='brempty'/>
<mac address='22:22:33:44:AA:BB'/>
@ -166,9 +178,8 @@
<mac address='22:00:00:44:AA:BF'/>
<model type='e1000'/>
</interface>
<controller type='usb'/>
<interface type='ethernet'>
<mac address='00:11:7F:33:44:55'/>
<mac address='00:11:7f:33:44:55'/>
<script path='/etc/qemu-ifup'/>
<target dev='nic02'/>
</interface>
@ -200,6 +211,9 @@
<channel name="record" mode="insecure"/>
</graphics>
<!-- controller devices -->
<controller type='scsi' model='virtio-scsi'/>
<controller type='usb'/>
<!-- sound devices -->
<sound model='sb16'/>
@ -363,6 +377,9 @@
<panic>
<address type='isa' iobase='0x505'/>
</panic>
<!-- memballoon device -->
<memballoon model='virtio'/>
</devices>
</domain>

View File

@ -93,6 +93,7 @@ Provides: virt-install
Provides: virt-clone
Provides: virt-image
Provides: virt-convert
Provides: virt-xml
Obsoletes: python-virtinst
%description -n virt-install
@ -194,6 +195,7 @@ fi
%{_mandir}/man1/virt-install.1*
%{_mandir}/man1/virt-clone.1*
%{_mandir}/man1/virt-convert.1*
%{_mandir}/man1/virt-xml.1*
%{_mandir}/man1/virt-image.1*
%{_mandir}/man5/virt-image.5*
@ -201,11 +203,13 @@ fi
%{_datadir}/%{name}/virt-clone
%{_datadir}/%{name}/virt-image
%{_datadir}/%{name}/virt-convert
%{_datadir}/%{name}/virt-xml
%{_bindir}/virt-install
%{_bindir}/virt-clone
%{_bindir}/virt-image
%{_bindir}/virt-convert
%{_bindir}/virt-xml
%changelog

283
virt-xml Executable file
View File

@ -0,0 +1,283 @@
#!/usr/bin/python -tt
#
# Copyright 2013 Red Hat, Inc.
# Cole Robinson <crobinso@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
import difflib
import logging
import os
import sys
import libvirt
import virtinst
from virtinst import cli
from virtinst.cli import fail, print_stdout, print_stderr
###################
# Utility helpers #
###################
def prompt_yes_or_no(msg):
while 1:
printmsg = msg + " (y/n): "
if "VIRTINST_TEST_SUITE" in os.environ:
printmsg += "\n"
sys.stdout.write(printmsg)
sys.stdout.flush()
inp = sys.stdin.readline().lower().strip()
if inp in ["y", "yes"]:
return True
elif inp in ["n", "no"]:
return False
else:
print_stdout(_("Please enter 'yes' or 'no'."))
def get_diff(origxml, newxml):
ret = "".join(difflib.unified_diff(origxml.splitlines(1),
newxml.splitlines(1),
fromfile="Original XML",
tofile="Altered XML"))
if ret:
logging.debug("XML diff:\n%s", ret)
else:
logging.debug("No XML diff, didn't generate any change.")
return ret
def get_domain_and_guest(conn, domstr):
if not domstr:
fail("--domain must be specified")
try:
int(domstr)
isint = True
except ValueError:
isint = False
try:
virtinst.util.validate_uuid(domstr)
isuuid = True
except ValueError:
isuuid = False
try:
if isint:
domain = conn.lookupByID(int(domstr))
elif isuuid:
domain = conn.lookupByUUIDString(domstr)
else:
domain = conn.lookupByName(domstr)
except libvirt.libvirtError, e:
fail(_("Could not find domain '%s': %s") % (domstr, e))
# XXX: Require this for first pass, but it sucks for testing
#if domain.info()[0] != libvirt.VIR_DOMAIN_SHUTOFF:
# fail(_("Domain '%s' must be shutoff.") % domain.name())
# XXX: any flags to use here? INACTIVE prob, though not secure or cpu
xml = domain.XMLDesc(0)
# We do this to minimize the diff, removing things like ' -> "
xml = virtinst.Guest(conn, parsexml=xml).get_xml_config()
return (domain, xml, virtinst.Guest(conn, parsexml=xml))
################
# Change logic #
################
def _find_devices_to_edit(guest, options, parserobj):
devlist = guest.get_devices(parserobj.devclass.virtual_device_type)
idx = None
if options.edit is None:
idx = 1
elif (options.edit.isdigit() or
options.edit.startswith("-") and options.edit[1:].isdigit()):
idx = int(options.edit)
if idx is not None:
if idx == 0:
fail(_("Invalid --edit option '%s'") % options.edit)
if not devlist:
fail(_("No --%s devices found in the XML") %
parserobj.cli_arg_name)
if len(devlist) < abs(idx):
fail(_("--edit %s requested but there's only %s "
"--%s devices in the XML") %
(idx, len(devlist), parserobj.cli_arg_name))
if idx > 0:
idx -= 1
inst = devlist[idx]
elif options.edit == "all":
inst = devlist[:]
else:
inst = parserobj.lookup_device_from_option_string(guest, options.edit)
if not inst:
fail(_("No matching devices found for --edit %s") % options.edit)
return inst
def change_xml(guest, options, parsermap):
# XXX: Make sure actions don't conflict
# XXX: Make sure XML options don't conflict
# XXX: Find a way to factor out whatever defaults there are
if options.edit is -1:
fail("--edit must be specified")
collisions = []
for option_variable_name, parserobj in parsermap.items():
if getattr(options, option_variable_name):
collisions.append(parserobj)
if len(collisions) == 0:
fail(_("No change specified."))
if len(collisions) != 1:
fail(_("Only one change operation may be specified "
"(conflicting options %s)") %
["--" + c.cli_arg_name for c in collisions])
parserobj = collisions[0]
if parserobj.devclass:
inst = _find_devices_to_edit(guest, options, parserobj)
else:
inst = guest
if options.edit and options.edit != '1' and options.edit != 'all':
fail(_("'--edit %s' doesn't make sense with --%s, "
"just use empty '--edit'") %
(options.edit, parserobj.cli_arg_name))
cli.parse_option_strings(parsermap, options, guest, inst)
#######################
# CLI option handling #
#######################
def parse_args():
# XXX: man page: mention introspection if it makes sense
# XXX: expand usage
# XXX: notes about the default actions, behavior, etc
parser = cli.setupParser(
"%(prog)s [options]",
_("Edit libvirt XML using command line options."),
introspection_epilog=True)
cli.add_connect_option(parser)
actg = parser.add_argument_group(_("Action Options"))
actg.add_argument("--domain", help=_("Domain name, id, or uuid"))
actg.add_argument("--edit", nargs='?', default=-1,
help=_("Edit VM XML"))
g = parser.add_argument_group(_("XML options"))
cli.add_disk_option(g)
cli.add_net_option(g)
cli.add_gfx_option(g)
cli.vcpu_cli_options(g)
cli.add_guest_xml_options(g)
cli.add_boot_option(g)
cli.add_fs_option(g)
cli.add_device_options(g)
misc = parser.add_argument_group(_("Miscellaneous Options"))
cli.add_misc_options(misc, prompt=False, printxml=False, dryrun=False)
misc.add_argument("--print-diff", action="store_true",
help=_("Only print the requested change, in diff format"))
misc.add_argument("--print-xml", action="store_true",
help=_("Only print the requested change, in full XML format"))
misc.add_argument("--confirm", action="store_true",
help=_("Require confirmation before saving any results."))
misc.add_argument("--define", action="store_true",
help=_("Force defining the domain, only required if a --print "
"option was specified."))
return parser.parse_args()
###################
# main() handling #
###################
def main(conn=None):
cli.earlyLogging()
options = parse_args()
if options.confirm or options.print_xml or options.print_diff:
options.quiet = False
if not options.print_xml and not options.print_diff:
options.define = True
if options.confirm and not options.print_xml:
options.print_diff = True
cli.setupLogging("virt-xml", options.debug, options.quiet)
parsermap = cli.build_parser_map(options)
if cli.check_option_introspection(options, parsermap):
return 0
if conn is None:
conn = cli.getConnection(options.connect)
domain, origxml, guest = get_domain_and_guest(conn, options.domain)
# XXX: do we ever need the domain?
ignore = domain
change_xml(guest, options, parsermap)
newxml = guest.get_xml_config()
diff = get_diff(origxml, newxml)
if options.print_diff:
if diff:
print_stdout(diff)
elif options.print_xml:
print_stdout(newxml)
if not options.define:
return 0
if options.confirm:
# XXX: Message needs to depend on what action we will take
if not prompt_yes_or_no(
_("Define '%s' with the changed XML?" % guest.name)):
return 0
conn.defineXML(guest.get_xml_config())
print_stdout(_("Domain '%s' defined successfully." % guest.name))
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except SystemExit, sys_e:
sys.exit(sys_e.code)
except KeyboardInterrupt:
logging.debug("", exc_info=True)
print_stderr(_("Aborted at user request"))
except Exception, main_e:
fail(main_e)

View File

@ -974,39 +974,39 @@ class _VirtCLIArgument(object):
self.ignore_default = ignore_default
def parse(self, opts, inst, support_cb=None):
def parse(self, opts, inst, support_cb=None, lookup=False):
val = opts.get_opt_param(self.cliname)
if val is None:
return
if val == "":
val = None
if support_cb:
support_cb(inst, self.attrname, self.cliname)
if self.is_onoff:
val = _on_off_convert(self.cliname, val)
if val == "default" and self.ignore_default:
if val == "default" and self.ignore_default and not lookup:
return
attr = None
if lookup and not self.attrname:
raise RuntimeError(_("Don't know how to match %s property %s") %
(getattr(inst, "virtual_device_type", ""), self.cliname))
try:
if self.setter_cb:
attr = None
elif callable(self.attrname):
attr = self.attrname
else:
attr = eval("inst." + self.attrname)
if self.attrname:
eval("inst." + self.attrname)
except AttributeError:
raise RuntimeError("programming error: obj=%s does not have "
"member=%s" % (inst, self.attrname))
if self.setter_cb:
if lookup:
return eval("inst." + self.attrname) == val
elif self.setter_cb:
self.setter_cb(opts, inst, self.cliname, val)
elif callable(attr):
attr(val)
else:
exec("inst." + self.attrname + " = val") # pylint: disable=W0122
class VirtOptionString(object):
def __init__(self, optstr, virtargs, remove_first=None):
"""
@ -1116,7 +1116,11 @@ class VirtCLIParser(object):
A command line argument just extends this interface, implements
_init_params, and calls set_param in the order it wants the options
parsed on the command line. See existing impls examples of how to
do all sorts of crazy stuff
do all sorts of crazy stuff.
set_param must be set unconditionally (ex from _init_params and not
from overriding _parse), so that we can show all options when the
user requests command line introspection like --disk=?
"""
devclass = None
@ -1206,6 +1210,28 @@ class VirtCLIParser(object):
return ret[0]
return ret
def lookup_device_from_option_string(self, guest, optstr):
"""
Given a passed option string, search the guests' device list
for all devices which match the passed options.
"""
devlist = guest.get_devices(self.devclass.virtual_device_type)[:]
ret = []
for inst in devlist:
opts = VirtOptionString(optstr, self._params,
remove_first=self.remove_first)
valid = True
for param in self._params:
if param.parse(opts, inst,
support_cb=None, lookup=True) is False:
valid = False
break
if valid:
ret.append(inst)
return ret
def _parse_single_optstr(self, guest, optstr, inst):
if not optstr:
return None
@ -1249,9 +1275,9 @@ class ParserNumatune(VirtCLIParser):
self.set_param("numatune.memory_mode", "mode")
##################
# --vcpu parsing #
##################
###################
# --vcpus parsing #
###################
class ParserVCPU(VirtCLIParser):
def _init_params(self):
@ -1317,7 +1343,17 @@ class ParserCPU(VirtCLIParser):
ignore = opts
policy = cliname
for feature_name in util.listify(val):
inst.cpu.add_feature(feature_name, policy)
featureobj = None
for f in inst.cpu.features:
if f.name == feature_name:
featureobj = f
break
if featureobj:
featureobj.policy = policy
else:
inst.cpu.add_feature(feature_name, policy)
self.set_param(None, "model", setter_cb=set_model_cb)
self.set_param("cpu.match", "match")
@ -1627,7 +1663,7 @@ class ParserNetwork(VirtCLIParser):
self.set_param("source_mode", "source_mode")
self.set_param("target_dev", "target")
self.set_param("model", "model")
self.set_param(None, "mac", setter_cb=set_mac_cb)
self.set_param("macaddr", "mac", setter_cb=set_mac_cb)
self.set_param("filterref", "filterref")
def _parse(self, optsobj, inst):
@ -1701,7 +1737,11 @@ class ParserController(VirtCLIParser):
self.set_param("model", "model")
self.set_param("index", "index")
self.set_param("master_startport", "master")
self.set_param("address.set_addrstr", "address")
def set_server_cb(opts, inst, cliname, val):
ignore = opts = cliname
inst.address.set_addrstr(val)
self.set_param(None, "address", setter_cb=set_server_cb)
def _parse(self, opts, inst):
if opts.fullopts == "usb2":
@ -1739,7 +1779,12 @@ class ParserRedir(VirtCLIParser):
self.set_param("bus", "bus")
self.set_param("type", "type")
self.set_param("parse_friendly_server", "server")
def set_server_cb(opts, inst, cliname, val):
ignore = opts = cliname
inst.parse_friendly_server(val)
self.set_param(None, "server", setter_cb=set_server_cb)
#################
@ -1893,15 +1938,28 @@ class _ParserChar(VirtCLIParser):
"optname" : cliname})
self.support_cb = support_check
self.set_param("type", "char_type")
self.set_param("source_path", "path")
self.set_param("source_mode", "mode")
self.set_param("protocol", "protocol")
self.set_param("target_type", "target_type")
self.set_param("target_name", "name")
self.set_param("set_friendly_source", "host")
self.set_param("set_friendly_bind", "bind_host")
self.set_param("set_friendly_target", "target_address")
def set_host_cb(opts, inst, cliname, val):
ignore = opts = cliname
inst.set_friendly_source(val)
self.set_param(None, "host", setter_cb=set_host_cb)
def set_bind_cb(opts, inst, cliname, val):
ignore = opts = cliname
inst.set_friendly_bind(val)
self.set_param(None, "bind_host", setter_cb=set_bind_cb)
def set_target_cb(opts, inst, cliname, val):
ignore = opts = cliname
inst.set_friendly_target(val)
self.set_param(None, "target_address", setter_cb=set_target_cb)
def _parse(self, opts, inst):
if opts.fullopts == "none" and inst.virtual_device_type == "console":
@ -2042,17 +2100,23 @@ def build_parser_map(options, skip=None, only=None):
return parsermap
def parse_option_strings(parsermap, options, guest, inst):
def parse_option_strings(parsermap, options, guest, instlist):
"""
Iterate over the parsermap, and launch the associated parser
function for every value that was filled in on 'options', which
came from argparse/the command line.
"""
instlist = util.listify(instlist)
if not instlist:
instlist = [None]
for option_variable_name in dir(options):
if option_variable_name not in parsermap:
continue
parsermap[option_variable_name].parse(
guest, getattr(options, option_variable_name), inst)
for inst in util.listify(instlist):
parsermap[option_variable_name].parse(
guest, getattr(options, option_variable_name), inst)
def check_option_introspection(options, parsermap):