[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[virt-tools-list] RFC: virt-manager: VM clone wizard



Hi all,

The attached patch adds a small wizard for cloning a VM. A screenshot of
the overview:

http://fedorapeople.org/~crobinso/virt-manager/clone-post/vmm-clone-overview.png

The wizard will generate a new VM name (usually <orig-name>-clone), new
storage paths as required, and MAC addresses. Storage is marked as
one of:

- Shared: Original and clone VM point to the same disk image
- Clone : Actually copy the original storage for use by the clone

Storage like removable media (cdrom, floppy), readonly or shareable
disks will be 'Shared' by default.

The storage drop down has a 'Details' choice:

http://fedorapeople.org/~crobinso/virt-manager/clone-post/vmm-clone-dropdown.png

This brings up a small dialog which allows changing the new disk path:

http://fedorapeople.org/~crobinso/virt-manager/clone-post/vmm-clone-storage-details.png

There is also a similar dialog for changing MAC addresses.

If we can't clone storage (maybe lack of permissions, or remote
unmanaged storage, older libvirt), we still allow cloning the VM, but
force the offending disks into 'Shared' mode. In the case of sharing a
read/write disk, we give a clear warning that this may result in
overwriting the original image.

Big thanks to Jeremy Perry for the UI designs.

Questions, comments, or feedback appreciated.

- Cole
# HG changeset patch
# User Cole Robinson <crobinso redhat com>
# Date 1248109741 14400
# Node ID a50a824071603cdcdfe0f60b10430a7e60e29fb1
# Parent  c38447a9cca39676057de40d7be560660763dee7
Add 'Clone VM' wizard.

