~ubuntu-branches/ubuntu/raring/virt-manager/raring-proposed

« back to all changes in this revision

Viewing changes to src/virtManager/clone.py

  • Committer: Bazaar Package Importer
  • Author(s): Guido Günther, Laurent Léonard, Guido Günther
  • Date: 2009-10-07 14:04:03 UTC
  • mfrom: (2.3.1 experimental)
  • mto: (2.3.2 experimental)
  • mto: This revision was merged to the branch mainline in revision 21.
  • Revision ID: james.westby@ubuntu.com-20091007140403-kl9yum4ocrar1acq
Tags: 0.8.0-2
[ Laurent Léonard ]
* [61651ae] Drop ${shlibs:Depends} from dependencies since there is no more
  C code.
* [3319434] Drop redo-patches target from debian/rules. Since gbp-pq
  is used now.
* [b04541e] Clean debian/rules. Drop commented post-patches target
  since there is no more autogen.sh.
* [145ba60] Update french translation.
* [799f18e] Update 0001-use-usr-share-gconf-for-schema-data.patch.
* [052f7bb] Remove XS-Python-Version field from debian/control. Since
  it is deprecated with pysupport.
* [bfb1611][1a66b27][a8ce142][4116d07][31ff60a] Fix some misspellings in
  french translation.
* [6e23aff] Clean build dependencies.
* [b71c8c9] Bump Debhelper version to 7.
* [c614f09] Bump Standards-Version to 3.8.3.
* [99a52c2] Clean debian/rules.
* [52f3b63] Add clean target in debian/rules. To clean automatically
  generated files: - debian/pycompat (see #424898)
* [d5e2ef7] Remove tests/Makefile in the debian/rules clean target. To get a
  clean Debian diff at rebuild time, the file is automatically generated.
* [7da6d9b] Don't close connection on all libvirt errors. Pulled from
  upstream 1c886d1863f7.

[ Guido Günther ]
* upload to unstable
* [464fb65] make package arch all since there is no more C code.
* [25f7663] switch to python-support and use python-distutils
* [c4da06a] bump python-libvirt dependency so we get all of VMs new
  features.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (C) 2009 Red Hat, Inc.
 
3
# Copyright (C) 2009 Cole Robinson <crobinso@redhat.com>
 
4
#
 
5
# This program is free software; you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License as published by
 
7
# the Free Software Foundation; either version 2 of the License, or
 
8
# (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
18
# MA 02110-1301 USA.
 
19
#
 
20
 
 
21
import traceback
 
22
import logging
 
23
import os
 
24
 
 
25
import gobject
 
26
import gtk.glade
 
27
 
 
28
from virtManager.error import vmmErrorDialog
 
29
from virtManager.asyncjob import vmmAsyncJob
 
30
from virtManager.createmeter import vmmCreateMeter
 
31
from virtManager.storagebrowse import vmmStorageBrowser
 
32
from virtManager import util
 
33
 
 
34
import virtinst
 
35
from virtinst import CloneManager
 
36
from virtinst.CloneManager import CloneDesign
 
37
from virtinst import VirtualNetworkInterface
 
38
 
 
39
STORAGE_COMBO_CLONE = 0
 
40
STORAGE_COMBO_SHARE = 1
 
41
STORAGE_COMBO_SEP = 2
 
42
STORAGE_COMBO_DETAILS = 3
 
43
 
 
44
STORAGE_INFO_ORIG_PATH = 0
 
45
STORAGE_INFO_NEW_PATH = 1
 
46
STORAGE_INFO_TARGET = 2
 
47
STORAGE_INFO_SIZE = 3
 
48
STORAGE_INFO_DEVTYPE = 4
 
49
STORAGE_INFO_DO_CLONE = 5
 
50
STORAGE_INFO_CAN_CLONE = 6
 
51
STORAGE_INFO_CAN_SHARE = 7
 
52
STORAGE_INFO_DO_DEFAULT = 8
 
53
STORAGE_INFO_DEFINFO = 9
 
54
STORAGE_INFO_FAILINFO = 10
 
55
STORAGE_INFO_COMBO = 11
 
56
 
 
57
NETWORK_INFO_LABEL = 0
 
58
NETWORK_INFO_ORIG_MAC = 1
 
59
NETWORK_INFO_NEW_MAC = 2
 
60
 
 
61
# XXX: Some method to check all storage size
 
62
# XXX: What to do for cleanup if clone fails?
 
63
# XXX: Disable mouse scroll for combo boxes
 
64
 
 
65
class vmmCloneVM(gobject.GObject):
 
66
    __gsignals__ = {
 
67
        "action-show-help": (gobject.SIGNAL_RUN_FIRST,
 
68
                             gobject.TYPE_NONE, [str]),
 
69
    }
 
70
 
 
71
    def __init__(self, config, orig_vm):
 
72
        self.__gobject_init__()
 
73
        self.config = config
 
74
        self.orig_vm = orig_vm
 
75
 
 
76
        self.window = gtk.glade.XML(self.config.get_glade_dir() + \
 
77
                                    "/vmm-clone.glade",
 
78
                                    "vmm-clone", domain="virt-manager")
 
79
        self.topwin = self.window.get_widget("vmm-clone")
 
80
 
 
81
        self.change_mac_window = gtk.glade.XML(self.config.get_glade_dir() + \
 
82
                                               "/vmm-clone.glade",
 
83
                                               "vmm-change-mac",
 
84
                                               domain="virt-manager")
 
85
        self.change_mac = self.change_mac_window.get_widget("vmm-change-mac")
 
86
        self.change_mac_window.signal_autoconnect({
 
87
            "on_vmm_change_mac_delete_event": self.change_mac_close,
 
88
            "on_change_mac_cancel_clicked" : self.change_mac_close,
 
89
            "on_change_mac_ok_clicked" : self.change_mac_finish,
 
90
        })
 
91
 
 
92
        self.change_storage_window = gtk.glade.XML(self.config.get_glade_dir()\
 
93
                                                   + "/vmm-clone.glade",
 
94
                                                   "vmm-change-storage",
 
95
                                                   domain="virt-manager")
 
96
        self.change_storage = self.change_storage_window.get_widget("vmm-change-storage")
 
97
        self.change_storage_window.signal_autoconnect({
 
98
            "on_vmm_change_storage_delete_event": self.change_storage_close,
 
99
            "on_change_storage_cancel_clicked" : self.change_storage_close,
 
100
            "on_change_storage_ok_clicked" : self.change_storage_finish,
 
101
            "on_change_storage_doclone_toggled" : self.change_storage_doclone_toggled,
 
102
 
 
103
            "on_change_storage_browse_clicked" : self.change_storage_browse,
 
104
        })
 
105
 
 
106
        self.err = vmmErrorDialog(self.topwin,
 
107
                                  0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
 
108
                                  _("Unexpected Error"),
 
109
                                  _("An unexpected error occurred"))
 
110
        self.topwin.hide()
 
111
 
 
112
        self.conn = self.orig_vm.connection
 
113
        self.clone_design = None
 
114
 
 
115
        self.storage_list = {}
 
116
        self.target_list = []
 
117
 
 
118
        self.net_list = {}
 
119
        self.mac_list = []
 
120
 
 
121
        self.storagemenu = None
 
122
        self.netmenu = None
 
123
        self.storage_browser = None
 
124
 
 
125
        self.window.signal_autoconnect({
 
126
            "on_clone_delete_event" : self.close,
 
127
            "on_clone_cancel_clicked" : self.close,
 
128
            "on_clone_ok_clicked" : self.finish,
 
129
            "on_clone_help_clicked" : self.show_help,
 
130
        })
 
131
 
 
132
        self.set_initial_state()
 
133
 
 
134
    def show(self):
 
135
        self.reset_state()
 
136
        self.topwin.show()
 
137
        self.topwin.present()
 
138
 
 
139
    def close(self, ignore1=None, ignore2=None):
 
140
        self.topwin.hide()
 
141
        self.change_mac_close()
 
142
        self.change_storage_close()
 
143
        return 1
 
144
 
 
145
    def change_mac_close(self, ignore1=None, ignore2=None):
 
146
        self.change_mac.hide()
 
147
        return 1
 
148
 
 
149
    def change_storage_close(self, ignore1=None, ignore2=None):
 
150
        self.change_storage.hide()
 
151
        return 1
 
152
 
 
153
 
 
154
    # First time setup
 
155
 
 
156
    def set_initial_state(self):
 
157
 
 
158
        blue = gtk.gdk.color_parse("#0072A8")
 
159
        self.window.get_widget("clone-header").modify_bg(gtk.STATE_NORMAL,
 
160
                                                         blue)
 
161
 
 
162
        box = self.window.get_widget("clone-vm-icon-box")
 
163
        image = gtk.image_new_from_icon_name("vm_clone_wizard",
 
164
                                             gtk.ICON_SIZE_DIALOG)
 
165
        image.show()
 
166
        box.pack_end(image, False)
 
167
 
 
168
    # Populate state
 
169
    def reset_state(self):
 
170
        # Populate default clone values
 
171
        self.setup_clone_info()
 
172
 
 
173
        cd = self.clone_design
 
174
        self.window.get_widget("clone-orig-name").set_text(cd.original_guest)
 
175
        self.window.get_widget("clone-new-name").set_text(cd.clone_name)
 
176
 
 
177
        # We need to determine which disks fail (and why).
 
178
        self.storage_list, self.target_list = self.check_all_storage()
 
179
 
 
180
        self.populate_storage_lists()
 
181
        self.populate_network_list()
 
182
 
 
183
        return
 
184
 
 
185
    def setup_clone_info(self):
 
186
        self.clone_design = self.build_new_clone_design()
 
187
 
 
188
    def build_new_clone_design(self, new_name=None):
 
189
        cd = CloneDesign(self.conn.vmm)
 
190
        cd.original_guest = self.orig_vm.get_name()
 
191
        if not new_name:
 
192
            new_name = virtinst.CloneManager.generate_clone_name(cd)
 
193
        cd.clone_name = new_name
 
194
 
 
195
        # Erase any clone_policy from the original design, so that we
 
196
        # get the entire device list.
 
197
        cd.clone_policy = []
 
198
 
 
199
        return cd
 
200
 
 
201
    def populate_network_list(self):
 
202
        net_box = self.window.get_widget("clone-network-box")
 
203
        for c in net_box.get_children():
 
204
            net_box.remove(c)
 
205
 
 
206
        self.net_list = {}
 
207
        self.mac_list = []
 
208
 
 
209
        def build_net_row(labelstr, origmac, newmac):
 
210
 
 
211
            label = gtk.Label(labelstr + " (%s)" % origmac)
 
212
            label.set_alignment(0, .5)
 
213
            button = gtk.Button(_("Details..."))
 
214
            button.connect("clicked", self.net_change_mac, origmac)
 
215
 
 
216
            hbox = gtk.HBox()
 
217
            hbox.set_spacing(12)
 
218
            hbox.pack_start(label)
 
219
            hbox.pack_end(button, False, False)
 
220
            hbox.show_all()
 
221
            net_box.pack_start(hbox, False, False)
 
222
 
 
223
            net_row = []
 
224
            net_row.insert(NETWORK_INFO_LABEL, labelstr)
 
225
            net_row.insert(NETWORK_INFO_ORIG_MAC, origmac)
 
226
            net_row.insert(NETWORK_INFO_NEW_MAC, newmac)
 
227
            self.net_list[origmac] = net_row
 
228
            self.mac_list.append(origmac)
 
229
 
 
230
        for net in self.orig_vm.get_network_devices():
 
231
            mac = net[2]
 
232
            net_dev = net[3]
 
233
            net_type = net[5]
 
234
 
 
235
            # Generate a new MAC
 
236
            obj = VirtualNetworkInterface(conn=self.conn.vmm,
 
237
                                          type=VirtualNetworkInterface.TYPE_USER)
 
238
            obj.setup(self.conn.vmm)
 
239
            newmac = obj.macaddr
 
240
 
 
241
 
 
242
            # [ interface type, device name, origmac, newmac, label ]
 
243
            if net_type == VirtualNetworkInterface.TYPE_USER:
 
244
                label = _("Usermode")
 
245
 
 
246
            elif net_type == VirtualNetworkInterface.TYPE_VIRTUAL:
 
247
                net = self.orig_vm.get_connection().get_net_by_name(net_dev)
 
248
 
 
249
                if net:
 
250
                    label = ""
 
251
 
 
252
                    use_nat, host_dev = net.get_ipv4_forward()
 
253
                    if not use_nat:
 
254
                        desc = _("Isolated network")
 
255
                    elif host_dev:
 
256
                        desc = _("NAT to %s") % host_dev
 
257
                    else:
 
258
                        desc = _("NAT")
 
259
                    label += "%s" % desc
 
260
 
 
261
                else:
 
262
                    label = (_("Virtual Network") +
 
263
                             (net_dev and " %s" % net_dev or ""))
 
264
 
 
265
            else:
 
266
                # 'bridge' or anything else
 
267
                label = (net_type.capitalize() +
 
268
                         net_dev and " %s" % net_dev or "")
 
269
 
 
270
            build_net_row(label, mac, newmac)
 
271
 
 
272
        no_net = bool(len(self.net_list.keys()) == 0)
 
273
        self.window.get_widget("clone-network-box").set_property("visible",
 
274
                                                                 not no_net)
 
275
        self.window.get_widget("clone-no-net").set_property("visible", no_net)
 
276
 
 
277
    def check_all_storage(self):
 
278
        """
 
279
        Determine which storage is cloneable, and which isn't
 
280
        """
 
281
        diskinfos = self.orig_vm.get_disk_devices()
 
282
        cd = self.clone_design
 
283
 
 
284
        storage_list = {}
 
285
 
 
286
        # We need to determine which disks fail (and why).
 
287
        all_targets = map(lambda d: d[1], diskinfos)
 
288
 
 
289
        for disk in diskinfos:
 
290
            force_target = disk[1]
 
291
            path = disk[3]
 
292
            ro = disk[6]
 
293
            shared = disk[7]
 
294
            devtype = disk[4]
 
295
 
 
296
            size = None
 
297
            clone_path = None
 
298
            failinfo = ""
 
299
            definfo = ""
 
300
 
 
301
            storage_row = []
 
302
            storage_row.insert(STORAGE_INFO_ORIG_PATH, path)
 
303
            storage_row.insert(STORAGE_INFO_NEW_PATH, clone_path)
 
304
            storage_row.insert(STORAGE_INFO_TARGET, force_target)
 
305
            storage_row.insert(STORAGE_INFO_SIZE, size)
 
306
            storage_row.insert(STORAGE_INFO_DEVTYPE, devtype)
 
307
            storage_row.insert(STORAGE_INFO_DO_CLONE, False)
 
308
            storage_row.insert(STORAGE_INFO_CAN_CLONE, False)
 
309
            storage_row.insert(STORAGE_INFO_CAN_SHARE, False)
 
310
            storage_row.insert(STORAGE_INFO_DO_DEFAULT, False)
 
311
            storage_row.insert(STORAGE_INFO_DEFINFO, definfo)
 
312
            storage_row.insert(STORAGE_INFO_FAILINFO, failinfo)
 
313
            storage_row.insert(STORAGE_INFO_COMBO, None)
 
314
 
 
315
            skip_targets = all_targets[:]
 
316
            skip_targets.remove(force_target)
 
317
 
 
318
            vol = self.conn.get_vol_by_path(path)
 
319
            default, definfo = do_we_default(self.conn, vol, path, ro, shared,
 
320
                                             devtype)
 
321
 
 
322
            def storage_add(failinfo=None):
 
323
                storage_row[STORAGE_INFO_DEFINFO] = definfo
 
324
                storage_row[STORAGE_INFO_DO_DEFAULT] = default
 
325
                storage_row[STORAGE_INFO_CAN_SHARE] = bool(definfo)
 
326
                if failinfo:
 
327
                    storage_row[STORAGE_INFO_FAILINFO] = failinfo
 
328
                    storage_row[STORAGE_INFO_DO_CLONE] = False
 
329
 
 
330
                storage_list[force_target] = storage_row
 
331
 
 
332
            # If origdisk is empty, deliberately make it fail
 
333
            if not path:
 
334
                storage_add(_("Nothing to clone."))
 
335
                continue
 
336
 
 
337
            try:
 
338
                cd.skip_target = skip_targets
 
339
                cd.setup_original()
 
340
            except Exception, e:
 
341
                logging.exception("Disk target '%s' caused clone error" %
 
342
                                  force_target)
 
343
                storage_add(str(e))
 
344
                continue
 
345
 
 
346
            can_clone, cloneinfo = can_we_clone(self.conn, vol, path)
 
347
            if not can_clone:
 
348
                storage_add(cloneinfo)
 
349
                continue
 
350
 
 
351
            try:
 
352
                # Generate disk path, make sure that works
 
353
                clone_path = None
 
354
                clone_path = CloneManager.generate_clone_disk_path(path, cd)
 
355
 
 
356
                logging.debug("Original path: %s\nGenerated clone path: %s" %
 
357
                              (path, clone_path))
 
358
 
 
359
                cd.clone_devices = clone_path
 
360
                size = cd.original_virtual_disks[0].size
 
361
            except Exception, e:
 
362
                logging.exception("Error setting generated path '%s'" %
 
363
                                  clone_path)
 
364
                storage_add(str(e))
 
365
 
 
366
            storage_row[STORAGE_INFO_CAN_CLONE] = True
 
367
            storage_row[STORAGE_INFO_NEW_PATH] = clone_path
 
368
            storage_row[STORAGE_INFO_SIZE] = self.pretty_storage(size)
 
369
            storage_add()
 
370
 
 
371
        return storage_list, all_targets
 
372
 
 
373
    def build_storage_entry(self, disk, storage_box):
 
374
        origpath = disk[STORAGE_INFO_ORIG_PATH]
 
375
        devtype = disk[STORAGE_INFO_DEVTYPE]
 
376
        size = disk[STORAGE_INFO_SIZE]
 
377
        can_clone = disk[STORAGE_INFO_CAN_CLONE]
 
378
        can_share = disk[STORAGE_INFO_CAN_SHARE]
 
379
        is_default = disk[STORAGE_INFO_DO_DEFAULT]
 
380
        definfo = disk[STORAGE_INFO_DEFINFO]
 
381
        failinfo = disk[STORAGE_INFO_FAILINFO]
 
382
        target = disk[STORAGE_INFO_TARGET]
 
383
 
 
384
        orig_name = self.orig_vm.get_name()
 
385
 
 
386
        disk_label = os.path.basename(origpath)
 
387
        info_label = None
 
388
        if not can_clone:
 
389
            info_label = gtk.Label()
 
390
            info_label.set_alignment(0, .5)
 
391
            info_label.set_markup("<span size='small'>%s</span>" % failinfo)
 
392
        if not is_default:
 
393
            disk_label += (definfo and " (%s)" % definfo or "")
 
394
 
 
395
        # Build icon
 
396
        icon = gtk.Image()
 
397
        if devtype == virtinst.VirtualDisk.DEVICE_FLOPPY:
 
398
            iconname = "media-floppy"
 
399
        elif devtype == virtinst.VirtualDisk.DEVICE_CDROM:
 
400
            iconname = "media-optical"
 
401
        else:
 
402
            iconname = "drive-harddisk"
 
403
        icon.set_from_icon_name(iconname, gtk.ICON_SIZE_MENU)
 
404
        disk_name_label = gtk.Label(disk_label)
 
405
        disk_name_label.set_alignment(0, .5)
 
406
        disk_name_box = gtk.HBox(spacing=9)
 
407
        disk_name_box.pack_start(icon, False)
 
408
        disk_name_box.pack_start(disk_name_label, True)
 
409
 
 
410
        def sep_func(model, it, combo):
 
411
            return model[it][2]
 
412
 
 
413
        # [String, sensitive, is sep]
 
414
        model = gtk.ListStore(str, bool, bool)
 
415
        option_combo = gtk.ComboBox(model)
 
416
        text = gtk.CellRendererText()
 
417
        option_combo.pack_start(text)
 
418
        option_combo.add_attribute(text, "text", 0)
 
419
        option_combo.add_attribute(text, "sensitive", 1)
 
420
        option_combo.set_row_separator_func(sep_func, option_combo)
 
421
        option_combo.connect("changed", self.storage_combo_changed, target)
 
422
 
 
423
        vbox = gtk.VBox(spacing=1)
 
424
        if can_clone or can_share:
 
425
            model.insert(STORAGE_COMBO_CLONE,
 
426
                         [(_("Clone this disk") +
 
427
                           (size and " (%s)" % size or "")),
 
428
                          can_clone, False])
 
429
            model.insert(STORAGE_COMBO_SHARE,
 
430
                         [_("Share disk with %s") % orig_name, can_share,
 
431
                          False])
 
432
            model.insert(STORAGE_COMBO_SEP, ["", False, True])
 
433
            model.insert(STORAGE_COMBO_DETAILS,
 
434
                         [_("Details..."), True, False])
 
435
 
 
436
            if can_clone and is_default:
 
437
                option_combo.set_active(STORAGE_COMBO_CLONE)
 
438
            else:
 
439
                option_combo.set_active(STORAGE_COMBO_SHARE)
 
440
        else:
 
441
            model.insert(STORAGE_COMBO_CLONE,
 
442
                         [_("Storage cannot be shared or cloned."),
 
443
                         False, False])
 
444
            option_combo.set_active(STORAGE_COMBO_CLONE)
 
445
 
 
446
        vbox.pack_start(disk_name_box, False, False)
 
447
        vbox.pack_start(option_combo, False, False)
 
448
        if info_label:
 
449
            vbox.pack_start(info_label, False, False)
 
450
        storage_box.pack_start(vbox, False, False)
 
451
 
 
452
        disk[STORAGE_INFO_COMBO] = option_combo
 
453
 
 
454
    def populate_storage_lists(self):
 
455
        storage_box = self.window.get_widget("clone-storage-box")
 
456
        for c in storage_box.get_children():
 
457
            storage_box.remove(c)
 
458
 
 
459
        for target in self.target_list:
 
460
            disk = self.storage_list[target]
 
461
            self.build_storage_entry(disk, storage_box)
 
462
 
 
463
        num_c = min(len(self.target_list), 3)
 
464
        if num_c:
 
465
            scroll = self.window.get_widget("clone-storage-scroll")
 
466
            scroll.set_size_request(-1, 80*num_c)
 
467
        storage_box.show_all()
 
468
 
 
469
        no_storage = not bool(len(self.target_list))
 
470
        self.window.get_widget("clone-storage-box").set_property("visible",not no_storage)
 
471
        self.window.get_widget("clone-no-storage-pass").set_property("visible", no_storage)
 
472
 
 
473
        skip_targets = []
 
474
        new_disks = []
 
475
        for target in self.target_list:
 
476
            do_clone = self.storage_list[target][STORAGE_INFO_DO_CLONE]
 
477
            new_path = self.storage_list[target][STORAGE_INFO_NEW_PATH]
 
478
 
 
479
            if do_clone:
 
480
                new_disks.append(new_path)
 
481
            else:
 
482
                skip_targets.append(target)
 
483
 
 
484
        self.clone_design.skip_target = skip_targets
 
485
        self.clone_design.clone_devices = new_disks
 
486
 
 
487
        # If any storage cannot be cloned or shared, don't allow cloning
 
488
        clone = True
 
489
        tooltip = ""
 
490
        for row in self.storage_list.values():
 
491
            can_clone = row[STORAGE_INFO_CAN_CLONE]
 
492
            can_share = row[STORAGE_INFO_CAN_SHARE]
 
493
            if not (can_clone or can_share):
 
494
                clone = False
 
495
                tooltip = _("One or more disks cannot be cloned or shared.")
 
496
                break
 
497
 
 
498
        ok_button = self.window.get_widget("clone-ok")
 
499
        ok_button.set_sensitive(clone)
 
500
        util.tooltip_wrapper(ok_button, tooltip)
 
501
 
 
502
    def net_show_popup(self, widget, event):
 
503
        if event.button != 3:
 
504
            return
 
505
 
 
506
        self.netmenu.popup(None, None, None, 0, event.time)
 
507
 
 
508
    def net_change_mac(self, ignore, origmac):
 
509
        row = self.net_list[origmac]
 
510
        orig_mac = row[NETWORK_INFO_ORIG_MAC]
 
511
        new_mac =  row[NETWORK_INFO_NEW_MAC]
 
512
        typ = row[NETWORK_INFO_LABEL]
 
513
 
 
514
        self.change_mac_window.get_widget("change-mac-orig").set_text(orig_mac)
 
515
        self.change_mac_window.get_widget("change-mac-type").set_text(typ)
 
516
        self.change_mac_window.get_widget("change-mac-new").set_text(new_mac)
 
517
 
 
518
        self.change_mac.show_all()
 
519
 
 
520
    def storage_show_popup(self, widget, event):
 
521
        if event.button != 3:
 
522
            return
 
523
 
 
524
        self.storagemenu.popup(None, None, None, 0, event.time)
 
525
 
 
526
    def storage_combo_changed(self, src, target):
 
527
        idx = src.get_active()
 
528
        row = self.storage_list[target]
 
529
 
 
530
        if idx == STORAGE_COMBO_CLONE:
 
531
            row[STORAGE_INFO_DO_CLONE] = True
 
532
            return
 
533
        elif idx == STORAGE_COMBO_SHARE:
 
534
            row[STORAGE_INFO_DO_CLONE] = False
 
535
            return
 
536
        elif idx != STORAGE_COMBO_DETAILS:
 
537
            return
 
538
 
 
539
        do_clone = row[STORAGE_INFO_DO_CLONE]
 
540
        if do_clone:
 
541
            src.set_active(STORAGE_COMBO_CLONE)
 
542
        else:
 
543
            src.set_active(STORAGE_COMBO_SHARE)
 
544
 
 
545
        # Show storage
 
546
        row = self.storage_change_path(row)
 
547
 
 
548
    def change_storage_doclone_toggled(self, src):
 
549
        do_clone = src.get_active()
 
550
 
 
551
        cs = self.change_storage_window
 
552
        cs.get_widget("change-storage-new").set_sensitive(do_clone)
 
553
        cs.get_widget("change-storage-browse").set_sensitive(do_clone)
 
554
 
 
555
    def storage_change_path(self, row):
 
556
        orig = row[STORAGE_INFO_ORIG_PATH]
 
557
        new  = row[STORAGE_INFO_NEW_PATH]
 
558
        tgt  = row[STORAGE_INFO_TARGET]
 
559
        size = row[STORAGE_INFO_SIZE]
 
560
        can_clone = row[STORAGE_INFO_CAN_CLONE]
 
561
        can_share = row[STORAGE_INFO_CAN_SHARE]
 
562
        do_clone = row[STORAGE_INFO_DO_CLONE]
 
563
 
 
564
        cs = self.change_storage_window
 
565
        cs.get_widget("change-storage-doclone").set_active(True)
 
566
        cs.get_widget("change-storage-doclone").toggled()
 
567
        cs.get_widget("change-storage-orig").set_text(orig)
 
568
        cs.get_widget("change-storage-target").set_text(tgt)
 
569
        cs.get_widget("change-storage-size").set_text(size or "-")
 
570
        cs.get_widget("change-storage-doclone").set_active(do_clone)
 
571
 
 
572
        if can_clone:
 
573
            cs.get_widget("change-storage-new").set_text(new or "")
 
574
        else:
 
575
            cs.get_widget("change-storage-new").set_text("")
 
576
        cs.get_widget("change-storage-doclone").set_sensitive(can_clone and
 
577
                                                              can_share)
 
578
 
 
579
        cs.get_widget("vmm-change-storage").show_all()
 
580
 
 
581
    def set_orig_vm(self, new_orig):
 
582
        self.orig_vm = new_orig
 
583
        self.conn = self.orig_vm.connection
 
584
 
 
585
    def change_mac_finish(self, ignore):
 
586
        orig = self.change_mac_window.get_widget("change-mac-orig").get_text()
 
587
        new = self.change_mac_window.get_widget("change-mac-new").get_text()
 
588
        row = self.net_list[orig]
 
589
 
 
590
        try:
 
591
            VirtualNetworkInterface(conn=self.conn.vmm,
 
592
                                    type=VirtualNetworkInterface.TYPE_USER,
 
593
                                    macaddr=new)
 
594
            row[NETWORK_INFO_NEW_MAC] = new
 
595
        except Exception, e:
 
596
            self.err.show_err(_("Error changing MAC address: %s") % str(e),
 
597
                                "".join(traceback.format_exc()))
 
598
            return
 
599
 
 
600
        self.change_mac_close()
 
601
 
 
602
    def change_storage_finish(self, ignore):
 
603
        cs = self.change_storage_window
 
604
        target = cs.get_widget("change-storage-target").get_text()
 
605
        row = self.storage_list[target]
 
606
 
 
607
        # Sync 'do clone' checkbox, and main dialog combo
 
608
        combo = row[STORAGE_INFO_COMBO]
 
609
        if cs.get_widget("change-storage-doclone").get_active():
 
610
            combo.set_active(STORAGE_COMBO_CLONE)
 
611
        else:
 
612
            combo.set_active(STORAGE_COMBO_SHARE)
 
613
 
 
614
        do_clone = row[STORAGE_INFO_DO_CLONE]
 
615
        if not do_clone:
 
616
            self.change_storage_close()
 
617
            return
 
618
 
 
619
        new_path = cs.get_widget("change-storage-new").get_text()
 
620
 
 
621
        if virtinst.VirtualDisk.path_exists(self.clone_design.original_conn,
 
622
                                            new_path):
 
623
            res = self.err.yes_no(_("Cloning will overwrite the existing "
 
624
                                    "file"),
 
625
                                    _("Using an existing image will overwrite "
 
626
                                      "the path during the clone process. Are "
 
627
                                      "you sure you want to use this path?"))
 
628
            if not res:
 
629
                return
 
630
 
 
631
        try:
 
632
            self.clone_design.clone_devices = new_path
 
633
            self.populate_storage_lists()
 
634
            row[STORAGE_INFO_NEW_PATH] = new_path
 
635
        except Exception, e:
 
636
            self.err.show_err(_("Error changing storage path: %s") % str(e),
 
637
                                "".join(traceback.format_exc()))
 
638
            return
 
639
 
 
640
        self.change_storage_close()
 
641
 
 
642
    def pretty_storage(self, size):
 
643
        if not size:
 
644
            return ""
 
645
        return "%.1f GB" % float(size)
 
646
 
 
647
    # Listeners
 
648
    def validate(self):
 
649
        name = self.window.get_widget("clone-new-name").get_text()
 
650
 
 
651
        # Make another clone_design
 
652
        cd = self.build_new_clone_design(name)
 
653
 
 
654
        # Set MAC addresses
 
655
        for mac in self.mac_list:
 
656
            row = self.net_list[mac]
 
657
            new_mac = row[NETWORK_INFO_NEW_MAC]
 
658
            cd.clone_mac = new_mac
 
659
 
 
660
        skip_targets = []
 
661
        new_paths = []
 
662
        warn_str = ""
 
663
        for target in self.target_list:
 
664
            path = self.storage_list[target][STORAGE_INFO_ORIG_PATH]
 
665
            new_path = self.storage_list[target][STORAGE_INFO_NEW_PATH]
 
666
            do_clone = self.storage_list[target][STORAGE_INFO_DO_CLONE]
 
667
            do_default = self.storage_list[target][STORAGE_INFO_DO_DEFAULT]
 
668
 
 
669
            if do_clone:
 
670
                new_paths.append(new_path)
 
671
            else:
 
672
                skip_targets.append(target)
 
673
                if not path or path == '-':
 
674
                    continue
 
675
 
 
676
                if not do_default:
 
677
                    continue
 
678
 
 
679
                warn_str += "%s: %s\n" % (target, path)
 
680
 
 
681
        cd.skip_target = skip_targets
 
682
        cd.setup_original()
 
683
        cd.clone_devices = new_paths
 
684
 
 
685
        if warn_str:
 
686
            res = self.err.ok_cancel(
 
687
                _("Skipping disks may cause data to be overwritten."),
 
688
                _("The following disk devices will not be cloned:\n\n%s\n"
 
689
                  "Running the new guest could overwrite data in these "
 
690
                  "disk images.")
 
691
                  % warn_str)
 
692
 
 
693
            if not res:
 
694
                return False
 
695
 
 
696
        cd.setup_clone()
 
697
 
 
698
        self.clone_design = cd
 
699
        return True
 
700
 
 
701
    def finish(self, src):
 
702
 
 
703
        # validate input
 
704
        try:
 
705
            if not self.validate():
 
706
                return
 
707
        except Exception, e:
 
708
            self.err.show_err(_("Uncaught error validating input: %s") % str(e),
 
709
                                "".join(traceback.format_exc()))
 
710
            return
 
711
 
 
712
        self.topwin.set_sensitive(False)
 
713
        self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
 
714
 
 
715
        title = (_("Creating virtual machine clone '%s'") %
 
716
                 self.clone_design.clone_name)
 
717
        text = title
 
718
        if self.clone_design.clone_devices:
 
719
            text = title + _(" and selected storage (this may take a while)")
 
720
 
 
721
        progWin = vmmAsyncJob(self.config, self._async_clone, [],
 
722
                              title=title, text=text)
 
723
        progWin.run()
 
724
        error, details = progWin.get_error()
 
725
 
 
726
        self.topwin.set_sensitive(True)
 
727
        self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
 
728
 
 
729
        if error is not None:
 
730
            self.err.show_err(error, details)
 
731
        else:
 
732
            self.close()
 
733
            self.conn.tick(noStatsUpdate=True)
 
734
 
 
735
    def _async_clone(self, asyncjob):
 
736
        newconn = None
 
737
        error = None
 
738
        details = None
 
739
 
 
740
        try:
 
741
            # Open a seperate connection to install on since this is async
 
742
            logging.debug("Threading off connection to clone VM.")
 
743
            newconn = util.dup_conn(self.config, self.conn)
 
744
            meter = vmmCreateMeter(asyncjob)
 
745
 
 
746
            self.clone_design.orig_connection = newconn
 
747
            for d in self.clone_design.clone_virtual_disks:
 
748
                d.conn = newconn
 
749
 
 
750
            self.clone_design.setup()
 
751
            CloneManager.start_duplicate(self.clone_design, meter)
 
752
        except Exception, e:
 
753
            error = (_("Error creating virtual machine clone '%s': %s") %
 
754
                      (self.clone_design.clone_name, str(e)))
 
755
            details = "".join(traceback.format_exc())
 
756
 
 
757
        if error:
 
758
            asyncjob.set_error(error, details)
 
759
        return
 
760
 
 
761
    def change_storage_browse(self, ignore):
 
762
 
 
763
        cs = self.change_storage_window
 
764
        def callback(self, txt):
 
765
            cs.get_widget("change-storage-new").set_text(txt)
 
766
 
 
767
        if self.storage_browser == None:
 
768
            self.storage_browser = vmmStorageBrowser(self.config, self.conn)
 
769
            self.storage_browser.connect("storage-browse-finish", callback)
 
770
 
 
771
        self.storage_browser.show(self.conn)
 
772
 
 
773
    def show_help(self, ignore1=None):
 
774
        # Nothing yet
 
775
        return
 
776
 
 
777
gobject.type_register(vmmCloneVM)
 
778
 
 
779
def can_we_clone(conn, vol, path):
 
780
    """Is the passed path even clone-able"""
 
781
    ret = True
 
782
    msg = None
 
783
 
 
784
    if not path or path == "-":
 
785
        msg = _("No storage to clone.")
 
786
 
 
787
    elif vol:
 
788
        # Managed storage
 
789
        if not virtinst.Storage.is_create_vol_from_supported(conn):
 
790
            if conn.is_remote() or not os.access(path, os.R_OK):
 
791
                msg = _("Connection does not support managed storage cloning.")
 
792
    else:
 
793
        if conn.is_remote():
 
794
            msg = _("Cannot clone unmanaged remote storage.")
 
795
        elif not os.access(path, os.R_OK):
 
796
            msg = _("No write access to parent directory.")
 
797
        elif not os.path.exists(path):
 
798
            msg = _("Path does not exist.")
 
799
 
 
800
    if msg:
 
801
        ret = False
 
802
 
 
803
    return (ret, msg)
 
804
 
 
805
def do_we_default(conn, vol, path, ro, shared, devtype):
 
806
    """ Returns (do we clone by default?, info string if not)"""
 
807
    info = ""
 
808
 
 
809
    def append_str(str1, str2, delim=", "):
 
810
        if not str2:
 
811
            return str1
 
812
        if str1:
 
813
            str1 += delim
 
814
        str1 += str2
 
815
        return str1
 
816
 
 
817
    if (devtype == virtinst.VirtualDisk.DEVICE_CDROM or
 
818
        devtype == virtinst.VirtualDisk.DEVICE_FLOPPY):
 
819
        info = append_str(info, _("Removable"))
 
820
 
 
821
    if ro:
 
822
        info = append_str(info, _("Read Only"))
 
823
    elif not vol and not os.access(path, os.W_OK):
 
824
        info = append_str(info, _("No write access"))
 
825
 
 
826
    if shared:
 
827
        info = append_str(info, _("Shareable"))
 
828
 
 
829
    return (not info, info)