clone: Rework the UI

* Drop the network editing, users can use the details window
* Drop the combo box approach in favor of a regular treeview
* Drop a lot validation checks which are redundant with modern
  virtinst. We probably lose some checks but I don't think it's
  too important
* Use the cloner API
* Add uitest coverage

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-09-04 15:34:13 -04:00
parent b9de8ad919
commit 16ebab2230
5 changed files with 811 additions and 1256 deletions

View File

@ -1,9 +1,31 @@
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import os
from tests import utils
from tests.uitests import utils as uiutils
class _CloneRow:
"""
Helper class for interacting with the clone row
"""
def __init__(self, *args):
self.chkcell = args[2]
self.txtcell = args[5]
self.is_cloneable = self.chkcell.showing
self.is_share_requested = (
not self.is_cloneable or not self.chkcell.checked)
self.is_clone_requested = not self.is_share_requested
def check_in_text(self, substr):
uiutils.check(lambda: substr in self.txtcell.text)
def select(self):
self.txtcell.click()
class CloneVM(uiutils.UITestCase):
"""
@ -21,63 +43,216 @@ class CloneVM(uiutils.UITestCase):
self.app.root.find("Clone...", "menu item").click()
return self.app.root.find("Clone Virtual Machine", "frame")
def _get_all_rows(self, win):
slist = win.find("storage-list")
def pred(node):
return node.roleName == "table cell"
cells = slist.findChildren(pred, isLambda=True)
idx = 0
rows = []
cellcount = 6
while idx < len(cells):
rows.append(_CloneRow(*cells[idx:idx + cellcount]))
idx += cellcount
# Skip the next row which is always a separator
idx += cellcount
return rows
##############
# Test cases #
##############
def testClone(self):
"""
Clone test-clone, which is meant to hit many clone code paths
"""
win = self._open_window("test-clone")
win.find("Clone", "push button").click()
# Verify the new VM popped up
self.app.root.find("test-clone1", "table cell")
def testCloneSimple(self):
"""
Clone test-clone-simple
"""
# Disable predictable so UUID generation doesn't collide
uri = utils.URIs.test_full.replace(",predictable", "")
self.app.uri = uri
# Clone 'test-clone-simple' which is the most basic case
# Cancel, and reopen
win = self._open_window("test-clone-simple")
win.find("Clone", "push button").click()
win.find("Cancel", "push button").click()
uiutils.check(lambda: not win.showing)
# Verify the new VM popped up
self.app.root.find("test-clone-simple-clone", "table cell")
def testFullClone(self):
"""
Clone test-full-clone, which should error due to lack of space
"""
win = self._open_window("test-clone-full")
win.find("Clone", "push button").click()
# Verify error dialog popped up
self.app.root.find(
".*There is not enough free space.*", "label")
def testCloneTweaks(self):
"""
Clone test-clone-simple, but tweak bits in the clone UI
"""
# Do default clone
win = self._open_window("test-clone-simple")
win.find_fuzzy(None,
"text", "Name").set_text("test-new-vm")
rows = self._get_all_rows(win)
assert len(rows) == 1
assert rows[0].is_clone_requested
rows[0].check_in_text("test-clone-simple.img")
win.find("Details...", "push button").click()
macwin = self.app.root.find("Change MAC address", "dialog")
macwin.find(None,
"text", "New MAC:").set_text("00:16:3e:cc:cf:05")
macwin.find("OK", "push button").click()
win.find("Clone", "push button").click()
uiutils.check(lambda: not win.showing)
win.combo_select("Clone this disk.*", "Details...")
# Check path was generated correctly
win = self._open_window("test-clone-simple-clone")
rows = self._get_all_rows(win)
assert len(rows) == 1
assert rows[0].is_clone_requested
rows[0].check_in_text("test-clone-simple-clone.img")
# Share storage and deal with warnings
rows[0].chkcell.click()
rows[0].check_in_text("Share disk with")
# Do 'cancel' first
win.find("Clone", "push button").click()
self._click_alert_button("cause data to be overwritten", "Cancel")
uiutils.check(lambda: win.active)
win.find("Clone", "push button").click()
self._click_alert_button("cause data to be overwritten", "OK")
uiutils.check(lambda: not win.active)
# Verify the new VM shared storage
win = self._open_window("test-clone-simple-clone1")
rows = self._get_all_rows(win)
assert len(rows) == 1
rows[0].check_in_text("test-clone-simple-clone.img")
def testCloneMulti(self):
# Clone 'test-clone', check some results, make sure clone works
win = self._open_window("test-clone\n")
win.find("Clone", "push button").click()
uiutils.check(lambda: not win.showing)
self.app.topwin.find("test-clone1", "table cell")
# Check test-many-devices which will not work, but confirm
# it errors gracefully
self.app.topwin.find("test-many-devices").click()
sbutton = self.app.topwin.find("Shut Down", "push button")
sbutton.click()
uiutils.check(lambda: not sbutton.sensitive)
self.sleep(.5)
win = self._open_window("test-many-devices")
win.find("Clone", "push button").click()
self._click_alert_button("No such file or", "Close")
win.keyCombo("<alt>F4")
uiutils.check(lambda: not win.showing)
def testCloneStorageChange(self):
# Disable predictable so UUID generation doesn't collide
uri = utils.URIs.test_full.replace(",predictable", "")
self.app.uri = uri
# Trigger some error handling scenarios
win = self._open_window("test-clone-simple")
newname = "test-aaabbb"
win.find("Name:", "text").set_text(newname)
win.find("Clone", "push button").click()
uiutils.check(lambda: not win.showing)
win = self._open_window(newname)
row = self._get_all_rows(win)[0]
row.check_in_text(newname)
oldnewname = newname
newname = "test-aaazzzzbbb"
win.find("Name:", "text").set_text(newname)
row.select()
win.find("Details", "push button").click()
stgwin = self.app.root.find("Change storage path", "dialog")
stgwin.find(None, "text",
"New Path:").set_text("/dev/default-pool/my-new-path")
stgwin.find("OK", "push button").click()
pathtxt = stgwin.find(None, "text", "New Path:")
uiutils.check(lambda: newname in pathtxt.text)
stgwin.find("Browse", "push button").click()
self._select_storagebrowser_volume("default-pool", "iso-vol")
uiutils.check(lambda: "iso-vol" in pathtxt.text)
stgwin.find("OK").click()
self._click_alert_button("overwrite the existing", "No")
uiutils.check(lambda: stgwin.showing)
stgwin.find("OK").click()
self._click_alert_button("overwrite the existing", "Yes")
uiutils.check(lambda: not stgwin.showing)
# Can't clone onto existing storage volume
win.find("Clone", "push button").click()
self._click_alert_button(".*Clone onto existing.*", "Close")
# Verify the new VM popped up
self.app.root.find("test-new-vm", "table cell")
# Reopen dialog and request to share it
win.find("Details", "push button").click()
stgwin = self.app.root.find("Change storage path", "dialog")
chkbox = stgwin.find("Create a new", "check")
uiutils.check(lambda: chkbox.checked)
chkbox.click()
# Cancel and reopen, confirm changes didn't stick
stgwin.find("Cancel").click()
uiutils.check(lambda: not stgwin.showing)
win.find("Details", "push button").click()
stgwin = self.app.root.find("Change storage path", "dialog")
chkbox = stgwin.find("Create a new", "check")
uiutils.check(lambda: chkbox.checked)
# Requesting sharing again and exit
chkbox.click()
stgwin.find("OK").click()
uiutils.check(lambda: not stgwin.active)
# Finish install, verify storage was shared
win.find("Clone", "push button").click()
self._click_alert_button("cause data to be overwritten", "OK")
uiutils.check(lambda: not win.active)
win = self._open_window(newname)
row = self._get_all_rows(win)[0].check_in_text(oldnewname)
def testCloneError(self):
# Trigger some error handling scenarios
win = self._open_window("test-clone-full\n")
win.find("Clone", "push button").click()
self._click_alert_button("not enough free space", "Close")
uiutils.check(lambda: win.showing)
win.keyCombo("<alt>F4")
win = self._open_window("test-clone-simple")
badname = "test/foo"
win.find("Name:", "text").set_text(badname)
rows = self._get_all_rows(win)
rows[0].chkcell.click()
rows[0].check_in_text("Share disk with")
win.find("Clone", "push button").click()
win.find("Clone", "push button").click()
self._click_alert_button("cause data to be overwritten", "OK")
self._click_alert_button(badname, "Close")
uiutils.check(lambda: win.active)
def testCloneNonmanaged(self):
# Verify unmanaged clone actual works
import tempfile
tmpsrc = tempfile.NamedTemporaryFile()
tmpdst = tempfile.NamedTemporaryFile()
open(tmpsrc.name, "w").write(__file__)
self.app.open(xmleditor_enabled=True)
manager = self.app.topwin
win = self._open_details_window("test-clone-simple")
win.find("IDE Disk 1", "table cell").click()
win.find("XML", "page tab").click()
xmleditor = win.find("XML editor")
origpath = "/dev/default-pool/test-clone-simple.img"
newpath = tmpsrc.name
xmleditor.set_text(xmleditor.text.replace(origpath, newpath))
win.find("config-apply").click()
win.find("Details", "page tab").click()
disksrc = win.find("disk-source-path")
uiutils.check(lambda: disksrc.text == newpath)
win.keyCombo("<alt>F4")
uiutils.check(lambda: not win.active)
uiutils.check(lambda: manager.active)
win = self._open_window("test-clone-simple")
row = self._get_all_rows(win)[0]
row.check_in_text(tmpsrc.name)
row.select()
win.find("Details", "push button").click()
stgwin = self.app.root.find("Change storage path", "dialog")
pathtxt = stgwin.find(None, "text", "New Path:")
os.unlink(tmpdst.name)
pathtxt.set_text(tmpdst.name)
stgwin.find("OK").click()
win.find("Clone", "push button").click()
uiutils.check(lambda: not win.active)
uiutils.check(lambda: os.path.exists(tmpdst.name))
assert open(tmpsrc.name).read() == open(tmpdst.name).read()

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkImage" id="image1">
@ -12,9 +12,6 @@
<property name="title" translatable="yes">Clone Virtual Machine</property>
<property name="type_hint">dialog</property>
<signal name="delete-event" handler="on_clone_delete_event" swapped="no"/>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
@ -96,7 +93,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<property name="spacing">24</property>
<child>
<object class="GtkBox" id="vbox5">
<property name="visible">True</property>
@ -127,7 +124,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Create clone based on:</property>
<property name="label" translatable="yes">Original VM:</property>
</object>
<packing>
<property name="left_attach">0</property>
@ -138,8 +135,8 @@
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Destination host:</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Connection:</property>
</object>
<packing>
<property name="left_attach">0</property>
@ -162,217 +159,28 @@
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment1">
<object class="GtkGrid" id="table1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<property name="row_spacing">12</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkGrid" id="table1">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">10</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkBox" id="vbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="clone-no-net">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">No networking devices</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="clone-network-box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Networking:</property>
<property name="use_markup">True</property>
<style>
<class name="vmm-lighter"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vbox77">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="clone-no-storage-pass">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">No storage to clone</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="clone-storage-scroll">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<child>
<object class="GtkViewport" id="storage-viewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="vbox7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="hbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkAlignment" id="alignment10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="right_padding">6</property>
<child>
<object class="GtkBox" id="clone-storage-box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<property name="label" translatable="yes">Storage:</property>
<property name="use_markup">True</property>
<style>
<class name="vmm-lighter"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="halign">start</property>
<property name="hexpand">False</property>
<property name="label" translatable="yes">_Name:</property>
<property name="use_markup">True</property>
<property name="use_underline">True</property>
@ -382,25 +190,123 @@
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="clone-new-name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="margin_top">3</property>
<property name="margin_bottom">3</property>
<property name="hexpand">True</property>
<property name="invisible_char">●</property>
<property name="width_chars">30</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="storage-scroll">
<property name="height_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="storage-list">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection">
<signal name="changed" handler="on_storage_selection_changed" swapped="no"/>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="storage-list-atkobject">
<property name="AtkObject::accessible-name">storage-list</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Storage:</property>
<property name="use_markup">True</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">storage-list</property>
<style>
<class name="vmm-lighter"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="storage-details-button">
<property name="label" translatable="yes">_Details...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_storage_details_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
@ -422,47 +328,6 @@
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">3</property>
<child>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="stock">gtk-info</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">False</property>
<property name="label" translatable="yes">&lt;span size='small'&gt;Cloning creates a new, independent copy of the original disk. Sharing
uses the existing disk image for both the original and the new machine.&lt;/span&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
@ -500,12 +365,12 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
@ -572,240 +437,9 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="vmm-change-mac">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Change MAC address</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<signal name="delete-event" handler="on_vmm_change_mac_delete_event" swapped="no"/>
<child>
<child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="change-mac-cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_change_mac_cancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="change-mac-ok">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_change_mac_ok_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">6</property>
<property name="left_padding">6</property>
<child>
<object class="GtkBox" id="vbox8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="hbox4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkGrid" id="table6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<object class="GtkAlignment" id="alignment9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">2</property>
<child>
<object class="GtkLabel" id="change-mac-type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label">type string</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="left_padding">2</property>
<child>
<object class="GtkLabel" id="change-mac-orig">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label">orig-mac</property>
<property name="max_width_chars">20</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="change-mac-new">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">New _MAC:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">change-mac-new</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">6</property>
<property name="right_padding">7</property>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-idle</property>
<property name="icon_size">6</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="height">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label77">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Type:</property>
<property name="use_markup">True</property>
<style>
<class name="vmm-lighter"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label16">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="label" translatable="yes">MAC:</property>
<property name="use_markup">True</property>
<style>
<class name="vmm-lighter"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="0">change-mac-cancel</action-widget>
<action-widget response="0">change-mac-ok</action-widget>
</action-widgets>
</object>
<object class="GtkDialog" id="vmm-change-storage">
<property name="can_focus">False</property>
@ -815,9 +449,6 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<signal name="delete-event" handler="on_vmm_change_storage_delete_event" swapped="no"/>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox2">
<property name="visible">True</property>
@ -936,10 +567,9 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<object class="GtkLabel" id="change-storage-orig">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label">orig-path</property>
<property name="selectable">True</property>
<property name="ellipsize">start</property>
<property name="max_width_chars">25</property>
</object>
<packing>
<property name="left_attach">1</property>
@ -1033,7 +663,6 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<object class="GtkAlignment" id="alignment5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">22</property>
<child>
<object class="GtkLabel" id="label12">
<property name="visible">True</property>
@ -1057,6 +686,7 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_change_storage_doclone_toggled" swapped="no"/>
@ -1071,8 +701,13 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<object class="GtkEntry" id="change-storage-new">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="width_chars">25</property>
<child internal-child="accessible">
<object class="AtkObject" id="change-storage-new-atkobject">
<property name="AtkObject::accessible-name">new-path</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
@ -1113,5 +748,8 @@ like change passwords or static IPs, please see the virt-sysprep(1) tool.&lt;/sp
<action-widget response="0">change-storage-cancel</action-widget>
<action-widget response="0">change-storage-ok</action-widget>
</action-widgets>
<child type="titlebar">
<placeholder/>
</child>
</object>
</interface>

File diff suppressed because it is too large Load Diff

View File

@ -208,11 +208,13 @@ class _CloneDiskInfo:
self.disk.set_backend_for_existing_path()
self.new_disk = None
self._do_share_reason = _get_shareable_msg(self.disk)
self._share_msg = _get_shareable_msg(self.disk)
self._cloneable_msg = -1
self._newpath_msg = None
self._action = None
self.set_clone_requested()
if self._do_share_reason:
if self.get_share_msg():
self.set_share_requested()
def is_clone_requested(self):
@ -222,38 +224,51 @@ class _CloneDiskInfo:
def is_preserve_requested(self):
return self._action in [self._ACTION_PRESERVE]
def _set_action(self, action):
if action != self._action:
self._action = action
def set_clone_requested(self):
self._action = self._ACTION_CLONE
self._set_action(self._ACTION_CLONE)
def set_share_requested(self):
self._action = self._ACTION_SHARE
self._set_action(self._ACTION_SHARE)
def set_preserve_requested(self):
self._action = self._ACTION_PRESERVE
def check_cloneable(self):
try:
msg = _get_cloneable_msg(self.disk)
if msg:
raise ValueError(msg)
except Exception as e:
log.debug("Exception processing clone original path", exc_info=True)
err = _("Could not determine original disk information: %s" % str(e))
raise ValueError(err) from None
self._set_action(self._ACTION_PRESERVE)
def set_new_path(self, path, sparse):
allow_create = not self.is_preserve_requested()
if allow_create:
self.check_cloneable()
msg = self.get_cloneable_msg()
if msg:
return
try:
self.new_disk = Cloner.build_clone_disk(
self.disk, path, allow_create, sparse)
except Exception as e:
log.debug("Error setting clone path.", exc_info=True)
raise ValueError(
_("Could not use path '%(path)s' for cloning: %(error)s") % {
"path": path,
"error": str(e),
})
err = (_("Could not use path '%(path)s' for cloning: %(error)s") %
{"path": path, "error": str(e)})
self._newpath_msg = err
def get_share_msg(self):
return self._share_msg
def get_cloneable_msg(self):
if self._cloneable_msg == -1:
self._cloneable_msg = _get_cloneable_msg(self.disk)
return self._cloneable_msg
def get_newpath_msg(self):
return self._newpath_msg
def raise_error(self):
if self.is_clone_requested() and self.get_cloneable_msg():
msg = self.get_cloneable_msg()
err = _("Could not determine original disk information: %s" % msg)
raise ValueError(err)
if self.is_share_requested():
return
if self.get_newpath_msg():
msg = self.get_newpath_msg()
raise ValueError(msg)
class Cloner(object):
@ -452,6 +467,7 @@ class Cloner(object):
# can copy. It's valid for nvram to not exist at VM define
# time, libvirt will create it for us
diskinfo.set_new_path(new_nvram_path, self._sparse)
diskinfo.raise_error()
diskinfo.new_disk.get_vol_install().reflink = self._reflink
else:
# There's no action to perform for this case, so drop it
@ -481,6 +497,9 @@ class Cloner(object):
self.new_guest.name,
orig_disk.path)
diskinfo.set_new_path(newpath, self._sparse)
if not diskinfo.new_disk:
# We hit an error, clients will raise it later
continue
new_disk = diskinfo.new_disk
assert new_disk

View File

@ -52,11 +52,13 @@ def _process_disks(options, cloner):
if origpath is None:
newpath = None
diskinfo.set_new_path(newpath, options.sparse)
diskinfo.raise_error()
def _validate_disks(cloner):
# Extra CLI validation for specified disks
for diskinfo in cloner.get_diskinfos():
diskinfo.raise_error()
if not diskinfo.new_disk:
continue
warn_overwrite = not diskinfo.is_preserve_requested()