diff -r c38447a9cca3 -r a50a82407160 src/virtManager/clone.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/virtManager/clone.py	Mon Jul 20 13:09:01 2009 -0400
@@ -0,0 +1,800 @@
+#
+# Copyright (C) 2009 Red Hat, Inc.
+# Copyright (C) 2009 Cole Robinson <crobinso redhat com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA.
+#
+
+import traceback
+import logging
+import os
+
+import gobject
+import gtk.glade
+
+from virtManager.error import vmmErrorDialog
+from virtManager.asyncjob import vmmAsyncJob
+from virtManager.createmeter import vmmCreateMeter
+from virtManager.storagebrowse import vmmStorageBrowser
+from virtManager import util
+
+import virtinst
+from virtinst import CloneManager
+from virtinst.CloneManager import CloneDesign
+from virtinst import VirtualNetworkInterface
+
+STORAGE_COMBO_CLONE = 0
+STORAGE_COMBO_SHARE = 1
+STORAGE_COMBO_SEP = 2
+STORAGE_COMBO_DETAILS = 3
+
+STORAGE_INFO_ORIG_PATH = 0
+STORAGE_INFO_NEW_PATH = 1
+STORAGE_INFO_TARGET = 2
+STORAGE_INFO_SIZE = 3
+STORAGE_INFO_DEVTYPE = 4
+STORAGE_INFO_DO_CLONE = 5
+STORAGE_INFO_CAN_CLONE = 6
+STORAGE_INFO_DO_DEFAULT = 7
+STORAGE_INFO_DEFINFO = 8
+STORAGE_INFO_FAILINFO = 9
+STORAGE_INFO_COMBO = 10
+
+NETWORK_INFO_LABEL = 0
+NETWORK_INFO_ORIG_MAC = 1
+NETWORK_INFO_NEW_MAC = 2
+
+# XXX: Some method to check all storage size
+# XXX: What to do for cleanup if clone fails?
+
+class vmmCloneVM(gobject.GObject):
+    __gsignals__ = {
+        "action-show-help": (gobject.SIGNAL_RUN_FIRST,
+                             gobject.TYPE_NONE, [str]),
+    }
+
+    def __init__(self, config, orig_vm):
+        self.__gobject_init__()
+        self.config = config
+        self.orig_vm = orig_vm
+
+        self.window = gtk.glade.XML(self.config.get_glade_dir() + \
+                                    "/vmm-clone.glade",
+                                    "vmm-clone", domain="virt-manager")
+        self.topwin = self.window.get_widget("vmm-clone")
+
+        self.change_mac_window = gtk.glade.XML(self.config.get_glade_dir() + \
+                                               "/vmm-clone.glade",
+                                               "vmm-change-mac",
+                                               domain="virt-manager")
+        self.change_mac = self.change_mac_window.get_widget("vmm-change-mac")
+        self.change_mac_window.signal_autoconnect({
+            "on_vmm_change_mac_delete_event": self.change_mac_close,
+            "on_change_mac_cancel_clicked" : self.change_mac_close,
+            "on_change_mac_ok_clicked" : self.change_mac_finish,
+        })
+
+        self.change_storage_window = gtk.glade.XML(self.config.get_glade_dir()\
+                                                   + "/vmm-clone.glade",
+                                                   "vmm-change-storage",
+                                                   domain="virt-manager")
+        self.change_storage = self.change_storage_window.get_widget("vmm-change-storage")
+        self.change_storage_window.signal_autoconnect({
+            "on_vmm_change_storage_delete_event": self.change_storage_close,
+            "on_change_storage_cancel_clicked" : self.change_storage_close,
+            "on_change_storage_ok_clicked" : self.change_storage_finish,
+            "on_change_storage_doclone_toggled" : self.change_storage_doclone_toggled,
+
+            "on_change_storage_browse_clicked" : self.change_storage_browse,
+        })
+
+        self.err = vmmErrorDialog(self.topwin,
+                                  0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+                                  _("Unexpected Error"),
+                                  _("An unexpected error occurred"))
+        self.topwin.hide()
+
+        self.conn = self.orig_vm.connection
+        self.clone_design = None
+
+        self.storage_list = {}
+        self.target_list = []
+
+        self.net_list = {}
+        self.mac_list = []
+
+        self.storagemenu = None
+        self.netmenu = None
+        self.storage_browser = None
+
+        self.window.signal_autoconnect({
+            "on_clone_delete_event" : self.close,
+            "on_clone_cancel_clicked" : self.close,
+            "on_clone_ok_clicked" : self.finish,
+            "on_clone_help_clicked" : self.show_help,
+        })
+
+        self.set_initial_state()
+
+    def show(self):
+        self.reset_state()
+        self.topwin.show()
+        self.topwin.present()
+
+    def close(self, ignore1=None, ignore2=None):
+        self.topwin.hide()
+        self.change_mac_close()
+        self.change_storage_close()
+        return 1
+
+    def change_mac_close(self, ignore1=None, ignore2=None):
+        self.change_mac.hide()
+        return 1
+
+    def change_storage_close(self, ignore1=None, ignore2=None):
+        self.change_storage.hide()
+        return 1
+
+
+    # First time setup
+
+    def set_initial_state(self):
+
+        blue = gtk.gdk.color_parse("#0072A8")
+        self.window.get_widget("clone-header").modify_bg(gtk.STATE_NORMAL,
+                                                         blue)
+
+        box = self.window.get_widget("clone-vm-icon-box")
+        iconfile = self.config.get_icon_dir() + "/virt-manager-icon.svg"
+        icon_pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(iconfile, 48, 48)
+        image = gtk.Image()
+        image.set_from_pixbuf(icon_pixbuf)
+        image.show()
+        box.pack_end(image, False)
+
+    # Populate state
+    def reset_state(self):
+        # Populate default clone values
+        self.setup_clone_info()
+
+        cd = self.clone_design
+        self.window.get_widget("clone-orig-name").set_text(cd.original_guest)
+        self.window.get_widget("clone-new-name").set_text(cd.clone_name)
+
+        # We need to determine which disks fail (and why).
+        self.storage_list, self.target_list = self.check_all_storage()
+
+        self.populate_storage_lists()
+        self.populate_network_list()
+
+        return
+
+    def setup_clone_info(self):
+        self.clone_design = self.build_new_clone_design()
+
+    def build_new_clone_design(self, new_name=None):
+        cd = CloneDesign(self.conn.vmm)
+        cd.original_guest = self.orig_vm.get_name()
+        if not new_name:
+            new_name = virtinst.CloneManager.generate_clone_name(cd)
+        cd.clone_name = new_name
+
+        # Erase any clone_policy from the original design, so that we
+        # get the entire device list.
+        cd.clone_policy = []
+
+        return cd
+
+    def populate_network_list(self):
+        net_box = self.window.get_widget("clone-network-box")
+        for c in net_box.get_children():
+            net_box.remove(c)
+
+        self.net_list = {}
+        self.mac_list = []
+
+        def build_net_row(labelstr, origmac, newmac):
+
+            label = gtk.Label(labelstr + " (%s)" % origmac)
+            label.set_alignment(0, .5)
+            button = gtk.Button(_("Details..."))
+            button.connect("clicked", self.net_change_mac, origmac)
+
+            hbox = gtk.HBox()
+            hbox.set_spacing(12)
+            hbox.pack_start(label)
+            hbox.pack_end(button, False, False)
+            hbox.show_all()
+            net_box.pack_start(hbox, False, False)
+
+            net_row = []
+            net_row.insert(NETWORK_INFO_LABEL, labelstr)
+            net_row.insert(NETWORK_INFO_ORIG_MAC, origmac)
+            net_row.insert(NETWORK_INFO_NEW_MAC, newmac)
+            self.net_list[origmac] = net_row
+            self.mac_list.append(origmac)
+
+        for net in self.orig_vm.get_network_devices():
+            mac = net[2]
+            net_dev = net[3]
+            net_type = net[5]
+
+            # Generate a new MAC
+            obj = VirtualNetworkInterface(conn=self.conn.vmm,
+                                          type=VirtualNetworkInterface.TYPE_USER)
+            obj.setup(self.conn.vmm)
+            newmac = obj.macaddr
+
+
+            # [ interface type, device name, origmac, newmac, label ]
+            if net_type == VirtualNetworkInterface.TYPE_USER:
+                label = _("Usermode")
+
+            elif net_type == VirtualNetworkInterface.TYPE_VIRTUAL:
+                net = self.orig_vm.get_connection().get_net_by_name(net_dev)
+
+                if net:
+                    label = ""
+
+                    use_nat, host_dev = net.get_ipv4_forward()
+                    if not use_nat:
+                        desc = _("Isolated network")
+                    elif host_dev:
+                        desc = _("NAT to %s") % host_dev
+                    else:
+                        desc = _("NAT")
+                    label += "%s" % desc
+
+                else:
+                    label = (_("Virtual Network") +
+                             (net_dev and " %s" % net_dev or ""))
+
+            else:
+                # 'bridge' or anything else
+                label = (net_type.capitalize() +
+                         net_dev and " %s" % net_dev or "")
+
+            build_net_row(label, mac, newmac)
+
+        no_net = bool(len(self.net_list.keys()) == 0)
+        self.window.get_widget("clone-network-box").set_property("visible",
+                                                                 not no_net)
+        self.window.get_widget("clone-no-net").set_property("visible", no_net)
+
+    def check_all_storage(self):
+        """
+        Determine which storage is cloneable, and which isn't
+        """
+        diskinfos = self.orig_vm.get_disk_devices()
+        cd = self.clone_design
+
+        storage_list = {}
+
+        # We need to determine which disks fail (and why).
+        all_targets = map(lambda d: d[1], diskinfos)
+
+        for disk in diskinfos:
+            force_target = disk[1]
+            path = disk[3]
+            ro = disk[6]
+            shared = disk[7]
+            devtype = disk[4]
+
+            size = None
+            clone_path = None
+            failinfo = ""
+            definfo = ""
+
+            storage_row = []
+            storage_row.insert(STORAGE_INFO_ORIG_PATH, path)
+            storage_row.insert(STORAGE_INFO_NEW_PATH, clone_path)
+            storage_row.insert(STORAGE_INFO_TARGET, force_target)
+            storage_row.insert(STORAGE_INFO_SIZE, size)
+            storage_row.insert(STORAGE_INFO_DEVTYPE, devtype)
+            storage_row.insert(STORAGE_INFO_DO_CLONE, False)
+            storage_row.insert(STORAGE_INFO_CAN_CLONE, False)
+            storage_row.insert(STORAGE_INFO_DO_DEFAULT, False)
+            storage_row.insert(STORAGE_INFO_DEFINFO, definfo)
+            storage_row.insert(STORAGE_INFO_FAILINFO, failinfo)
+            storage_row.insert(STORAGE_INFO_COMBO, None)
+
+            skip_targets = all_targets[:]
+            skip_targets.remove(force_target)
+
+            vol = self.conn.get_vol_by_path(path)
+            default, definfo = do_we_default(self.conn, vol, path, ro, shared,
+                                             devtype)
+
+            def storage_add(failinfo=None):
+                storage_row[STORAGE_INFO_DEFINFO] = definfo
+                storage_row[STORAGE_INFO_DO_DEFAULT] = default
+                if failinfo:
+                    storage_row[STORAGE_INFO_FAILINFO] = failinfo
+                    storage_row[STORAGE_INFO_DO_CLONE] = False
+
+                storage_list[force_target] = storage_row
+
+            # If origdisk is empty, deliberately make it fail
+            if not path:
+                storage_add(_("Nothing to clone."))
+                continue
+
+            try:
+                cd.skip_target = skip_targets
+                cd.setup_original()
+            except Exception, e:
+                logging.exception("Disk target '%s' caused clone error" %
+                                  force_target)
+                storage_add(str(e))
+                continue
+
+            can_clone, cloneinfo = can_we_clone(self.conn, vol, path)
+            if not can_clone:
+                storage_add(cloneinfo)
+                continue
+
+            try:
+                # Generate disk path, make sure that works
+                clone_path = None
+                clone_path = CloneManager.generate_clone_disk_path(path, cd)
+
+                logging.debug("Original path: %s\nGenerated clone path: %s" %
+                              (path, clone_path))
+
+                cd.clone_devices = clone_path
+                size = cd.original_virtual_disks[0].size
+            except Exception, e:
+                logging.exception("Error setting generated path '%s'" %
+                                  clone_path)
+                storage_add(str(e))
+
+            storage_row[STORAGE_INFO_CAN_CLONE] = True
+            storage_row[STORAGE_INFO_NEW_PATH] = clone_path
+            storage_row[STORAGE_INFO_SIZE] = self.pretty_storage(size)
+            storage_add()
+
+        return storage_list, all_targets
+
+    def build_storage_entry(self, disk, storage_box):
+        origpath = disk[STORAGE_INFO_ORIG_PATH]
+        devtype = disk[STORAGE_INFO_DEVTYPE]
+        size = disk[STORAGE_INFO_SIZE]
+        can_clone = disk[STORAGE_INFO_CAN_CLONE]
+        is_default = disk[STORAGE_INFO_DO_DEFAULT]
+        definfo = disk[STORAGE_INFO_DEFINFO]
+        failinfo = disk[STORAGE_INFO_FAILINFO]
+        target = disk[STORAGE_INFO_TARGET]
+
+        orig_name = self.orig_vm.get_name()
+
+        disk_label = os.path.basename(origpath)
+        info_label = None
+        if not can_clone:
+            info_label = gtk.Label()
+            info_label.set_alignment(0, .5)
+            info_label.set_markup("<span size='small'>%s</span>" % failinfo)
+        if not is_default:
+            disk_label += (definfo and " (%s)" % definfo or "")
+
+        # Build icon
+        icon = gtk.Image()
+        if devtype == virtinst.VirtualDisk.DEVICE_FLOPPY:
+            iconname = "media-floppy"
+        elif devtype == virtinst.VirtualDisk.DEVICE_CDROM:
+            iconname = "media-optical"
+        else:
+            iconname = "drive-harddisk"
+        icon.set_from_icon_name(iconname, gtk.ICON_SIZE_MENU)
+        disk_name_label = gtk.Label(disk_label)
+        disk_name_label.set_alignment(0, .5)
+        disk_name_box = gtk.HBox(spacing=9)
+        disk_name_box.pack_start(icon, False)
+        disk_name_box.pack_start(disk_name_label, True)
+
+        def sep_func(model, it, combo):
+            return model[it][2]
+
+        # [String, sensitive, is sep]
+        model = gtk.ListStore(str, bool, bool)
+        model.insert(STORAGE_COMBO_CLONE,
+                     [(_("Clone this disk") + (size and " (%s)" % size or "")),
+                     can_clone, False])
+        model.insert(STORAGE_COMBO_SHARE,
+                     [_("Share disk with %s") % orig_name, True, False])
+        model.insert(STORAGE_COMBO_SEP, ["", False, True])
+        model.insert(STORAGE_COMBO_DETAILS,
+                     [_("Details..."), True, False])
+
+        option_combo = gtk.ComboBox(model)
+        text = gtk.CellRendererText()
+        option_combo.pack_start(text)
+        option_combo.add_attribute(text, "text", 0)
+        option_combo.add_attribute(text, "sensitive", 1)
+        option_combo.set_row_separator_func(sep_func, option_combo)
+        option_combo.connect("changed", self.storage_combo_changed, target)
+
+        vbox = gtk.VBox(spacing=1)
+        vbox.pack_start(disk_name_box, False, False)
+        vbox.pack_start(option_combo, False, False)
+        if info_label:
+            vbox.pack_start(info_label, False, False)
+        storage_box.pack_start(vbox, False, False)
+
+        if can_clone and is_default:
+            option_combo.set_active(STORAGE_COMBO_CLONE)
+        else:
+            option_combo.set_active(STORAGE_COMBO_SHARE)
+        disk[STORAGE_INFO_COMBO] = option_combo
+
+    def populate_storage_lists(self):
+        storage_box = self.window.get_widget("clone-storage-box")
+        for c in storage_box.get_children():
+            storage_box.remove(c)
+
+        for target in self.target_list:
+            disk = self.storage_list[target]
+            self.build_storage_entry(disk, storage_box)
+
+        num_c = min(len(self.target_list), 3)
+        if num_c:
+            scroll = self.window.get_widget("clone-storage-scroll")
+            scroll.set_size_request(-1, 80*num_c)
+        storage_box.show_all()
+
+        no_storage = not bool(len(self.target_list))
+        self.window.get_widget("clone-storage-box").set_property("visible",not no_storage)
+        self.window.get_widget("clone-no-storage-pass").set_property("visible", no_storage)
+
+        skip_targets = []
+        new_disks = []
+        for target in self.target_list:
+            do_clone = self.storage_list[target][STORAGE_INFO_DO_CLONE]
+            new_path = self.storage_list[target][STORAGE_INFO_NEW_PATH]
+
+            if do_clone:
+                new_disks.append(new_path)
+            else:
+                skip_targets.append(target)
+
+        self.clone_design.skip_target = skip_targets
+        self.clone_design.clone_devices = new_disks
+
+    def net_show_popup(self, widget, event):
+        if event.button != 3:
+            return
+
+        self.netmenu.popup(None, None, None, 0, event.time)
+
+    def net_change_mac(self, ignore, origmac):
+        row = self.net_list[origmac]
+        orig_mac = row[NETWORK_INFO_ORIG_MAC]
+        new_mac =  row[NETWORK_INFO_NEW_MAC]
+        typ = row[NETWORK_INFO_LABEL]
+
+        self.change_mac_window.get_widget("change-mac-orig").set_text(orig_mac)
+        self.change_mac_window.get_widget("change-mac-type").set_text(typ)
+        self.change_mac_window.get_widget("change-mac-new").set_text(new_mac)
+
+        self.change_mac.show_all()
+
+    def storage_show_popup(self, widget, event):
+        if event.button != 3:
+            return
+
+        self.storagemenu.popup(None, None, None, 0, event.time)
+
+    def storage_combo_changed(self, src, target):
+        idx = src.get_active()
+        row = self.storage_list[target]
+
+        if idx == STORAGE_COMBO_CLONE:
+            row[STORAGE_INFO_DO_CLONE] = True
+            return
+        elif idx == STORAGE_COMBO_SHARE:
+            row[STORAGE_INFO_DO_CLONE] = False
+            return
+        elif idx != STORAGE_COMBO_DETAILS:
+            return
+
+        do_clone = row[STORAGE_INFO_DO_CLONE]
+        if do_clone:
+            src.set_active(STORAGE_COMBO_CLONE)
+        else:
+            src.set_active(STORAGE_COMBO_SHARE)
+
+        # Show storage
+        row = self.storage_change_path(row)
+
+    def change_storage_doclone_toggled(self, src):
+        do_clone = src.get_active()
+
+        cs = self.change_storage_window
+        cs.get_widget("change-storage-new").set_sensitive(do_clone)
+        cs.get_widget("change-storage-browse").set_sensitive(do_clone)
+
+    def storage_change_path(self, row):
+        orig = row[STORAGE_INFO_ORIG_PATH]
+        new  = row[STORAGE_INFO_NEW_PATH]
+        tgt  = row[STORAGE_INFO_TARGET]
+        size = row[STORAGE_INFO_SIZE]
+        can_clone = row[STORAGE_INFO_CAN_CLONE]
+        do_clone = row[STORAGE_INFO_DO_CLONE]
+
+        cs = self.change_storage_window
+        cs.get_widget("change-storage-doclone").set_active(True)
+        cs.get_widget("change-storage-doclone").toggled()
+        cs.get_widget("change-storage-orig").set_text(orig)
+        cs.get_widget("change-storage-target").set_text(tgt)
+        cs.get_widget("change-storage-size").set_text(size or "-")
+        cs.get_widget("change-storage-doclone").set_active(do_clone)
+
+        if can_clone:
+            cs.get_widget("change-storage-new").set_text(new or "")
+        else:
+            cs.get_widget("change-storage-new").set_text("")
+        cs.get_widget("change-storage-doclone").set_sensitive(can_clone)
+
+        cs.get_widget("vmm-change-storage").show_all()
+
+    def set_orig_vm(self, new_orig):
+        self.orig_vm = new_orig
+        self.conn = self.orig_vm.connection
+
+    def change_mac_finish(self, ignore):
+        orig = self.change_mac_window.get_widget("change-mac-orig").get_text()
+        new = self.change_mac_window.get_widget("change-mac-new").get_text()
+        row = self.net_list[orig]
+
+        try:
+            VirtualNetworkInterface(conn=self.conn.vmm,
+                                    type=VirtualNetworkInterface.TYPE_USER,
+                                    macaddr=new)
+            row[NETWORK_INFO_NEW_MAC] = new
+        except Exception, e:
+            self.err.show_err(_("Error changing MAC address: %s") % str(e),
+                                "".join(traceback.format_exc()))
+            return
+
+        self.change_mac_close()
+
+    def change_storage_finish(self, ignore):
+        cs = self.change_storage_window
+        target = cs.get_widget("change-storage-target").get_text()
+        row = self.storage_list[target]
+
+        # Sync 'do clone' checkbox, and main dialog combo
+        combo = row[STORAGE_INFO_COMBO]
+        if cs.get_widget("change-storage-doclone").get_active():
+            combo.set_active(STORAGE_COMBO_CLONE)
+        else:
+            combo.set_active(STORAGE_COMBO_SHARE)
+
+        do_clone = row[STORAGE_INFO_DO_CLONE]
+        if not do_clone:
+            self.change_storage_close()
+            return
+
+        new_path = cs.get_widget("change-storage-new").get_text()
+
+        if virtinst.VirtualDisk.path_exists(self.clone_design.original_conn,
+                                            new_path):
+            res = self.err.yes_no(_("Cloning will overwrite the existing "
+                                    "file"),
+                                    _("Using an existing image will overwrite "
+                                      "the path during the clone process. Are "
+                                      "you sure you want to use this path?"))
+            if not res:
+                return
+
+        try:
+            self.clone_design.clone_devices = new_path
+            self.populate_storage_lists()
+            row[STORAGE_INFO_NEW_PATH] = new_path
+        except Exception, e:
+            self.err.show_err(_("Error changing storage path: %s") % str(e),
+                                "".join(traceback.format_exc()))
+            return
+
+        self.change_storage_close()
+
+    def pretty_storage(self, size):
+        if not size:
+            return ""
+        return "%.1f GB" % float(size)
+
+    # Listeners
+    def validate(self):
+        name = self.window.get_widget("clone-new-name").get_text()
+
+        # Make another clone_design
+        cd = self.build_new_clone_design(name)
+
+        # Set MAC addresses
+        for mac in self.mac_list:
+            row = self.net_list[mac]
+            new_mac = row[NETWORK_INFO_NEW_MAC]
+            cd.clone_mac = new_mac
+
+        skip_targets = []
+        new_paths = []
+        warn_str = ""
+        for target in self.target_list:
+            path = self.storage_list[target][STORAGE_INFO_ORIG_PATH]
+            new_path = self.storage_list[target][STORAGE_INFO_NEW_PATH]
+            do_clone = self.storage_list[target][STORAGE_INFO_DO_CLONE]
+            do_default = self.storage_list[target][STORAGE_INFO_DO_DEFAULT]
+
+            if do_clone:
+                new_paths.append(new_path)
+            else:
+                skip_targets.append(target)
+                if not path or path == '-':
+                    continue
+
+                if not do_default:
+                    continue
+
+                warn_str += "%s: %s\n" % (target, path)
+
+        cd.skip_target = skip_targets
+        cd.setup_original()
+        cd.clone_devices = new_paths
+
+        if warn_str:
+            res = self.err.ok_cancel(
+                _("Skipping disks may cause data to be overwritten."),
+                _("The following disk devices will not be cloned:\n\n%s\n"
+                  "Running the new guest could overwrite data in these "
+                  "disk images.")
+                  % warn_str)
+
+            if not res:
+                return False
+
+        cd.setup_clone()
+
+        self.clone_design = cd
+        return True
+
+    def finish(self, src):
+
+        # validate input
+        try:
+            if not self.validate():
+                return
+        except Exception, e:
+            self.err.show_err(_("Uncaught error validating input: %s") % str(e),
+                                "".join(traceback.format_exc()))
+            return
+
+        self.topwin.set_sensitive(False)
+        self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+
+        title = (_("Creating virtual machine clone '%s'") %
+                 self.clone_design.clone_name)
+        text = title
+        if self.clone_design.clone_devices:
+            text = title + _(" and selected storage (this may take a while)")
+
+        progWin = vmmAsyncJob(self.config, self._async_clone, [],
+                              title=title, text=text)
+        progWin.run()
+        error, details = progWin.get_error()
+
+        self.topwin.set_sensitive(True)
+        self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
+
+        if error is not None:
+            self.err.show_err(error, details)
+        else:
+            self.close()
+            self.conn.tick(noStatsUpdate=True)
+
+    def _async_clone(self, asyncjob):
+        newconn = None
+        error = None
+        details = None
+
+        try:
+            # Open a seperate connection to install on since this is async
+            logging.debug("Threading off connection to clone VM.")
+            newconn = util.dup_conn(self.config, self.conn)
+            meter = vmmCreateMeter(asyncjob)
+
+            self.clone_design.orig_connection = newconn
+            for d in self.clone_design.clone_virtual_disks:
+                d.conn = newconn
+
+            self.clone_design.setup()
+            CloneManager.start_duplicate(self.clone_design, meter)
+        except Exception, e:
+            error = (_("Error creating virtual machine clone '%s': %s") %
+                      (self.clone_design.clone_name, str(e)))
+            details = "".join(traceback.format_exc())
+
+        if error:
+            asyncjob.set_error(error, details)
+        return
+
+    def change_storage_browse(self, ignore):
+
+        cs = self.change_storage_window
+        def callback(self, txt):
+            cs.get_widget("change-storage-new").set_text(txt)
+
+        if self.storage_browser == None:
+            self.storage_browser = vmmStorageBrowser(self.config, self.conn)
+            self.storage_browser.connect("storage-browse-finish", callback)
+
+        self.storage_browser.show(self.conn)
+
+    def show_help(self, ignore1=None):
+        # Nothing yet
+        return
+
+gobject.type_register(vmmCloneVM)
+
+def can_we_clone(conn, vol, path):
+    """Is the passed path even clone-able"""
+    ret = True
+    msg = None
+
+    if not path or path == "-":
+        msg = _("No storage to clone.")
+
+    elif vol:
+        # Managed storage
+        if not virtinst.Storage.is_create_vol_from_supported(conn):
+            if conn.is_remote() or not os.access(path, os.R_OK):
+                msg = _("Connection does not support managed storage cloning.")
+    else:
+        if conn.is_remote():
+            msg = _("Cannot clone unmanaged remote storage.")
+        elif not os.access(path, os.R_OK):
+            msg = _("No write access to parent directory.")
+        elif not os.path.exists(path):
+            msg = _("Path does not exist.")
+
+    if msg:
+        ret = False
+
+    return (ret, msg)
+
+def do_we_default(conn, vol, path, ro, shared, devtype):
+    """ Returns (do we clone by default?, info string if not)"""
+    info = ""
+
+    def append_str(str1, str2, delim=", "):
+        if not str2:
+            return str1
+        if str1:
+            str1 += delim
+        str1 += str2
+        return str1
+
+    if (devtype == virtinst.VirtualDisk.DEVICE_CDROM or
+        devtype == virtinst.VirtualDisk.DEVICE_FLOPPY):
+        info = append_str(info, _("Removable"))
+
+    if ro:
+        info = append_str(info, _("Read Only"))
+    elif not vol and not os.access(path, os.W_OK):
+        info = append_str(info, _("No write access"))
+
+    if shared:
+        info = append_str(info, _("Shareable"))
+
+    return (not info, info)
diff -r c38447a9cca3 -r a50a82407160 src/virtManager/connection.py
--- a/src/virtManager/connection.py	Tue Jul 14 22:04:34 2009 -0400
+++ b/src/virtManager/connection.py	Mon Jul 20 13:09:01 2009 -0400
@@ -243,6 +243,11 @@
     def get_net(self, uuid):
         return self.nets[uuid]
 
