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:
parent
b9de8ad919
commit
16ebab2230
|
@ -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()
|
||||
|
|
620
ui/clone.ui
620
ui/clone.ui
|
@ -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"><span size='small'>Cloning creates a new, independent copy of the original disk. Sharing
|
||||
uses the existing disk image for both the original and the new machine.</span></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.</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.</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.</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.</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.</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.</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.</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.</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>
|
||||
|
|
1117
virtManager/clone.py
1117
virtManager/clone.py
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue