diff options
-rw-r--r-- | tests/data/capabilities/kvm-x86_64-insecure-domcaps.xml | 180 | ||||
-rw-r--r-- | tests/data/testdriver/testdriver.xml | 2 | ||||
-rw-r--r-- | tests/uitests/test_createvm.py | 87 | ||||
-rw-r--r-- | tests/uitests/test_details.py | 180 | ||||
-rw-r--r-- | tests/uitests/test_mediachange.py | 9 | ||||
-rw-r--r-- | tests/utils.py | 1 | ||||
-rw-r--r-- | ui/details.ui | 13 | ||||
-rw-r--r-- | virtManager/details/details.py | 75 | ||||
-rw-r--r-- | virtManager/lib/inspection.py | 11 | ||||
-rw-r--r-- | virtManager/object/domain.py | 143 | ||||
-rw-r--r-- | virtManager/vmwindow.py | 18 |
11 files changed, 559 insertions, 160 deletions
diff --git a/tests/data/capabilities/kvm-x86_64-insecure-domcaps.xml b/tests/data/capabilities/kvm-x86_64-insecure-domcaps.xml new file mode 100644 index 00000000..8071e69b --- /dev/null +++ b/tests/data/capabilities/kvm-x86_64-insecure-domcaps.xml @@ -0,0 +1,180 @@ +<domainCapabilities> + <path>/usr/bin/qemu-system-x86_64</path> + <domain>kvm</domain> + <machine>pc-i440fx-4.2</machine> + <arch>x86_64</arch> + <vcpu max='255'/> + <iothreads supported='yes'/> + <os supported='yes'> + <enum name='firmware'> + <value>efi</value> + </enum> + <loader supported='yes'> + <value>/usr/share/edk2/ovmf/OVMF_CODE.fd</value> + <enum name='type'> + <value>rom</value> + <value>pflash</value> + </enum> + <enum name='readonly'> + <value>yes</value> + <value>no</value> + </enum> + <enum name='secure'> + <value>no</value> + </enum> + </loader> + </os> + <cpu> + <mode name='host-passthrough' supported='yes'/> + <mode name='host-model' supported='yes'> + <model fallback='forbid'>Skylake-Client-IBRS</model> + <vendor>Intel</vendor> + <feature policy='require' name='ss'/> + <feature policy='require' name='vmx'/> + <feature policy='require' name='hypervisor'/> + <feature policy='require' name='tsc_adjust'/> + <feature policy='require' name='clflushopt'/> + <feature policy='require' name='umip'/> + <feature policy='require' name='md-clear'/> + <feature policy='require' name='stibp'/> + <feature policy='require' name='arch-capabilities'/> + <feature policy='require' name='ssbd'/> + <feature policy='require' name='xsaves'/> + <feature policy='require' name='pdpe1gb'/> + <feature policy='require' name='invtsc'/> + <feature policy='require' name='ibpb'/> + <feature policy='require' name='amd-ssbd'/> + <feature policy='require' name='skip-l1dfl-vmentry'/> + </mode> + <mode name='custom' supported='yes'> + <model usable='yes'>qemu64</model> + <model usable='yes'>qemu32</model> + <model usable='no'>phenom</model> + <model usable='yes'>pentium3</model> + <model usable='yes'>pentium2</model> + <model usable='yes'>pentium</model> + <model usable='yes'>n270</model> + <model usable='yes'>kvm64</model> + <model usable='yes'>kvm32</model> + <model usable='yes'>coreduo</model> + <model usable='yes'>core2duo</model> + <model usable='no'>athlon</model> + <model usable='yes'>Westmere-IBRS</model> + <model usable='yes'>Westmere</model> + <model usable='no'>Skylake-Server-IBRS</model> + <model usable='no'>Skylake-Server</model> + <model usable='yes'>Skylake-Client-IBRS</model> + <model usable='yes'>Skylake-Client</model> + <model usable='yes'>SandyBridge-IBRS</model> + <model usable='yes'>SandyBridge</model> + <model usable='yes'>Penryn</model> + <model usable='no'>Opteron_G5</model> + <model usable='no'>Opteron_G4</model> + <model usable='no'>Opteron_G3</model> + <model usable='yes'>Opteron_G2</model> + <model usable='yes'>Opteron_G1</model> + <model usable='yes'>Nehalem-IBRS</model> + <model usable='yes'>Nehalem</model> + <model usable='yes'>IvyBridge-IBRS</model> + <model usable='yes'>IvyBridge</model> + <model usable='no'>Icelake-Server</model> + <model usable='no'>Icelake-Client</model> + <model usable='yes'>Haswell-noTSX-IBRS</model> + <model usable='yes'>Haswell-noTSX</model> + <model usable='yes'>Haswell-IBRS</model> + <model usable='yes'>Haswell</model> + <model usable='no'>EPYC-IBPB</model> + <model usable='no'>EPYC</model> + <model usable='no'>Dhyana</model> + <model usable='yes'>Conroe</model> + <model usable='no'>Cascadelake-Server</model> + <model usable='yes'>Broadwell-noTSX-IBRS</model> + <model usable='yes'>Broadwell-noTSX</model> + <model usable='yes'>Broadwell-IBRS</model> + <model usable='yes'>Broadwell</model> + <model usable='yes'>486</model> + </mode> + </cpu> + <devices> + <disk supported='yes'> + <enum name='diskDevice'> + <value>disk</value> + <value>cdrom</value> + <value>floppy</value> + <value>lun</value> + </enum> + <enum name='bus'> + <value>ide</value> + <value>fdc</value> + <value>scsi</value> + <value>virtio</value> + <value>usb</value> + <value>sata</value> + </enum> + <enum name='model'> + <value>virtio</value> + <value>virtio-transitional</value> + <value>virtio-non-transitional</value> + </enum> + </disk> + <graphics supported='yes'> + <enum name='type'> + <value>sdl</value> + <value>vnc</value> + <value>spice</value> + </enum> + </graphics> + <video supported='yes'> + <enum name='modelType'> + <value>vga</value> + <value>cirrus</value> + <value>vmvga</value> + <value>qxl</value> + <value>virtio</value> + <value>none</value> + <value>bochs</value> + <value>ramfb</value> + </enum> + </video> + <hostdev supported='yes'> + <enum name='mode'> + <value>subsystem</value> + </enum> + <enum name='startupPolicy'> + <value>default</value> + <value>mandatory</value> + <value>requisite</value> + <value>optional</value> + </enum> + <enum name='subsysType'> + <value>usb</value> + <value>pci</value> + <value>scsi</value> + </enum> + <enum name='capsType'/> + <enum name='pciBackend'/> + </hostdev> + <rng supported='yes'> + <enum name='model'> + <value>virtio</value> + <value>virtio-transitional</value> + <value>virtio-non-transitional</value> + </enum> + <enum name='backendModel'> + <value>random</value> + <value>egd</value> + <value>builtin</value> + </enum> + </rng> + </devices> + <features> + <gic supported='no'/> + <vmcoreinfo supported='yes'/> + <genid supported='yes'/> + <backingStoreInput supported='yes'/> + <backup supported='no'/> + <sev supported='no'/> + </features> +</domainCapabilities> + + diff --git a/tests/data/testdriver/testdriver.xml b/tests/data/testdriver/testdriver.xml index e6618869..c7cc7e4c 100644 --- a/tests/data/testdriver/testdriver.xml +++ b/tests/data/testdriver/testdriver.xml @@ -703,7 +703,7 @@ test-many-devices, like an alternate RNG, EOL OS ID, title field <currentMemory unit='GiB'>64</currentMemory> <vcpu>2</vcpu> <cpu mode='host-model'/> - <os> + <os firmware='efi'> <type arch='i686'>hvm</type> <boot dev='hd'/> </os> diff --git a/tests/uitests/test_createvm.py b/tests/uitests/test_createvm.py index 0f623d28..8085d5d6 100644 --- a/tests/uitests/test_createvm.py +++ b/tests/uitests/test_createvm.py @@ -231,6 +231,14 @@ class NewVM(uiutils.UITestCase): appl.click() uiutils.check(lambda: not appl.sensitive) + # Change NIC mac + vmwindow.find_fuzzy("NIC", "table cell").click() + tab = vmwindow.find("network-tab") + tab.print_nodes() + tab.find("mac-entry", "text").text = "00:11:00:11:00:11" + appl.click() + uiutils.check(lambda: not appl.sensitive) + # Start the install, close via the VM window vmwindow.find_fuzzy("Begin Installation", "button").click() uiutils.check(lambda: newvm.showing is False) @@ -510,6 +518,20 @@ class NewVM(uiutils.UITestCase): vmname = "container1" details = self.app.root.find_fuzzy("%s on" % vmname, "frame") + # Tweak init values + details.find("Boot Options", "table cell").click() + tab = details.find("boot-tab") + tab.print_nodes() + tab.find("Init path:", "text").text = "" + tab.find("Init args:", "text").text = "some args" + appl = details.find("config-apply") + appl.click() + self._click_alert_button("init path must be specified", "OK") + uiutils.check(lambda: appl.sensitive) + tab.find("Init path:", "text").text = "/some/path" + appl.click() + uiutils.check(lambda: not appl.sensitive) + # Check that addhw container options are disabled details.find("add-hardware", "push button").click() addhw = self.app.root.find("Add New Virtual Hardware", "frame") @@ -526,6 +548,71 @@ class NewVM(uiutils.UITestCase): uiutils.check(lambda: not newvm.showing) self.app.root.find_fuzzy("%s on" % vmname, "frame") + def testNewVMCustomizeCancel(self): + """ + Test cancelling out of the customize wizard + """ + newvm = self._open_create_wizard() + newvm.find_fuzzy("Manual", "radio").click() + self.forward(newvm) + newvm.find("oslist-entry").text = "generic" + newvm.find("oslist-popover").find_fuzzy("generic").click() + self.forward(newvm) + self.forward(newvm) + self.forward(newvm) + + newvm.find_fuzzy("Customize", "check").click() + newvm.find_fuzzy("Finish", "button").click() + vmname = "vm1" + details = self.app.root.find_fuzzy("%s on" % vmname, "frame") + + details.find("Cancel Installation", "push button").click() + self._click_alert_button("abort the installation", "No") + uiutils.check(lambda: details.active) + details.find("Cancel Installation", "push button").click() + self._click_alert_button("abort the installation", "Yes") + uiutils.check(lambda: not details.active) + uiutils.check(lambda: not newvm.active) + + def testNewVMCustomizeMisc(self): + """ + Some specific customize logic paths + """ + newvm = self._open_create_wizard() + newvm.find_fuzzy("Manual", "radio").click() + self.forward(newvm) + newvm.find("oslist-entry").text = "generic" + newvm.find("oslist-popover").find_fuzzy("generic").click() + self.forward(newvm) + self.forward(newvm) + self.forward(newvm) + + newvm.find_fuzzy("Customize", "check").click() + newvm.find_fuzzy("Finish", "button").click() + vmname = "vm1" + details = self.app.root.find_fuzzy("%s on" % vmname, "frame") + + # Test name change + tab = details.find("overview-tab") + nametext = tab.find("Name:", "text") + nametext.text = "foonewname" + details.find("config-apply").click() + self.app.root.find_fuzzy("foonewname", "frame") + + # Trigger XML failure to hit some codepaths + nametext.text = "" + details.find("Begin Installation").click() + self._click_alert_button("unapplied changes", "Yes") + self._click_alert_button("name must be specified", "Close") + uiutils.check(lambda: details.showing) + + # Discard XML change and continue with install + details.find("Begin Installation").click() + self._click_alert_button("unapplied changes", "No") + uiutils.check(lambda: not details.showing) + uiutils.check(lambda: not newvm.showing) + self.app.root.find_fuzzy("foonewname on", "frame") + def testNewVMContainerTree(self): """ diff --git a/tests/uitests/test_details.py b/tests/uitests/test_details.py index ab07183f..b5287228 100644 --- a/tests/uitests/test_details.py +++ b/tests/uitests/test_details.py @@ -96,11 +96,57 @@ class Details(uiutils.UITestCase): self._testRename(origname, "test-new-name") + def testDetailsStateMisc(self): + """ + Test state changes and unapplied changes warnings + """ + self.app.uri = tests.utils.URIs.kvm + win = self._open_details_window(vmname="test", shutdown=True) + self.app.topwin.click_title() + # Double run to hit a show() codepath + win = self._open_details_window(vmname="test") + uiutils.check(lambda: win.active) + appl = win.find("config-apply", "push button") + + # View Manager option + win.find("File", "menu").click() + win.find("View Manager", "menu item").click() + uiutils.check(lambda: self.app.topwin.active) + self.app.topwin.keyCombo("<alt>F4") + uiutils.check(lambda: win.active) + + # Make a change and then trigger unapplied change warning + tab = self._select_hw(win, "Overview", "overview-tab") + tab.find("Name:", "text").text = "" + uiutils.check(lambda: appl.sensitive) + run = win.find("Run", "push button") + run.click() + # Trigger apply error to hit some code paths + self._click_alert_button("unapplied changes", "Yes") + self._click_alert_button("name must be specified", "Close") + uiutils.check(lambda: run.sensitive) + consolebtn = win.find("Console", "radio button") + consolebtn.click() + self._click_alert_button("unapplied changes", "Yes") + self._click_alert_button("name must be specified", "Close") + uiutils.check(lambda: not consolebtn.checked) + + # Test the pause toggle + win.find("config-cancel").click() + run.click() + uiutils.check(lambda: not run.sensitive) + pause = win.find("Pause", "toggle button") + pause.click() + uiutils.check(lambda: pause.checked) + pause.click() + uiutils.check(lambda: not pause.checked) + uiutils.check(lambda: win.active) + def testDetailsEditDomain1(self): """ Test overview, memory, cpu pages """ - self.app.uri = tests.utils.URIs.kvm + self.app.uri = tests.utils.URIs.kvm_cpu_insecure win = self._open_details_window(vmname="test") appl = win.find("config-apply", "push button") @@ -120,14 +166,9 @@ class Details(uiutils.UITestCase): appl.click() uiutils.check(lambda: not appl.sensitive) - # vCPUs - tab = self._select_hw(win, "CPUs", "cpu-tab") - tab.find("vCPU allocation:", "spin button").text = "4" - appl.click() - uiutils.check(lambda: not appl.sensitive) - # Static CPU config # more cpu config: host-passthrough, copy, clear CPU, manual + tab = self._select_hw(win, "CPUs", "cpu-tab") tab.find("cpu-model").click_combo_entry() tab.find_fuzzy("Clear CPU", "menu item").click() appl.click() @@ -136,27 +177,49 @@ class Details(uiutils.UITestCase): tab.find("coreduo", "menu item").click() appl.click() uiutils.check(lambda: not appl.sensitive) + tab.find_fuzzy("CPU security", "check box").click() + appl.click() + uiutils.check(lambda: not appl.sensitive) tab.find("cpu-model").click_combo_entry() tab.find("Application Default", "menu item").click() appl.click() uiutils.check(lambda: not appl.sensitive) - tab.find_fuzzy("Copy host").click() + copyhost = tab.find("Copy host", "check box") + uiutils.check(lambda: copyhost.selected) + copyhost.click() tab.find("cpu-model").click_combo_entry() tab.find("Hypervisor Default", "menu item").click() appl.click() uiutils.check(lambda: not appl.sensitive) + tab.find("cpu-model").text = "host-passthrough" + appl.click() + uiutils.check(lambda: not appl.sensitive) + + # vCPUs + tab.find("vCPU allocation:", "spin button").text = "50" + appl.click() + uiutils.check(lambda: not appl.sensitive) # CPU topology tab.find_fuzzy("Topology", "toggle button").click_expander() tab.find_fuzzy("Manually set", "check").click() - tab.find("Sockets:", "spin button").typeText("8") + sockets = tab.find("Sockets:", "spin button") + sockets.typeText("8") tab.find("Cores:", "spin button").typeText("2") tab.find("Threads:", "spin button").typeText("2") appl.click() uiutils.check(lambda: not appl.sensitive) + # Confirm VCPUs were adjusted vcpualloc = tab.find_fuzzy("vCPU allocation", "spin") uiutils.check(lambda: vcpualloc.text == "32") + # Unset topology + tab.find_fuzzy("Manually set", "check").click() + uiutils.check(lambda: not sockets.sensitive) + appl.click() + # Currently generates an error + # uiutils.check(lambda: not appl.sensitive) + def testDetailsEditDomain2(self): """ @@ -207,17 +270,43 @@ class Details(uiutils.UITestCase): # Kernel boot tab.find_fuzzy("Direct kernel boot", "toggle button").click_expander() tab.find_fuzzy("Enable direct kernel", "check box").click() + + tab.find("Kernel args:", "text").text = "console=ttyS0" + appl.click() + self._click_alert_button("arguments without specifying", "OK") + uiutils.check(lambda: win.active) + + initrd = tab.find("Initrd path:", "text") + tab.find("initrd-browse", "push button").click() + self._select_storagebrowser_volume("default-pool", "backingl1.img") + uiutils.check(lambda: win.active) + uiutils.check(lambda: "backing" in initrd.text) + appl.click() + self._click_alert_button("initrd without specifying", "OK") + uiutils.check(lambda: win.active) + tab.find("kernel-browse", "push button").click() self._select_storagebrowser_volume("default-pool", "bochs-vol") uiutils.check(lambda: win.active) kernelpath = tab.find("Kernel path:", "text") uiutils.check(lambda: "bochs" in kernelpath.text) - tab.find("Initrd path:", "text").text = "/tmp/initrd" - tab.find("DTB path:", "text").text = "/tmp/dtb" - tab.find("Kernel args:", "text").text = "console=ttyS0" + + dtb = tab.find("DTB path:", "text") + tab.find("dtb-browse", "push button").click() + self._select_storagebrowser_volume("default-pool", "iso-vol") + uiutils.check(lambda: win.active) + uiutils.check(lambda: "iso-vol" in dtb.text) + appl.click() uiutils.check(lambda: not appl.sensitive) + # Now disable kernel, but verify that we keep the values in the UI + tab.find_fuzzy("Enable direct kernel", "check box").click() + appl.click() + uiutils.check(lambda: not appl.sensitive) + tab = self._select_hw(win, "OS information", "os-tab") + tab = self._select_hw(win, "Boot Options", "boot-tab") + uiutils.check(lambda: "backing" in initrd.text) def testDetailsEditDiskNet(self): """ @@ -235,12 +324,14 @@ class Details(uiutils.UITestCase): tab.find("Advanced options", "toggle button").click_expander() tab.find("Cache mode:", "text").text = "unsafe" tab.find("Discard mode:", "text").text = "unmap" + tab.find("Detect zeroes:", "text").text = "unmap" appl.click() uiutils.check(lambda: not appl.sensitive) # Network values w/ macvtap manual tab = self._select_hw(win, "NIC :54:32:10", "network-tab") + tab.find("IP address", "push button").click() src = tab.find("net-source") src.click() self.pressKey("Home") @@ -248,6 +339,7 @@ class Details(uiutils.UITestCase): "menu item").bring_on_screen().click() tab.find("Device name:", "text").text = "fakedev12" tab.combo_select("Device model:", "rtl8139") + tab.find("Link state:", "check box").click() appl.click() uiutils.check(lambda: not appl.sensitive) @@ -264,31 +356,54 @@ class Details(uiutils.UITestCase): uiutils.check(lambda: not appl.sensitive) - def testDetailsEditDevices(self): + def testDetailsEditDevices1(self): """ Test all other devices """ - win = self._open_details_window(vmname="test-many-devices") + win = self._open_details_window(vmname="test-many-devices", + shutdown=True) appl = win.find("config-apply", "push button") - self._stop_vm(win) - - # Graphics + # Graphics simple VNC -> SPICE tab = self._select_hw(win, "Display VNC", "graphics-tab") tab.combo_select("Type:", "Spice") appl.click() uiutils.check(lambda: not appl.sensitive) - tab.combo_select("Type:", "VNC") + # Spice GL example + tab.combo_select("Listen type:", "None") + tab.find("OpenGL:", "check box").click() + tab.combo_check_default("graphics-rendernode", "0000") appl.click() uiutils.check(lambda: not appl.sensitive) + # Switch to VNC with options + tab.combo_select("Type:", "VNC") + tab.combo_select("Listen type:", "Address") + tab.find("graphics-port-auto", "check").click() + tab.find("graphics-port", "spin button").text = "6001" + tab.find("Password:", "check").click() + passwd = tab.find_fuzzy("graphics-password", "text") + newpass = "foobar" + passwd.typeText(newpass) + appl.click() + uiutils.check(lambda: not appl.sensitive) # Sound device tab = self._select_hw(win, "Sound sb16", "sound-tab") tab.find("Model:", "text").text = "ac97" appl.click() uiutils.check(lambda: not appl.sensitive) + # Test non-disk removal + win.find("config-remove").click() + cell = win.find("Sound ac97", "table cell") + oldtext = cell.text + self._click_alert_button("Are you sure", "No") + uiutils.check(lambda: cell.state_selected) + cell.click(button=3) + self.app.root.find("Remove Hardware", "menu item").click() + self._click_alert_button("Are you sure", "Yes") + uiutils.check(lambda: cell.text != oldtext) # Host device @@ -315,6 +430,11 @@ class Details(uiutils.UITestCase): uiutils.check(lambda: not appl.sensitive) + def testDetailsEditDevices2(self): + win = self._open_details_window(vmname="test-many-devices", + shutdown=True) + appl = win.find("config-apply", "push button") + # Controller SCSI tab = self._select_hw( win, "Controller VirtIO SCSI 9", "controller-tab") @@ -351,15 +471,25 @@ class Details(uiutils.UITestCase): appl.click() uiutils.check(lambda: not appl.sensitive) + # TPM tweaks + tab = self._select_hw(win, "TPM", "tpm-tab") + tab.combo_select("tpm-model", "CRB") + appl.click() + uiutils.check(lambda: not appl.sensitive) # vsock tweaks tab = self._select_hw(win, "VirtIO VSOCK", "vsock-tab") addr = tab.find("vsock-cid") auto = tab.find("vsock-auto") uiutils.check(lambda: addr.text == "5") + addr.text = "7" + appl.click() + uiutils.check(lambda: addr.text == "7") + uiutils.check(lambda: not appl.sensitive) auto.click() uiutils.check(lambda: not addr.visible) appl.click() + uiutils.check(lambda: not appl.sensitive) def testDetailsMiscEdits(self): @@ -392,6 +522,15 @@ class Details(uiutils.UITestCase): delete.find_fuzzy("Delete", "button").click() uiutils.check(lambda: win.active) + # Attempt to apply changes when skipping away, but they fail + tab.find("Advanced options", "toggle button").click_expander() + cacheui = tab.find("Cache mode:", "text") + origcache = cacheui.text + cacheui.text = "badcachemode" + hwlist.find("CPUs", "table cell").click() + self._click_alert_button("There are unapplied changes", "Yes") + self._click_alert_button("badcachemode", "Close") + # Cancelling changes tab = self._select_hw(win, "IDE Disk 1", "disk-tab") share = tab.find("Shareable:", "check box") @@ -449,6 +588,11 @@ class Details(uiutils.UITestCase): self._click_alert_button("changes will be lost", "Yes") uiutils.check(lambda: not tab.showing) + # Verify addhardware right click works + cell = win.find("Overview", "table cell").click(button=3) + self.app.root.find("Add Hardware", "menu item").click() + self.app.root.find("Add New Virtual Hardware", "frame") + def testDetailsXMLEdit(self): """ Test XML editing interaction diff --git a/tests/uitests/test_mediachange.py b/tests/uitests/test_mediachange.py index 6a3d118d..457d1526 100644 --- a/tests/uitests/test_mediachange.py +++ b/tests/uitests/test_mediachange.py @@ -53,6 +53,10 @@ class MediaChange(uiutils.UITestCase): combo.click_combo_entry() combo.find(path) entry.click() + # Use the storage browser to select new floppy storage + tab.find("Browse", "push button").click() + self._select_storagebrowser_volume("default-pool", "iso-vol") + appl.click() # Browse for image hw.find("IDE CDROM 1", "table cell").click() @@ -61,8 +65,11 @@ class MediaChange(uiutils.UITestCase): entry.click() tab.find("Browse", "push button").click() self._select_storagebrowser_volume("default-pool", "backingl1.img") - appl.click() # Check 'already in use' dialog + appl.click() + self._click_alert_button("already in use by", "No") + uiutils.check(lambda: appl.sensitive) + appl.click() self._click_alert_button("already in use by", "Yes") uiutils.check(lambda: not appl.sensitive) uiutils.check(lambda: "backing" in entry.text) diff --git a/tests/utils.py b/tests/utils.py index 5bc73f11..be343ffb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -79,6 +79,7 @@ class _URIs(object): _uri_qemu = _m("qemu:///system") _kvm_x86_caps = _caps("kvm-x86_64.xml") + _domcaps("kvm-x86_64-domcaps.xml") self.kvm = _uri_qemu + _kvm_x86_caps + self.kvm_cpu_insecure = _uri_qemu + _caps("kvm-x86_64.xml") + _domcaps("kvm-x86_64-insecure-domcaps.xml") self.kvm_remote = _m("qemu+tls://fakeuri.example.com/system") + _kvm_x86_caps self.kvm_session = _m("qemu:///session") + _kvm_x86_caps diff --git a/ui/details.ui b/ui/details.ui index db0fa5a1..56a40096 100644 --- a/ui/details.ui +++ b/ui/details.ui @@ -2942,8 +2942,9 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="halign">end</property> - <property name="label" translatable="yes">MAC address:</property> + <property name="label" translatable="yes">_MAC address:</property> <property name="use_underline">True</property> + <property name="mnemonic_widget">network-mac-entry</property> </object> <packing> <property name="left_attach">0</property> @@ -3022,6 +3023,11 @@ <property name="visible">True</property> <property name="can_focus">True</property> <signal name="changed" handler="on_network_mac_entry_changed" swapped="no"/> + <child internal-child="accessible"> + <object class="AtkObject" id="network-mac-entry-atkobject"> + <property name="AtkObject::accessible-name">mac-entry</property> + </object> + </child> </object> <packing> <property name="expand">False</property> @@ -4613,6 +4619,11 @@ <property name="can_focus">True</property> </object> </child> + <child internal-child="accessible"> + <object class="AtkObject" id="tpm-model-atkobject"> + <property name="AtkObject::accessible-name">tpm-model</property> + </object> + </child> </object> <packing> <property name="left_attach">1</property> diff --git a/virtManager/details/details.py b/virtManager/details/details.py index dfc733bf..926f93dd 100644 --- a/virtManager/details/details.py +++ b/virtManager/details/details.py @@ -190,10 +190,8 @@ def _label_for_device(dev): } if devtype == "interface": - if dev.macaddr: - return _("NIC %(mac)s") % {"mac": dev.macaddr[-9:]} - else: - return _("NIC") + mac = dev.macaddr[-9:] or "" + return _("NIC %(mac)s") % {"mac": mac} if devtype == "input": if dev.type == "tablet": @@ -202,31 +200,26 @@ def _label_for_device(dev): return _("Mouse") elif dev.type == "keyboard": return _("Keyboard") - return _("Input") + return _("Input") # pragma: no cover if devtype == "serial": - if dev.target_port is not None: - return _("Serial %(num)d") % {"num": int(dev.target_port) + 1} - return _("Serial") + port = dev.target_port or 0 + return _("Serial %(num)d") % {"num": port + 1} if devtype == "parallel": - if dev.target_port is not None: - return _("Parallel %(num)d") % {"num": int(dev.target_port) + 1} - return _("Parallel") + port = dev.target_port or 0 + return _("Parallel %(num)d") % {"num": port + 1} if devtype == "console": - if dev.target_port is not None: - return _("Console %(num)d") % {"num": int(dev.target_port) + 1} - return _("Console") + port = dev.target_port or 0 + return _("Console %(num)d") % {"num": port + 1} if devtype == "channel": name = vmmAddHardware.char_pretty_channel_name(dev.target_name) if name: return _("Channel %(name)s") % {"name": name} pretty_type = vmmAddHardware.char_pretty_type(dev.type) - if pretty_type: - return _("Channel %(type)s") % {"type": pretty_type} - return _("Channel") + return _("Channel %(type)s") % {"type": pretty_type} if devtype == "graphics": pretty = vmmGraphicsDetails.graphics_pretty_type_simple(dev.type) @@ -290,9 +283,7 @@ def _icon_for_device(dev): return "input-mouse" if devtype == "redirdev": - if dev.bus == "usb": - return "device_usb" - return "device_pci" + return "device_usb" if devtype == "hostdev": if dev.type == "usb": @@ -331,7 +322,7 @@ def _get_performance_icon_name(): # fallback to system-run if it is missing icon = "utilities-system-monitor" if not Gtk.IconTheme.get_default().has_icon(icon): - icon = "system-run" + icon = "system-run" # pragma: no cover return icon @@ -916,7 +907,7 @@ class vmmDetails(vmmGObjectUI): # force select the list entry before showing popup_menu path_tuple = widget.get_path_at_pos(int(event.x), int(event.y)) if path_tuple is None: - return False + return False # pragma: no cover path = path_tuple[0] _iter = widget.get_model().get_iter(path) widget.get_selection().select_iter(_iter) @@ -939,6 +930,17 @@ class vmmDetails(vmmGObjectUI): return uiutil.get_list_selected_row(self.widget("hw-list")) def has_unapplied_changes(self, row): + """ + This is a bit confusing. + + * If there are now changes pending, we return False + * If there are changes pending, we prompt the user whether + they want to apply them. If they say no, return False + * If the applying the changes succeeds, return False + * Return True if applying the changes failed. In this + case the caller should attempt to abort the action they + are trying to perform, if possible + """ if not row: return False @@ -1086,23 +1088,29 @@ class vmmDetails(vmmGObjectUI): except Exception as e: # pragma: no cover self.err.show_err((_("Error launching hardware dialog: %s") % str(e))) - def remove_non_disk(self, devobj): + + def _remove_non_disk(self, devobj): if not self.err.chkbox_helper(self.config.get_confirm_removedev, self.config.set_confirm_removedev, text1=(_("Are you sure you want to remove this device?"))): return - self.remove_device(devobj) - def remove_disk(self, disk): + success = vmmDeleteStorage.remove_devobj_internal( + self.vm, self.err, devobj) + if not success: + return + self.disable_apply() + + def _remove_disk(self, disk): dialog = vmmDeleteStorage(disk) dialog.show(self.topwin, self.vm) def remove_xml_dev(self, src_ignore): devobj = self.get_hw_row()[HW_LIST_COL_DEVICE] if devobj.DEVICE_TYPE == "disk": - self.remove_disk(devobj) + self._remove_disk(devobj) else: - self.remove_non_disk(devobj) + self._remove_non_disk(devobj) ############################ @@ -1288,7 +1296,7 @@ class vmmDetails(vmmGObjectUI): ignore = src row = self.get_boot_selection() if not row: - return + return # pragma: no cover row_key = row[BOOT_KEY] boot_order = self.get_config_boot_order() @@ -1780,11 +1788,6 @@ class vmmDetails(vmmGObjectUI): kwargs, self.vm, self.err, devobj=devobj) - # Device removal - def remove_device(self, devobj): - success = vmmDeleteStorage.remove_devobj_internal(self.vm, self.err, devobj) - if success: - self.disable_apply() ####################### # vmwindow Public API # @@ -2195,14 +2198,12 @@ class vmmDetails(vmmGObjectUI): self.vsockdetails.set_dev(dev) def refresh_char_page(self, chardev): - char_type = chardev.DEVICE_TYPE.capitalize() + char_type = chardev.DEVICE_TYPE target_port = chardev.target_port dev_type = chardev.type or "pty" primary = self.vm.serial_is_console_dup(chardev) - show_target_type = not (chardev.DEVICE_TYPE in - ["serial", "parallel"]) + show_target_type = not (char_type in ["serial", "parallel"]) - typelabel = "" if char_type == "serial": typelabel = _("Serial Device") elif char_type == "parallel": diff --git a/virtManager/lib/inspection.py b/virtManager/lib/inspection.py index 11f3298a..a93aa6ae 100644 --- a/virtManager/lib/inspection.py +++ b/virtManager/lib/inspection.py @@ -41,14 +41,15 @@ def _make_fake_data(): for prefix in ["test_app1_", "test_app2_"]: import time app = vmmInspectionApplication() - app.description = prefix + "description" - app.name = prefix + "name" - app.display_name = prefix + "display_name" + if "app1" in prefix: + app.display_name = prefix + "display_name" + app.summary = prefix + "summary-" + str(time.time()) + else: + app.name = prefix + "name" + app.description = prefix + "description-" + str(time.time()) + "\n" app.epoch = 1 app.version = "2" app.release = "3" - app.summary = prefix + "summary-" + str(time.time()) - app.description = prefix + "description-" + str(time.time()) data.applications.append(app) return data diff --git a/virtManager/object/domain.py b/virtManager/object/domain.py index e8750220..f212408e 100644 --- a/virtManager/object/domain.py +++ b/virtManager/object/domain.py @@ -108,9 +108,9 @@ class vmmDomainSnapshot(vmmLibvirtObject): return self._backend.getName() def _conn_tick_poll_param(self): - return None + return None # pragma: no cover def class_name(self): - return "snapshot" + return "snapshot" # pragma: no cover def _XMLDesc(self, flags): return self._backend.getXMLDesc(flags=flags) @@ -138,17 +138,14 @@ class vmmDomainSnapshot(vmmLibvirtObject): "crashed": libvirt.VIR_DOMAIN_CRASHED, "pmsuspended": getattr(libvirt, "VIR_DOMAIN_PMSUSPENDED", 7) } - - if state == "disk-snapshot" or state not in statemap: - state = "shutoff" - return statemap.get(state, libvirt.VIR_DOMAIN_NOSTATE) + return statemap.get(state, libvirt.VIR_DOMAIN_SHUTOFF) def run_status(self): status = self._state_str_to_int() return LibvirtEnumMap.pretty_run_status(status, False) def run_status_icon_name(self): status = self._state_str_to_int() - if status not in LibvirtEnumMap.VM_STATUS_ICONS: + if status not in LibvirtEnumMap.VM_STATUS_ICONS: # pragma: no cover log.debug("Unknown status %d, using NOSTATE", status) status = libvirt.VIR_DOMAIN_NOSTATE return LibvirtEnumMap.VM_STATUS_ICONS[status] @@ -314,7 +311,8 @@ class vmmDomain(vmmLibvirtObject): # We don't want virt-manager to track Domain-0 since it # doesn't work with our UI. Raising an error will ensures it # is blacklisted. - raise RuntimeError("Can't track Domain-0 as a vmmDomain") + raise RuntimeError( # pragma: no cover + "Can't track Domain-0 as a vmmDomain") ########################### @@ -353,12 +351,6 @@ class vmmDomain(vmmLibvirtObject): return True return False - def get_id_pretty(self): - i = self.get_id() - if i < 0: - return "-" - return str(i) - def has_nvram(self): return bool(self.get_xmlobj().os.loader_ro is True and self.get_xmlobj().os.loader_type == "pflash" and @@ -427,12 +419,19 @@ class vmmDomain(vmmLibvirtObject): # If we are removing multiple dev from an active VM, a double # attempt may result in a lookup failure. If device is present # in the active XML, assume all is good. - if self.get_xmlobj().find_device(origdev): + if self.get_xmlobj().find_device(origdev): # pragma: no cover log.debug("Device in active config but not inactive config.") return - raise RuntimeError(_("Could not find specified device in the " - "inactive VM configuration: %s") % repr(origdev)) + raise RuntimeError( # pragma: no cover + _("Could not find specified device in the " + "inactive VM configuration: %s") % repr(origdev)) + + def _process_device_define(self, editdev, xmlobj, do_hotplug): + if do_hotplug: + self.hotplug(device=editdev) + else: + self._redefine_xmlobj(xmlobj) def _copy_nvram_file(self, new_name): """ @@ -464,6 +463,8 @@ class vmmDomain(vmmLibvirtObject): ############################## def rename_domain(self, new_name): + Guest.validate_name(self.conn.get_backend(), str(new_name)) + new_nvram = None old_nvram = None if self.has_nvram(): @@ -510,7 +511,7 @@ class vmmDomain(vmmLibvirtObject): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, False) if not editdev: - return + return # pragma: no cover if con: rmcon = xmlobj.find_device(con) @@ -531,12 +532,17 @@ class vmmDomain(vmmLibvirtObject): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover xmlobj.devices.replace_child(editdev, newdev) self._redefine_xmlobj(xmlobj) return editdev, newdev + + ########################## + # non-device XML editing # + ########################## + def define_cpu(self, vcpus=_SENTINEL, model=_SENTINEL, secure=_SENTINEL, sockets=_SENTINEL, cores=_SENTINEL, threads=_SENTINEL): @@ -643,7 +649,7 @@ class vmmDomain(vmmLibvirtObject): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if path != _SENTINEL: editdev.path = path @@ -667,10 +673,7 @@ class vmmDomain(vmmLibvirtObject): if bus != _SENTINEL: editdev.change_bus(self.xmlobj, bus) - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_network(self, devobj, do_hotplug, ntype=_SENTINEL, source=_SENTINEL, @@ -679,7 +682,7 @@ class vmmDomain(vmmLibvirtObject): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if ntype != _SENTINEL: editdev.source = None @@ -699,10 +702,7 @@ class vmmDomain(vmmLibvirtObject): if linkstate != _SENTINEL: editdev.link_state = "up" if linkstate else "down" - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_graphics(self, devobj, do_hotplug, listen=_SENTINEL, addr=_SENTINEL, port=_SENTINEL, @@ -711,7 +711,7 @@ class vmmDomain(vmmLibvirtObject): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if addr != _SENTINEL or listen != _SENTINEL: if listen == "none": @@ -729,32 +729,26 @@ class vmmDomain(vmmLibvirtObject): if rendernode != _SENTINEL: editdev.rendernode = rendernode - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_sound(self, devobj, do_hotplug, model=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if model != _SENTINEL: if editdev.model != model: editdev.address.clear() editdev.model = model - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_video(self, devobj, do_hotplug, model=_SENTINEL, accel3d=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if model != _SENTINEL and model != editdev.model: editdev.model = model @@ -772,17 +766,14 @@ class vmmDomain(vmmLibvirtObject): if accel3d != _SENTINEL: editdev.accel3d = accel3d - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_watchdog(self, devobj, do_hotplug, model=_SENTINEL, action=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if model != _SENTINEL: if editdev.model != model: @@ -792,32 +783,26 @@ class vmmDomain(vmmLibvirtObject): if action != _SENTINEL: editdev.action = action - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_smartcard(self, devobj, do_hotplug, model=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if model != _SENTINEL: editdev.mode = model editdev.type = None editdev.type = editdev.default_type() - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_controller(self, devobj, do_hotplug, model=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover def _change_model(): if editdev.type == "usb": @@ -849,16 +834,13 @@ class vmmDomain(vmmLibvirtObject): if model != _SENTINEL: _change_model() - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_filesystem(self, devobj, do_hotplug, newdev=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if newdev != _SENTINEL: # pylint: disable=maybe-no-member @@ -872,56 +854,44 @@ class vmmDomain(vmmLibvirtObject): editdev.source = newdev.source editdev.target = newdev.target - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_hostdev(self, devobj, do_hotplug, rom_bar=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if rom_bar != _SENTINEL: editdev.rom_bar = rom_bar - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_tpm(self, devobj, do_hotplug, model=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if model != _SENTINEL: editdev.model = model - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) def define_vsock(self, devobj, do_hotplug, auto_cid=_SENTINEL, cid=_SENTINEL): xmlobj = self._make_xmlobj_to_define() editdev = self._lookup_device_to_define(xmlobj, devobj, do_hotplug) if not editdev: - return + return # pragma: no cover if auto_cid != _SENTINEL: editdev.auto_cid = auto_cid if cid != _SENTINEL: editdev.cid = cid - if do_hotplug: - self.hotplug(device=editdev) - else: - self._redefine_xmlobj(xmlobj) + self._process_device_define(editdev, xmlobj, do_hotplug) #################### @@ -1375,12 +1345,12 @@ class vmmDomain(vmmLibvirtObject): def has_managed_save(self): if not self.managedsave_supported: - return False + return False # pragma: no cover if self._has_managed_save is None: try: self._has_managed_save = self._backend.hasManagedSaveImage(0) - except Exception as e: + except Exception as e: # pragma: no cover if self.conn.support.is_libvirt_error_no_domain(e): return False raise @@ -1389,7 +1359,7 @@ class vmmDomain(vmmLibvirtObject): def remove_saved_image(self): if not self.has_managed_save(): - return + return # pragma: no cover self._backend.managedSaveRemove(0) self._has_managed_save = None @@ -1494,9 +1464,9 @@ class vmmDomain(vmmLibvirtObject): def _normalize_status(self, status): if status == libvirt.VIR_DOMAIN_NOSTATE: - return libvirt.VIR_DOMAIN_RUNNING + return libvirt.VIR_DOMAIN_RUNNING # pragma: no cover elif status == libvirt.VIR_DOMAIN_BLOCKED: - return libvirt.VIR_DOMAIN_RUNNING + return libvirt.VIR_DOMAIN_RUNNING # pragma: no cover return status def is_active(self): @@ -1534,7 +1504,7 @@ class vmmDomain(vmmLibvirtObject): def run_status_icon_name(self): status = self.status() - if status not in LibvirtEnumMap.VM_STATUS_ICONS: + if status not in LibvirtEnumMap.VM_STATUS_ICONS: # pragma: no cover log.debug("Unknown status %s, using NOSTATE", status) status = libvirt.VIR_DOMAIN_NOSTATE return LibvirtEnumMap.VM_STATUS_ICONS[status] @@ -1642,7 +1612,7 @@ class vmmDomainVirtinst(vmmDomain): def get_uuid(self): return self._backend.uuid def get_id(self): - return -1 + return -1 # pragma: no cover def has_managed_save(self): return False @@ -1750,4 +1720,5 @@ class vmmDomainVirtinst(vmmDomain): self._redefine_xml_internal(self._orig_xml or "", xmlobj.get_xml()) def rename_domain(self, new_name): + Guest.validate_name(self._backend.conn, str(new_name)) self.define_name(new_name) diff --git a/virtManager/vmwindow.py b/virtManager/vmwindow.py index 6c6a612b..3a2c8b7e 100644 --- a/virtManager/vmwindow.py +++ b/virtManager/vmwindow.py @@ -220,7 +220,7 @@ class vmmVMWindow(vmmGObjectUI): def _close(self): fs = self.widget("details-menu-view-fullscreen") if fs.get_active(): - fs.set_active(False) + fs.set_active(False) # pragma: no cover if not self.is_visible(): return @@ -268,7 +268,7 @@ class vmmVMWindow(vmmGObjectUI): def window_resized(self, ignore, ignore2): if not self.is_visible(): - return + return # pragma: no cover self._window_size = self.topwin.get_size() def control_fullscreen(self, src): @@ -282,12 +282,8 @@ class vmmVMWindow(vmmGObjectUI): active = src.get_active() self.config.set_details_show_toolbar(active) - - if (active and not - self.widget("details-menu-view-fullscreen").get_active()): - self.widget("toolbar-box").show() - else: - self.widget("toolbar-box").hide() + fsactive = self.widget("details-menu-view-fullscreen").get_active() + self.widget("toolbar-box").set_visible(active and not fsactive) def details_console_changed(self, src): if self.ignoreDetails: @@ -304,7 +300,7 @@ class vmmVMWindow(vmmGObjectUI): pages = self.widget("details-pages") if pages.get_current_page() == DETAILS_PAGE_DETAILS: if self._details.vmwindow_has_unapplied_changes(): - self.sync_details_console_view(True) + self.sync_details_console_view(pages.get_current_page()) return self._details.disable_apply() @@ -534,13 +530,13 @@ class vmmVMWindow(vmmGObjectUI): dialog_type=Gtk.FileChooserAction.SAVE, browse_reason=self.config.CONFIG_DIR_SCREENSHOT, default_name=default) - if not path: + if not path: # pragma: no cover log.debug("No screenshot path given, skipping save.") return filename = path if not filename.endswith(".png"): - filename += ".png" + filename += ".png" # pragma: no cover open(filename, "wb").write(ret) |