+    def get_net_by_name(self, name):
+        for net in self.nets.values():
+            if net.get_name() == name:
+                return net
+
     def get_net_device(self, path):
         if not self.netdev_helper:
             raise ValueError("No netdev helper specified.")
diff -r c38447a9cca3 -r a50a82407160 src/virtManager/details.py
--- a/src/virtManager/details.py	Tue Jul 14 22:04:34 2009 -0400
+++ b/src/virtManager/details.py	Mon Jul 20 13:09:01 2009 -0400
@@ -106,6 +106,8 @@
                                 gobject.TYPE_NONE, []),
         "action-migrate-domain": (gobject.SIGNAL_RUN_FIRST,
                                   gobject.TYPE_NONE, (str,str,str)),
+        "action-clone-domain": (gobject.SIGNAL_RUN_FIRST,
+                                gobject.TYPE_NONE, (str,str)),
         }
 
 
@@ -282,6 +284,7 @@
             "on_details_menu_destroy_activate": self.control_vm_destroy,
             "on_details_menu_pause_activate": self.control_vm_pause,
             "on_details_menu_migrate_activate": self.populate_migrate_menu,
+            "on_details_menu_clone_activate": self.control_vm_clone,
             "on_details_menu_screenshot_activate": self.control_vm_screenshot,
             "on_details_menu_graphics_activate": self.control_vm_console,
             "on_details_menu_view_toolbar_activate": self.toggle_toolbar,
@@ -737,6 +740,10 @@
     def control_vm_destroy(self, src):
         self.emit("action-destroy-domain", self.vm.get_connection().get_uri(), self.vm.get_uuid())
 
+    def control_vm_clone(self, src):
+        self.emit("action-clone-domain", self.vm.get_connection().get_uri(),
+                  self.vm.get_uuid())
+
     def control_vm_migrate(self, src, uri):
         self.emit("action-migrate-domain", self.vm.get_connection().get_uri(),
                   self.vm.get_uuid(), uri)
diff -r c38447a9cca3 -r a50a82407160 src/virtManager/engine.py
--- a/src/virtManager/engine.py	Tue Jul 14 22:04:34 2009 -0400
+++ b/src/virtManager/engine.py	Mon Jul 20 13:09:01 2009 -0400
@@ -28,6 +28,7 @@
 
 from virtManager.about import vmmAbout
 from virtManager.netdevhelper import vmmNetDevHelper
+from virtManager.clone import vmmCloneVM
 from virtManager.connect import vmmConnect
 from virtManager.connection import vmmConnection
 from virtManager.createmeter import vmmCreateMeter
@@ -229,6 +230,8 @@
         self.reboot_domain(src, uri, uuid)
     def _do_migrate_domain(self, src, uri, uuid, desturi):
         self.migrate_domain(uri, uuid, desturi)
+    def _do_clone_domain(self, src, uri, uuid):
+        self.clone_domain(uri, uuid)
     def _do_exit_app(self, src):
         self.exit_app()
 
@@ -305,6 +308,7 @@
                 details.connect("action-exit-app", self._do_exit_app)
                 details.connect("action-view-manager", self._do_show_manager)
                 details.connect("action-migrate-domain", self._do_migrate_domain)
+                details.connect("action-clone-domain", self._do_clone_domain)
 
             except Exception, e:
                 self.err.show_err(_("Error bringing up domain details: %s") % str(e),
@@ -323,6 +327,7 @@
             self.windowManager.connect("action-reboot-domain", self._do_reboot_domain)
             self.windowManager.connect("action-destroy-domain", self._do_destroy_domain)
             self.windowManager.connect("action-migrate-domain", self._do_migrate_domain)
+            self.windowManager.connect("action-clone-domain", self._do_clone_domain)
             self.windowManager.connect("action-show-console", self._do_show_console)
             self.windowManager.connect("action-show-details", self._do_show_details)
             self.windowManager.connect("action-show-preferences", self._do_show_preferences)
@@ -381,6 +386,7 @@
             "windowHost": None,
             "windowDetails": {},
             "windowConsole": {},
+            "windowClone": None,
             }
         self.connections[uri]["connection"].connect("vm-removed", self._do_vm_removed)
         self.connections[uri]["connection"].connect("state-changed", self._do_connection_changed)
@@ -698,5 +704,23 @@
 
         return available_migrate_hostnames
 
+    def clone_domain(self, uri, uuid):
+        con = self._lookup_connection(uri)
+        orig_vm = con.get_vm(uuid)
+        clone_window = self.connections[uri]["windowClone"]
+
+        try:
+            if clone_window == None:
+                clone_window = vmmCloneVM(self.get_config(), orig_vm)
+                clone_window.connect("action-show-help", self._do_show_help)
+                self.connections[uri]["windowClone"] = clone_window
+            else:
+                clone_window.set_orig_vm(orig_vm)
+
+            clone_window.show()
+        except Exception, e:
+            self.err.show_err(_("Error setting clone parameters: %s") %
+                              str(e), "".join(traceback.format_exc()))
+
 
 gobject.type_register(vmmEngine)
diff -r c38447a9cca3 -r a50a82407160 src/virtManager/manager.py
--- a/src/virtManager/manager.py	Tue Jul 14 22:04:34 2009 -0400
+++ b/src/virtManager/manager.py	Mon Jul 20 13:09:01 2009 -0400
@@ -107,6 +107,8 @@
                                gobject.TYPE_NONE, [str]),
         "action-migrate-domain": (gobject.SIGNAL_RUN_FIRST,
                                   gobject.TYPE_NONE, (str,str,str)),
+        "action-clone-domain": (gobject.SIGNAL_RUN_FIRST,
+                                gobject.TYPE_NONE, (str,str)),
         "action-exit-app": (gobject.SIGNAL_RUN_FIRST,
                             gobject.TYPE_NONE, []),}
 
@@ -231,6 +233,13 @@
                                              self.populate_migrate_submenu)
         self.vmmenu.add(self.vmmenu_items["migrate"])
 
+        self.vmmenu_items["clone"] = gtk.ImageMenuItem("_Clone")
+        self.vmmenu_items["clone"].show()
+        self.vmmenu_items["clone"].connect("activate", self.open_clone_window)
+        self.vmmenu.add(self.vmmenu_items["clone"])
+
+        self.vmmenu_items["hsep2"] = gtk.SeparatorMenuItem()
+
         self.vmmenu_items["hsep2"] = gtk.SeparatorMenuItem()
         self.vmmenu_items["hsep2"].show()
         self.vmmenu.add(self.vmmenu_items["hsep2"])
@@ -704,6 +713,12 @@
         elif self.current_connection():
             self.open_connection()
 
+    def open_clone_window(self, ignore1=None, ignore2=None, ignore3=None):
+        if self.current_vmuuid():
+            self.emit("action-clone-domain", self.current_connection_uri(),
+                      self.current_vmuuid())
+        elif self.current_connection():
+            print "no clone connection"
 
     def vm_selected(self, selection):
         conn = self.current_connection()
diff -r c38447a9cca3 -r a50a82407160 src/vmm-clone.glade
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/vmm-clone.glade	Mon Jul 20 13:09:01 2009 -0400
@@ -0,0 +1,872 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Mon Jul 20 12:13:44 2009 -->
+<glade-interface>
+  <widget class="GtkDialog" id="vmm-change-mac">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Change MAC address</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <signal name="delete_event" handler="on_vmm_change_mac_delete_event"/>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkAlignment" id="alignment4">
+            <property name="visible">True</property>
+            <property name="top_padding">6</property>
+            <property name="left_padding">6</property>
+            <child>
+              <widget class="GtkVBox" id="vbox8">
+                <property name="visible">True</property>
+                <property name="spacing">12</property>
+                <child>
+                  <widget class="GtkHBox" id="hbox4">
+                    <property name="visible">True</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <widget class="GtkTable" id="table6">
+                        <property name="visible">True</property>
+                        <property name="n_rows">3</property>
+                        <property name="n_columns">3</property>
+                        <property name="column_spacing">6</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <widget class="GtkAlignment" id="alignment9">
+                            <property name="visible">True</property>
+                            <property name="left_padding">2</property>
+                            <child>
+                              <widget class="GtkLabel" id="change-mac-type">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label">type string</property>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">2</property>
+                            <property name="right_attach">3</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkAlignment" id="alignment8">
+                            <property name="visible">True</property>
+                            <property name="left_padding">2</property>
+                            <child>
+                              <widget class="GtkLabel" id="change-mac-orig">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label">orig-mac</property>
+                                <property name="width_chars">20</property>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">2</property>
+                            <property name="right_attach">3</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkEntry" id="change-mac-new">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">2</property>
+                            <property name="right_attach">3</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label10">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">New MAC:</property>
+                          </widget>
+                          <packing>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkAlignment" id="alignment7">
+                            <property name="visible">True</property>
+                            <property name="left_padding">6</property>
+                            <property name="right_padding">7</property>
+                            <child>
+                              <widget class="GtkImage" id="image3">
+                                <property name="visible">True</property>
+                                <property name="icon_size">6</property>
+                                <property name="icon_name">network-idle</property>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label77">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;span color='#484848'&gt;Type:&lt;/span&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label16">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">&lt;span color='#484848'&gt;MAC:&lt;/span&gt;</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="change-mac-cancel">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+                <signal name="clicked" handler="on_change_mac_cancel_clicked"/>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="change-mac-ok">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+                <signal name="clicked" handler="on_change_mac_ok_clicked"/>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="vmm-change-storage">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Change storage path</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <signal name="delete_event" handler="on_vmm_change_storage_delete_event"/>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox2">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox2">
+            <property name="visible">True</property>
+            <property name="border_width">6</property>
+            <property name="spacing">12</property>
+            <child>
+              <widget class="GtkTable" id="table4">
+                <property name="visible">True</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">6</property>
+                <property name="row_spacing">6</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment6">
+                    <property name="visible">True</property>
+                    <property name="left_padding">18</property>
+                    <property name="right_padding">18</property>
+                    <child>
+                      <widget class="GtkImage" id="image2">
+                        <property name="visible">True</property>
+                        <property name="stock">gtk-harddisk</property>
+                        <property name="icon_size">6</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkTable" id="table5">
+                    <property name="visible">True</property>
+                    <property name="n_rows">3</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">12</property>
+                    <property name="row_spacing">6</property>
+                    <child>
+                      <widget class="GtkLabel" id="change-storage-size">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label">size</property>
+                      </widget>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="change-storage-target">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label">target</property>
+                      </widget>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="change-storage-orig">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label">orig-path</property>
+                        <property name="ellipsize">PANGO_ELLIPSIZE_START</property>
+                        <property name="width_chars">25</property>
+                      </widget>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="label15">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="yalign">1</property>
+                        <property name="label" translatable="yes">&lt;span color='#484848'&gt;Size:&lt;/span&gt;</property>
+                        <property name="use_markup">True</property>
+                      </widget>
+                      <packing>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="label14">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">&lt;span color='#484848'&gt;Target:&lt;/span&gt;</property>
+                        <property name="use_markup">True</property>
+                      </widget>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="label13">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">&lt;span color='#484848'&gt;Path:&lt;/span&gt;</property>
+                        <property name="use_markup">True</property>
+                      </widget>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="y_options">GTK_EXPAND</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Existing disk</property>
+                  </widget>
+                  <packing>
+                    <property name="right_attach">2</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkTable" id="table3">
+                <property name="visible">True</property>
+                <property name="border_width">6</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">3</property>
+                <property name="column_spacing">6</property>
+                <property name="row_spacing">6</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment5">
+                    <property name="visible">True</property>
+                    <property name="left_padding">22</property>
+                    <child>
+                      <widget class="GtkLabel" id="label12">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">&lt;span color='#484848'&gt;New Path:&lt;/span&gt;</property>
+                        <property name="use_markup">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkCheckButton" id="change-storage-doclone">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="label" translatable="yes">Create a new disk (clone) for the virtual machine</property>
+                    <property name="response_id">0</property>
+                    <property name="draw_indicator">True</property>
+                    <signal name="toggled" handler="on_change_storage_doclone_toggled"/>
+                  </widget>
+                  <packing>
+                    <property name="right_attach">3</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="change-storage-new">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="width_chars">25</property>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkButton" id="change-storage-browse">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="label" translatable="yes">Browse...</property>
+                    <property name="response_id">0</property>
+                    <signal name="clicked" handler="on_change_storage_browse_clicked"/>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">2</property>
+                    <property name="right_attach">3</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area2">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="change-storage-cancel">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+                <signal name="clicked" handler="on_change_storage_cancel_clicked"/>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="change-storage-ok">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+                <signal name="clicked" handler="on_change_storage_ok_clicked"/>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkWindow" id="vmm-clone">
+    <property name="title" translatable="yes">Clone Virtual Machine</property>
+    <signal name="delete_event" handler="on_clone_delete_event"/>
+    <child>
+      <widget class="GtkVBox" id="vbox1">
+        <property name="visible">True</property>
+        <child>
+          <widget class="GtkViewport" id="clone-header">
+            <property name="visible">True</property>
+            <property name="resize_mode">GTK_RESIZE_QUEUE</property>
+            <child>
+              <widget class="GtkHBox" id="hbox77">
+                <property name="visible">True</property>
+                <property name="border_width">6</property>
+                <property name="spacing">10</property>
+                <child>
+                  <widget class="GtkHBox" id="clone-vm-icon-box">
+                    <property name="visible">True</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHBox" id="hbox2">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkLabel" id="label2">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">&lt;span size='large' color='white'&gt;Clone virtual machine&lt;/span&gt;</property>
+                        <property name="use_markup">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkVBox" id="vbox4">
+            <property name="visible">True</property>
+            <property name="border_width">12</property>
+            <property name="spacing">12</property>
+            <child>
+              <widget class="GtkVBox" id="vbox3">
+                <property name="visible">True</property>
+                <property name="spacing">18</property>
+                <child>
+                  <widget class="GtkVBox" id="vbox5">
+                    <property name="visible">True</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">3</property>
+                        <child>
+                          <widget class="GtkLabel" id="label4">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Create a clone based on:</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="clone-orig-name">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label">orig name</property>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkAlignment" id="alignment1">
+                        <property name="visible">True</property>
+                        <property name="left_padding">12</property>
+                        <child>
+                          <widget class="GtkTable" id="table1">
+                            <property name="visible">True</property>
+                            <property name="n_rows">3</property>
+                            <property name="n_columns">2</property>
+                            <property name="column_spacing">18</property>
+                            <property name="row_spacing">10</property>
+                            <child>
+                              <widget class="GtkVBox" id="vbox6">
+                                <property name="visible">True</property>
+                                <child>
+                                  <widget class="GtkLabel" id="clone-no-net">
+                                    <property name="visible">True</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label" translatable="yes">No networking devices</property>
+                                  </widget>
+                                </child>
+                                <child>
+                                  <widget class="GtkVBox" id="clone-network-box">
+                                    <property name="visible">True</property>
+                                    <property name="spacing">6</property>
+                                    <child>
+                                      <placeholder/>
+                                    </child>
+                                  </widget>
+                                  <packing>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">1</property>
+                                <property name="bottom_attach">2</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="label7">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="yalign">0</property>
+                                <property name="label" translatable="yes">&lt;span color='#484848'&gt;Networking:&lt;/span&gt;</property>
+                                <property name="use_markup">True</property>
+                              </widget>
+                              <packing>
+                                <property name="top_attach">1</property>
+                                <property name="bottom_attach">2</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkVBox" id="vbox77">
+                                <property name="visible">True</property>
+                                <child>
+                                  <widget class="GtkLabel" id="clone-no-storage-pass">
+                                    <property name="visible">True</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label" translatable="yes">No storage to clone</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkScrolledWindow" id="clone-storage-scroll">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+                                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                                    <child>
+                                      <widget class="GtkViewport" id="viewport1">
+                                        <property name="visible">True</property>
+                                        <property name="resize_mode">GTK_RESIZE_QUEUE</property>
+                                        <property name="shadow_type">GTK_SHADOW_NONE</property>
+                                        <child>
+                                          <widget class="GtkVBox" id="vbox7">
+                                            <property name="visible">True</property>
+                                            <child>
+                                              <widget class="GtkHBox" id="hbox6">
+                                                <property name="visible">True</property>
+                                                <child>
+                                                  <widget class="GtkAlignment" id="alignment10">
+                                                    <property name="visible">True</property>
+                                                    <property name="right_padding">6</property>
+                                                    <child>
+                                                      <widget class="GtkVBox" id="clone-storage-box">
+                                                        <property name="visible">True</property>
+                                                        <property name="spacing">12</property>
+                                                        <child>
+                                                          <placeholder/>
+                                                        </child>
+                                                      </widget>
+                                                    </child>
+                                                  </widget>
+                                                  <packing>
+                                                    <property name="expand">False</property>
+                                                    <property name="fill">False</property>
+                                                  </packing>
+                                                </child>
+                                                <child>
+                                                  <widget class="GtkAlignment" id="alignment3">
+                                                    <property name="visible">True</property>
+                                                    <child>
+                                                      <placeholder/>
+                                                    </child>
+                                                  </widget>
+                                                  <packing>
+                                                    <property name="expand">False</property>
+                                                    <property name="fill">False</property>
+                                                    <property name="position">1</property>
+                                                  </packing>
+                                                </child>
+                                              </widget>
+                                              <packing>
+                                                <property name="expand">False</property>
+                                                <property name="fill">False</property>
+                                              </packing>
+                                            </child>
+                                            <child>
+                                              <widget class="GtkAlignment" id="alignment2">
+                                                <property name="visible">True</property>
+                                                <child>
+                                                  <placeholder/>
+                                                </child>
+                                              </widget>
+                                              <packing>
+                                                <property name="expand">False</property>
+                                                <property name="fill">False</property>
+                                                <property name="position">1</property>
+                                              </packing>
+                                            </child>
+                                          </widget>
+                                        </child>
+                                      </widget>
+                                    </child>
+                                  </widget>
+                                  <packing>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">2</property>
+                                <property name="bottom_attach">3</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="label6">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="yalign">0</property>
+                                <property name="label" translatable="yes">&lt;span color='#484848'&gt;Storage:&lt;/span&gt;</property>
+                                <property name="use_markup">True</property>
+                              </widget>
+                              <packing>
+                                <property name="top_attach">2</property>
+                                <property name="bottom_attach">3</property>
+                                <property name="x_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkLabel" id="label3">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">&lt;span color='#484848'&gt;Name:&lt;/span&gt;</property>
+                                <property name="use_markup">True</property>
+                              </widget>
+                              <packing>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkEntry" id="clone-new-name">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="invisible_char">●</property>
+                              </widget>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="y_options"></property>
+                                <property name="y_padding">3</property>
+                              </packing>
+                            </child>
+                          </widget>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</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>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkHBox" id="hbox5">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkButton" id="clone-help">
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="no_show_all">True</property>
+                    <property name="label" translatable="yes">gtk-help</property>
+                    <property name="use_stock">True</property>
+                    <property name="response_id">0</property>
+                    <signal name="clicked" handler="on_clone_help_clicked"/>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkButton" id="clone-cancel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="label" translatable="yes">gtk-cancel</property>
+                    <property name="use_stock">True</property>
+                    <property name="response_id">0</property>
+                    <signal name="clicked" handler="on_clone_cancel_clicked"/>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="pack_type">GTK_PACK_END</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkButton" id="clone-ok">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="response_id">0</property>
+                    <signal name="clicked" handler="on_clone_ok_clicked"/>
+                    <child>
+                      <widget class="GtkHBox" id="hbox3">
+                        <property name="visible">True</property>
+                        <property name="spacing">2</property>
+                        <child>
+                          <widget class="GtkImage" id="image1">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-new</property>
+                          </widget>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="label5">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">C_lone Virtual Machine</property>
+                            <property name="use_underline">True</property>
+                            <property name="mnemonic_widget">clone-ok</property>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="pack_type">GTK_PACK_END</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff -r c38447a9cca3 -r a50a82407160 src/vmm-details.glade
--- a/src/vmm-details.glade	Tue Jul 14 22:04:34 2009 -0400
+++ b/src/vmm-details.glade	Mon Jul 20 13:09:01 2009 -0400
@@ -130,6 +130,14 @@
                       </widget>
                     </child>
                     <child>
+                      <widget class="GtkMenuItem" id="details-menu-clone">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">_Clone...</property>
+                        <property name="use_underline">True</property>
+                        <signal name="activate" handler="on_details_menu_clone_activate"/>
+                      </widget>
+                    </child>
+                    <child>
                       <widget class="GtkMenuItem" id="details-menu-migrate">
                         <property name="visible">True</property>
                         <property name="label" translatable="yes">_Migrate</property>

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]