1# Copyright (C) 2008, 2013, 2014, 2015 Red Hat, Inc.
2# Copyright (C) 2008 Cole Robinson <crobinso@redhat.com>
3#
4# This work is licensed under the GNU GPLv2 or later.
5# See the COPYING file in the top-level directory.
6
7import io
8import pkgutil
9import os
10import threading
11import time
12
13from gi.repository import Gtk
14from gi.repository import Pango
15
16import virtinst
17import virtinst.generatename
18from virtinst import log
19
20from .lib import uiutil
21from .asyncjob import vmmAsyncJob
22from .baseclass import vmmGObjectUI
23from .connmanager import vmmConnectionManager
24from .device.addstorage import vmmAddStorage
25from .device.mediacombo import vmmMediaCombo
26from .device.netlist import vmmNetworkList
27from .engine import vmmEngine
28from .object.domain import vmmDomainVirtinst
29from .oslist import vmmOSList
30from .storagebrowse import vmmStorageBrowser
31from .vmwindow import vmmVMWindow
32
33# Number of seconds to wait for media detection
34DETECT_TIMEOUT = 20
35
36DEFAULT_MEM = 1024
37
38(PAGE_NAME,
39 PAGE_INSTALL,
40 PAGE_MEM,
41 PAGE_STORAGE,
42 PAGE_FINISH) = range(5)
43
44(INSTALL_PAGE_ISO,
45 INSTALL_PAGE_URL,
46 INSTALL_PAGE_MANUAL,
47 INSTALL_PAGE_IMPORT,
48 INSTALL_PAGE_CONTAINER_APP,
49 INSTALL_PAGE_CONTAINER_OS,
50 INSTALL_PAGE_VZ_TEMPLATE) = range(7)
51
52# Column numbers for os type/version list models
53(OS_COL_ID,
54 OS_COL_LABEL,
55 OS_COL_IS_SEP,
56 OS_COL_IS_SHOW_ALL) = range(4)
57
58
59#####################
60# Pretty UI helpers #
61#####################
62
63def _pretty_arch(_a):
64    if _a == "armv7l":
65        return "arm"
66    return _a
67
68
69def _pretty_storage(size):
70    return _("%.1f GiB") % float(size)
71
72
73def _pretty_memory(mem):
74    return _("%d MiB") % (mem / 1024.0)
75
76
77###########################################################
78# Helpers for tracking devices we create from this wizard #
79###########################################################
80
81def is_virt_bootstrap_installed():
82    return pkgutil.find_loader('virtBootstrap') is not None
83
84
85class _GuestData:
86    """
87    Wrapper to hold all data that will go into the Guest object,
88    so we can rebuild it as needed.
89    """
90    def __init__(self, conn, capsinfo):
91        self.conn = conn
92        self.capsinfo = capsinfo
93        self.failed_guest = None
94
95        self.default_graphics_type = None
96        self.skip_default_sound = None
97        self.x86_cpu_default = None
98
99        self.disk = None
100        self.filesystem = None
101        self.interface = None
102        self.init = None
103
104        self.machine = None
105        self.os_variant = None
106        self.uefi_path = None
107        self.name = None
108
109        self.vcpus = None
110        self.memory = None
111        self.currentMemory = None
112
113        self.location = None
114        self.cdrom = None
115        self.extra_args = None
116        self.livecd = False
117
118    def build_installer(self):
119        kwargs = {}
120        if self.location:
121            kwargs["location"] = self.location
122        if self.cdrom:
123            kwargs["cdrom"] = self.cdrom
124
125        installer = virtinst.Installer(self.conn, **kwargs)
126        if self.extra_args:
127            installer.set_extra_args([self.extra_args])
128        if self.livecd:
129            installer.livecd = True
130        return installer
131
132    def build_guest(self):
133        guest = virtinst.Guest(self.conn)
134        guest.set_capabilities_defaults(self.capsinfo)
135
136        if self.machine:
137            # If no machine was explicitly selected, we don't overwrite
138            # it, because we want to
139            guest.os.machine = self.machine
140        if self.os_variant:
141            guest.set_os_name(self.os_variant)
142        if self.uefi_path:
143            guest.set_uefi_path(self.uefi_path)
144
145        if self.filesystem:
146            guest.add_device(self.filesystem)
147        if self.disk:
148            guest.add_device(self.disk)
149        if self.interface:
150            guest.add_device(self.interface)
151
152        if self.init:
153            guest.os.init = self.init
154        if self.name:
155            guest.name = self.name
156        if self.vcpus:
157            guest.vcpus = self.vcpus
158        if self.currentMemory:
159            guest.currentMemory = self.currentMemory
160        if self.memory:
161            guest.memory = self.memory
162
163        return guest
164
165
166##############
167# Main class #
168##############
169
170class vmmCreateVM(vmmGObjectUI):
171    @classmethod
172    def show_instance(cls, parentobj, uri=None):
173        try:
174            if not cls._instance:
175                cls._instance = vmmCreateVM()
176            cls._instance.show(parentobj and parentobj.topwin or None, uri=uri)
177        except Exception as e:  # pragma: no cover
178            if not parentobj:
179                raise
180            parentobj.err.show_err(
181                    _("Error launching create dialog: %s") % str(e))
182
183    def __init__(self):
184        vmmGObjectUI.__init__(self, "createvm.ui", "vmm-create")
185        self._cleanup_on_app_close()
186
187        self.conn = None
188        self._capsinfo = None
189
190        self._gdata = None
191
192        # Distro detection state variables
193        self._detect_os_in_progress = False
194        self._os_already_detected_for_media = False
195
196        self._customize_window = None
197
198        self._storage_browser = None
199        self._netlist = None
200
201        self._addstorage = vmmAddStorage(self.conn, self.builder, self.topwin)
202        self.widget("storage-align").add(self._addstorage.top_box)
203        def _browse_file_cb(ignore, widget):
204            self._browse_file(widget)
205        self._addstorage.connect("browse-clicked", _browse_file_cb)
206
207        self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin)
208        self._mediacombo.connect("changed", self._iso_changed_cb)
209        self._mediacombo.connect("activate", self._iso_activated_cb)
210        self._mediacombo.set_mnemonic_label(
211                self.widget("install-iso-label"))
212        self.widget("install-iso-align").add(self._mediacombo.top_box)
213
214        self.builder.connect_signals({
215            "on_vmm_newcreate_delete_event": self._close_requested,
216
217            "on_create_cancel_clicked": self._close_requested,
218            "on_create_back_clicked": self._back_clicked,
219            "on_create_forward_clicked": self._forward_clicked,
220            "on_create_finish_clicked": self._finish_clicked,
221            "on_create_pages_switch_page": self._page_changed,
222
223            "on_create_conn_changed": self._conn_changed,
224            "on_method_changed": self._method_changed,
225            "on_xen_type_changed": self._xen_type_changed,
226            "on_arch_changed": self._arch_changed,
227            "on_virt_type_changed": self._virt_type_changed,
228            "on_machine_changed": self._machine_changed,
229            "on_vz_virt_type_changed": self._vz_virt_type_changed,
230
231            "on_install_iso_browse_clicked": self._browse_iso,
232            "on_install_url_entry_changed": self._url_changed,
233            "on_install_url_entry_activate": self._url_activated,
234            "on_install_import_browse_clicked": self._browse_import,
235            "on_install_app_browse_clicked": self._browse_app,
236            "on_install_oscontainer_browse_clicked": self._browse_oscontainer,
237            "on_install_container_source_toggle": self._container_source_toggle,
238
239            "on_install_detect_os_toggled": self._detect_os_toggled_cb,
240
241            "on_enable_storage_toggled": self._toggle_enable_storage,
242
243            "on_create_vm_name_changed": self._name_changed,
244        })
245        self.bind_escape_key_close()
246
247        self._init_state()
248
249
250
251    ###########################
252    # Standard window methods #
253    ###########################
254
255    def show(self, parent, uri):
256        log.debug("Showing new vm wizard")
257
258        if not self.is_visible():
259            self._reset_state(uri)
260            self.topwin.set_transient_for(parent)
261            vmmEngine.get_instance().increment_window_counter()
262
263        self.topwin.present()
264
265    def _close(self, ignore1=None, ignore2=None):
266        if self.is_visible():
267            log.debug("Closing new vm wizard")
268            vmmEngine.get_instance().decrement_window_counter()
269
270        self.topwin.hide()
271
272        self._cleanup_customize_window()
273        if self._storage_browser:
274            self._storage_browser.close()
275        self._set_conn(None)
276        self._gdata = None
277
278    def _cleanup(self):
279        if self._storage_browser:
280            self._storage_browser.cleanup()
281            self._storage_browser = None
282        if self._netlist:  # pragma: no cover
283            self._netlist.cleanup()
284            self._netlist = None
285        if self._mediacombo:
286            self._mediacombo.cleanup()
287            self._mediacombo = None
288        if self._addstorage:
289            self._addstorage.cleanup()
290            self._addstorage = None
291
292        self.conn = None
293        self._capsinfo = None
294        self._gdata = None
295
296
297    ##########################
298    # Initial state handling #
299    ##########################
300
301    def _show_startup_error(self, error, hideinstall=True):
302        self.widget("startup-error-box").show()
303        self.widget("create-forward").set_sensitive(False)
304        if hideinstall:
305            self.widget("install-box").hide()
306            self.widget("arch-expander").hide()
307
308        self.widget("startup-error").set_text(_("Error: %s") % error)
309        return False
310
311    def _show_startup_warning(self, error):
312        self.widget("startup-error-box").show()
313        self.widget("startup-error").set_markup(
314            _("<span size='small'>Warning: %s</span>") % error)
315
316    def _show_arch_warning(self, error):
317        self.widget("arch-warning-box").show()
318        self.widget("arch-warning").set_markup(
319            _("<span size='small'>Warning: %s</span>") % error)
320
321
322    def _init_state(self):
323        self.widget("create-pages").set_show_tabs(False)
324        self.widget("install-method-pages").set_show_tabs(False)
325
326        # Connection list
327        self.widget("create-conn-label").set_text("")
328        self.widget("startup-error").set_text("")
329        conn_list = self.widget("create-conn")
330        conn_model = Gtk.ListStore(str, str)
331        conn_list.set_model(conn_model)
332        text = uiutil.init_combo_text_column(conn_list, 1)
333        text.set_property("ellipsize", Pango.EllipsizeMode.MIDDLE)
334
335        def set_model_list(widget_id):
336            lst = self.widget(widget_id)
337            model = Gtk.ListStore(str)
338            lst.set_model(model)
339            lst.set_entry_text_column(0)
340
341        # Lists for the install urls
342        set_model_list("install-url-combo")
343
344        # Lists for OS container bootstrap
345        set_model_list("install-oscontainer-source-url-combo")
346
347        # Architecture
348        archList = self.widget("arch")
349        # [label, guest.os.arch value]
350        archModel = Gtk.ListStore(str, str)
351        archList.set_model(archModel)
352        uiutil.init_combo_text_column(archList, 0)
353        archList.set_row_separator_func(
354            lambda m, i, ignore: m[i][0] is None, None)
355
356        # guest.os.type value for xen (hvm vs. xen)
357        hyperList = self.widget("xen-type")
358        # [label, guest.os_type value]
359        hyperModel = Gtk.ListStore(str, str)
360        hyperList.set_model(hyperModel)
361        uiutil.init_combo_text_column(hyperList, 0)
362
363        # guest.os.machine value
364        lst = self.widget("machine")
365        # [machine ID]
366        model = Gtk.ListStore(str)
367        lst.set_model(model)
368        uiutil.init_combo_text_column(lst, 0)
369        lst.set_row_separator_func(lambda m, i, ignore: m[i][0] is None, None)
370
371        # guest.type value for xen (qemu vs kvm)
372        lst = self.widget("virt-type")
373        # [label, guest.type value]
374        model = Gtk.ListStore(str, str)
375        lst.set_model(model)
376        uiutil.init_combo_text_column(lst, 0)
377
378        # OS distro list
379        self._os_list = vmmOSList()
380        self.widget("install-os-align").add(self._os_list.search_entry)
381        self.widget("os-label").set_mnemonic_widget(self._os_list.search_entry)
382
383    def _reset_state(self, urihint=None):
384        """
385        Reset all UI state to default values. Conn specific state is
386        populated in _populate_conn_state
387        """
388        self.reset_finish_cursor()
389
390        self.widget("create-pages").set_current_page(PAGE_NAME)
391        self._page_changed(None, None, PAGE_NAME)
392
393        # Name page state
394        self.widget("create-vm-name").set_text("")
395        self.widget("method-local").set_active(True)
396        self.widget("create-conn").set_active(-1)
397        activeconn = self._populate_conn_list(urihint)
398        self.widget("arch-expander").set_expanded(False)
399        self.widget("vz-virt-type-hvm").set_active(True)
400
401        if self._set_conn(activeconn) is False:
402            return False
403
404
405        # Everything from this point forward should be connection independent
406
407        # Distro/Variant
408        self._os_list.reset_state()
409        self._os_already_detected_for_media = False
410
411        def _populate_media_model(media_model, urls):
412            media_model.clear()
413            for url in (urls or []):
414                media_model.append([url])
415
416        # Install local
417        self._mediacombo.reset_state()
418
419        # Install URL
420        self.widget("install-urlopts-entry").set_text("")
421        self.widget("install-url-entry").set_text("")
422        self.widget("install-url-options").set_expanded(False)
423        urlmodel = self.widget("install-url-combo").get_model()
424        _populate_media_model(urlmodel, self.config.get_media_urls())
425
426        # Install import
427        self.widget("install-import-entry").set_text("")
428
429        # Install container app
430        self.widget("install-app-entry").set_text("/bin/sh")
431
432        # Install container OS
433        self.widget("install-oscontainer-fs").set_text("")
434        self.widget("install-oscontainer-source-url-entry").set_text("")
435        self.widget("install-oscontainer-source-user").set_text("")
436        self.widget("install-oscontainer-source-passwd").set_text("")
437        self.widget("install-oscontainer-source-insecure").set_active(False)
438        self.widget("install-oscontainer-bootstrap").set_active(False)
439        self.widget("install-oscontainer-auth-options").set_expanded(False)
440        self.widget("install-oscontainer-rootpw").set_text("")
441        src_model = (self.widget("install-oscontainer-source-url-combo")
442                         .get_model())
443        _populate_media_model(src_model, self.config.get_container_urls())
444
445        # Install VZ container from template
446        self.widget("install-container-template").set_text("centos-7-x86_64")
447
448        # Storage
449        self.widget("enable-storage").set_active(True)
450        self._addstorage.reset_state()
451        self._addstorage.widget("storage-create").set_active(True)
452        self._addstorage.widget("storage-entry").set_text("")
453
454        # Final page
455        self.widget("summary-customize").set_active(False)
456
457
458    def _set_caps_state(self):
459        """
460        Set state that is dependent on when capsinfo changes
461        """
462        self.widget("arch-warning-box").hide()
463        self._gdata = self._build_guestdata()
464        guest = self._gdata.build_guest()
465
466        # Helper state
467        is_local = not self.conn.is_remote()
468        is_storage_capable = self.conn.support.conn_storage()
469        can_storage = (is_local or is_storage_capable)
470        is_pv = guest.os.is_xenpv()
471        is_container_only = self.conn.is_container_only()
472        is_vz = self.conn.is_vz()
473        is_vz_container = is_vz and guest.os.is_container()
474        can_remote_url = self.conn.get_backend().support_remote_url_install()
475
476        installable_arch = bool(guest.os.is_x86() or
477                guest.os.is_ppc64() or
478                guest.os.is_s390x())
479
480        if guest.prefers_uefi():
481            try:
482                self._gdata.uefi_path = guest.get_uefi_path()
483                # Call for validation
484                guest.set_uefi_path(self._gdata.uefi_path)
485                installable_arch = True
486                log.debug("UEFI found, setting it as default.")
487            except Exception as e:
488                installable_arch = False
489                log.debug("Error checking for UEFI default", exc_info=True)
490                msg = _("Failed to setup UEFI: %s\n"
491                        "Install options are limited.") % e
492                self._show_arch_warning(msg)
493
494        # Install Options
495        method_tree = self.widget("method-tree")
496        method_manual = self.widget("method-manual")
497        method_local = self.widget("method-local")
498        method_import = self.widget("method-import")
499        method_container_app = self.widget("method-container-app")
500
501        method_tree.set_sensitive((is_local or can_remote_url) and
502                                  installable_arch)
503        method_local.set_sensitive(not is_pv and can_storage and
504                                   installable_arch)
505        method_manual.set_sensitive(not is_container_only)
506        method_import.set_sensitive(can_storage)
507        virt_methods = [method_local, method_tree,
508                method_manual, method_import]
509
510        local_tt = None
511        tree_tt = None
512        import_tt = None
513
514        if not is_local:
515            if not can_remote_url:
516                tree_tt = _("Libvirt version does not "
517                            "support remote URL installs.")
518            if not is_storage_capable:  # pragma: no cover
519                local_tt = _("Connection does not support storage management.")
520                import_tt = local_tt
521
522        if is_pv:
523            local_tt = _("CDROM/ISO installs not available for paravirt guests.")
524
525        if not installable_arch:
526            msg = (_("Architecture '%s' is not installable") %
527                   guest.os.arch)
528            tree_tt = msg
529            local_tt = msg
530
531        if not any([w.get_active() and w.get_sensitive()
532                    for w in virt_methods]):
533            for w in virt_methods:
534                if w.get_sensitive():
535                    w.set_active(True)
536                    break
537
538        if not (is_container_only or
539                [w for w in virt_methods if w.get_sensitive()]):
540            return self._show_startup_error(  # pragma: no cover
541                    _("No install methods available for this connection."),
542                    hideinstall=False)
543
544        method_tree.set_tooltip_text(tree_tt or "")
545        method_local.set_tooltip_text(local_tt or "")
546        method_import.set_tooltip_text(import_tt or "")
547
548        # Container install options
549        method_container_app.set_active(True)
550        self.widget("container-install-box").set_visible(is_container_only)
551        self.widget("vz-install-box").set_visible(is_vz)
552        self.widget("virt-install-box").set_visible(
553            not is_container_only and not is_vz_container)
554
555        self.widget("kernel-info-box").set_visible(not installable_arch)
556
557    def _populate_conn_state(self):
558        """
559        Update all state that has some dependency on the current connection
560        """
561        self.conn.schedule_priority_tick(pollnet=True,
562                                         pollpool=True,
563                                         pollnodedev=True)
564
565        self.widget("install-box").show()
566        self.widget("create-forward").set_sensitive(True)
567
568        self._capsinfo = None
569        self.conn.invalidate_caps()
570
571        if not self.conn.caps.has_install_options():
572            error = _("No hypervisor options were found for this "
573                      "connection.")
574
575            if self.conn.is_qemu():
576                error += "\n\n"
577                error += _("This usually means that QEMU or KVM is not "
578                           "installed on your machine, or the KVM kernel "
579                           "modules are not loaded.")
580            return self._show_startup_error(error)
581
582        self._change_caps()
583
584        # A bit out of order, but populate the xen/virt/arch/machine lists
585        # so we can work with a default.
586        self._populate_xen_type()
587        self._populate_arch()
588        self._populate_virt_type()
589
590        show_arch = (self.widget("xen-type").get_visible() or
591                     self.widget("virt-type").get_visible() or
592                     self.widget("arch").get_visible() or
593                     self.widget("machine").get_visible())
594        uiutil.set_grid_row_visible(self.widget("arch-expander"), show_arch)
595
596        if self.conn.is_qemu():
597            if not self._capsinfo.guest.is_kvm_available():
598                error = _("KVM is not available. This may mean the KVM "
599                 "package is not installed, or the KVM kernel modules "
600                 "are not loaded. Your virtual machines may perform poorly.")
601                self._show_startup_warning(error)
602
603        elif self.conn.is_vz():
604            has_hvm_guests = False
605            has_exe_guests = False
606            for g in self.conn.caps.guests:
607                if g.os_type == "hvm":
608                    has_hvm_guests = True
609                if g.os_type == "exe":
610                    has_exe_guests = True
611
612            self.widget("vz-virt-type-hvm").set_sensitive(has_hvm_guests)
613            self.widget("vz-virt-type-exe").set_sensitive(has_exe_guests)
614            self.widget("vz-virt-type-hvm").set_active(has_hvm_guests)
615            self.widget("vz-virt-type-exe").set_active(
616                not has_hvm_guests and has_exe_guests)
617
618        # ISO media
619        # Dependent on connection so we need to do this here
620        self._mediacombo.set_conn(self.conn)
621        self._mediacombo.reset_state()
622
623        # Allow container bootstrap only for local connection and
624        # only if virt-bootstrap is installed. Otherwise, show message.
625        is_local = not self.conn.is_remote()
626        vb_installed = is_virt_bootstrap_installed()
627        vb_enabled = is_local and vb_installed
628
629        oscontainer_widget_conf = {
630            "install-oscontainer-notsupport-conn": not is_local,
631            "install-oscontainer-notsupport": not vb_installed,
632            "install-oscontainer-bootstrap": vb_enabled,
633            "install-oscontainer-source": vb_enabled,
634            "install-oscontainer-rootpw-box": vb_enabled
635            }
636        for w in oscontainer_widget_conf:
637            self.widget(w).set_visible(oscontainer_widget_conf[w])
638
639        # Memory
640        memory = int(self.conn.host_memory_size())
641        mem_label = (_("Up to %(maxmem)s available on the host") %
642                     {'maxmem': _pretty_memory(memory)})
643        mem_label = ("<span size='small'>%s</span>" % mem_label)
644        self.widget("mem").set_range(50, memory // 1024)
645        self.widget("phys-mem-label").set_markup(mem_label)
646
647        # CPU
648        phys_cpus = int(self.conn.host_active_processor_count())
649        cpu_label = (ngettext("Up to %(numcpus)d available",
650                              "Up to %(numcpus)d available",
651                              phys_cpus) %
652                     {'numcpus': int(phys_cpus)})
653        cpu_label = ("<span size='small'>%s</span>" % cpu_label)
654        self.widget("cpus").set_range(1, max(phys_cpus, 1))
655        self.widget("phys-cpu-label").set_markup(cpu_label)
656
657        # Storage
658        self._addstorage.conn = self.conn
659        self._addstorage.reset_state()
660
661        # Networking
662        self.widget("advanced-expander").set_expanded(False)
663
664        self._netlist = vmmNetworkList(self.conn, self.builder, self.topwin)
665        self.widget("netdev-ui-align").add(self._netlist.top_box)
666        self._netlist.reset_state()
667
668    def _conn_state_changed(self, conn):
669        if conn.is_disconnected():
670            self._close()
671
672    def _set_conn(self, newconn):
673        self.widget("startup-error-box").hide()
674        self.widget("arch-warning-box").hide()
675
676        oldconn = self.conn
677        self.conn = newconn
678        if oldconn:
679            oldconn.disconnect_by_obj(self)
680        if self._netlist:
681            self.widget("netdev-ui-align").remove(self._netlist.top_box)
682            self._netlist.cleanup()
683            self._netlist = None
684
685        if not self.conn:
686            return self._show_startup_error(
687                                _("No active connection to install on."))
688        self.conn.connect("state-changed", self._conn_state_changed)
689
690        try:
691            return self._populate_conn_state()
692        except Exception as e:  # pragma: no cover
693            log.exception("Error setting create wizard conn state.")
694            return self._show_startup_error(str(e))
695
696
697    def _change_caps(self, gtype=None, arch=None, domtype=None):
698        """
699        Change the cached capsinfo for the passed values, and trigger
700        all needed UI refreshing
701        """
702        if gtype is None:
703            # If none specified, prefer HVM so install options aren't limited
704            # with a default PV choice.
705            for g in self.conn.caps.guests:
706                if g.os_type == "hvm":
707                    gtype = "hvm"
708                    break
709
710        capsinfo = self.conn.caps.guest_lookup(os_type=gtype,
711                                               arch=arch,
712                                               typ=domtype)
713
714        if self._capsinfo:
715            if (self._capsinfo.guest == capsinfo.guest and
716                self._capsinfo.domain == capsinfo.domain):
717                return
718
719        self._capsinfo = capsinfo
720        log.debug("Guest type set to os_type=%s, arch=%s, dom_type=%s",
721                      self._capsinfo.os_type,
722                      self._capsinfo.arch,
723                      self._capsinfo.hypervisor_type)
724        self._populate_machine()
725        self._set_caps_state()
726
727
728    ##################################################
729    # Helpers for populating hv/arch/machine/conn UI #
730    ##################################################
731
732    def _populate_xen_type(self):
733        model = self.widget("xen-type").get_model()
734        model.clear()
735
736        default = 0
737        guests = []
738        if self.conn.is_xen() or self.conn.is_test():
739            guests = self.conn.caps.guests[:]
740
741        for guest in guests:
742            if not guest.domains:
743                continue  # pragma: no cover
744
745            gtype = guest.os_type
746            dom = guest.domains[0]
747            domtype = dom.hypervisor_type
748            label = self.conn.pretty_hv(gtype, domtype)
749
750            # Don't add multiple rows for each arch
751            for m in model:
752                if m[0] == label:
753                    label = None
754                    break
755            if label is None:
756                continue
757
758            # Determine if this is the default given by guest_lookup
759            if (gtype == self._capsinfo.os_type and
760                domtype == self._capsinfo.hypervisor_type):
761                default = len(model)
762
763            model.append([label, gtype])
764
765        show = bool(len(model))
766        uiutil.set_grid_row_visible(self.widget("xen-type"), show)
767        if show:
768            self.widget("xen-type").set_active(default)
769
770    def _populate_arch(self):
771        model = self.widget("arch").get_model()
772        model.clear()
773
774        default = 0
775        archs = []
776        for guest in self.conn.caps.guests:
777            if guest.os_type == self._capsinfo.os_type:
778                archs.append(guest.arch)
779
780        # Combine x86/i686 to avoid confusion
781        if (self.conn.caps.host.cpu.arch == "x86_64" and
782            "x86_64" in archs and "i686" in archs):
783            archs.remove("i686")
784        archs.sort()
785
786        prios = ["x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le",
787            "s390x"]
788        if self.conn.caps.host.cpu.arch not in prios:
789            prios = []  # pragma: no cover
790        for p in prios[:]:
791            if p not in archs:
792                prios.remove(p)
793            else:
794                archs.remove(p)
795        if prios:
796            if archs:
797                prios += [None]
798            archs = prios + archs
799
800        default = 0
801        if self._capsinfo.arch in archs:
802            default = archs.index(self._capsinfo.arch)
803
804        for arch in archs:
805            model.append([_pretty_arch(arch), arch])
806
807        show = not (len(archs) < 2)
808        uiutil.set_grid_row_visible(self.widget("arch"), show)
809        self.widget("arch").set_active(default)
810
811    def _populate_virt_type(self):
812        model = self.widget("virt-type").get_model()
813        model.clear()
814
815        # Allow choosing between qemu and kvm for archs that traditionally
816        # have a decent amount of TCG usage, like armv7l. Also include
817        # aarch64 which can be used for arm32 VMs as well
818        domains = [d.hypervisor_type for d in self._capsinfo.guest.domains[:]]
819        if not self.conn.is_qemu():
820            domains = []
821        elif self._capsinfo.arch in ["i686", "x86_64", "ppc64", "ppc64le"]:
822            domains = []
823
824        default = 0
825        if self._capsinfo.hypervisor_type in domains:
826            default = domains.index(self._capsinfo.hypervisor_type)
827
828        prios = ["kvm"]
829        for domain in prios:
830            if domain not in domains:
831                continue
832            domains.remove(domain)
833            domains.insert(0, domain)
834
835        for domain in domains:
836            label = self.conn.pretty_hv(self._capsinfo.os_type, domain)
837            model.append([label, domain])
838
839        show = bool(len(model) > 1)
840        uiutil.set_grid_row_visible(self.widget("virt-type"), show)
841        self.widget("virt-type").set_active(default)
842
843    def _populate_machine(self):
844        model = self.widget("machine").get_model()
845
846        machines = self._capsinfo.machines[:]
847        if self._capsinfo.arch in ["i686", "x86_64"]:
848            machines = []
849        machines.sort()
850
851        defmachine = None
852        prios = []
853        recommended_machine = virtinst.Guest.get_recommended_machine(
854                self._capsinfo)
855        if recommended_machine:
856            defmachine = recommended_machine
857            prios = [defmachine]
858
859        for p in prios[:]:
860            if p not in machines:
861                prios.remove(p)  # pragma: no cover
862            else:
863                machines.remove(p)
864        if prios:
865            machines = prios + [None] + machines
866
867        default = 0
868        if defmachine and defmachine in machines:
869            default = machines.index(defmachine)
870
871        self.widget("machine").disconnect_by_func(self._machine_changed)
872        try:
873            model.clear()
874            for m in machines:
875                model.append([m])
876
877            show = (len(machines) > 1)
878            uiutil.set_grid_row_visible(self.widget("machine"), show)
879            if show:
880                self.widget("machine").set_active(default)
881        finally:
882            self.widget("machine").connect("changed", self._machine_changed)
883
884    def _populate_conn_list(self, urihint=None):
885        conn_list = self.widget("create-conn")
886        model = conn_list.get_model()
887        model.clear()
888
889        default = -1
890        connmanager = vmmConnectionManager.get_instance()
891        for connobj in connmanager.conns.values():
892            if not connobj.is_active():
893                continue
894
895            if connobj.get_uri() == urihint:
896                default = len(model)
897            elif default < 0 and not connobj.is_remote():
898                # Favor local connections over remote connections
899                default = len(model)
900
901            model.append([connobj.get_uri(), connobj.get_pretty_desc()])
902
903        no_conns = (len(model) == 0)
904
905        if default < 0 and not no_conns:
906            default = 0  # pragma: no cover
907
908        activeuri = ""
909        activedesc = ""
910        activeconn = None
911        if not no_conns:
912            conn_list.set_active(default)
913            activeuri, activedesc = model[default]
914            activeconn = connmanager.conns[activeuri]
915
916        self.widget("create-conn-label").set_text(activedesc)
917        if len(model) <= 1:
918            self.widget("create-conn").hide()
919            self.widget("create-conn-label").show()
920        else:
921            self.widget("create-conn").show()
922            self.widget("create-conn-label").hide()
923
924        return activeconn
925
926
927    ###############################
928    # Misc UI populating routines #
929    ###############################
930
931    def _populate_summary_storage(self, path=None):
932        storagetmpl = "<span size='small'>%s</span>"
933        storagesize = ""
934        storagepath = ""
935
936        disk = self._gdata.disk
937        fs = self._gdata.filesystem
938        if disk:
939            if disk.wants_storage_creation():
940                storagesize = "%s" % _pretty_storage(disk.get_size())
941            if not path:
942                path = disk.get_source_path()
943            storagepath = (storagetmpl % path)
944        elif fs:
945            storagepath = storagetmpl % fs.source
946        else:
947            storagepath = _("None")
948
949        self.widget("summary-storage").set_markup(storagesize)
950        self.widget("summary-storage").set_visible(bool(storagesize))
951        self.widget("summary-storage-path").set_markup(storagepath)
952
953    def _populate_summary(self):
954        guest = self._gdata.build_guest()
955        mem = _pretty_memory(int(guest.memory))
956        cpu = str(int(guest.vcpus))
957
958        instmethod = self._get_config_install_page()
959        install = ""
960        if instmethod == INSTALL_PAGE_ISO:
961            install = _("Local CDROM/ISO")
962        elif instmethod == INSTALL_PAGE_URL:
963            install = _("URL Install Tree")
964        elif instmethod == INSTALL_PAGE_IMPORT:
965            install = _("Import existing OS image")
966        elif instmethod == INSTALL_PAGE_MANUAL:
967            install = _("Manual install")
968        elif instmethod == INSTALL_PAGE_CONTAINER_APP:
969            install = _("Application container")
970        elif instmethod == INSTALL_PAGE_CONTAINER_OS:
971            install = _("Operating system container")
972        elif instmethod == INSTALL_PAGE_VZ_TEMPLATE:
973            install = _("Virtuozzo container")
974
975        self.widget("summary-os").set_text(guest.osinfo.label)
976        self.widget("summary-install").set_text(install)
977        self.widget("summary-mem").set_text(mem)
978        self.widget("summary-cpu").set_text(cpu)
979        self._populate_summary_storage()
980
981        ignore0, nsource, ignore1 = self._netlist.get_network_selection()
982        expand = not nsource
983        if expand:
984            self.widget("advanced-expander").set_expanded(True)
985
986
987    ################################
988    # UI state getters and helpers #
989    ################################
990
991    def _get_config_name(self):
992        return self.widget("create-vm-name").get_text()
993
994    def _get_config_machine(self):
995        return uiutil.get_list_selection(self.widget("machine"),
996            check_visible=True)
997
998    def _get_config_install_page(self):
999        if self.widget("vz-install-box").get_visible():
1000            if self.widget("vz-virt-type-exe").get_active():
1001                return INSTALL_PAGE_VZ_TEMPLATE
1002        if self.widget("virt-install-box").get_visible():
1003            if self.widget("method-local").get_active():
1004                return INSTALL_PAGE_ISO
1005            elif self.widget("method-tree").get_active():
1006                return INSTALL_PAGE_URL
1007            elif self.widget("method-import").get_active():
1008                return INSTALL_PAGE_IMPORT
1009            elif self.widget("method-manual").get_active():
1010                return INSTALL_PAGE_MANUAL
1011        else:
1012            if self.widget("method-container-app").get_active():
1013                return INSTALL_PAGE_CONTAINER_APP
1014            if self.widget("method-container-os").get_active():
1015                return INSTALL_PAGE_CONTAINER_OS
1016
1017    def _is_container_install(self):
1018        return self._get_config_install_page() in [INSTALL_PAGE_CONTAINER_APP,
1019                                                   INSTALL_PAGE_CONTAINER_OS,
1020                                                   INSTALL_PAGE_VZ_TEMPLATE]
1021
1022
1023    def _get_config_oscontainer_bootstrap(self):
1024        return self.widget("install-oscontainer-bootstrap").get_active()
1025
1026
1027    def _get_config_oscontainer_source_url(self, store_media=False):
1028        src_url = (self.widget("install-oscontainer-source-url-entry")
1029                       .get_text().strip())
1030
1031        if src_url and store_media:
1032            self.config.add_container_url(src_url)
1033
1034        return src_url
1035
1036
1037    def _get_config_oscontainer_source_username(self):
1038        return (self.widget("install-oscontainer-source-user")
1039                    .get_text().strip())
1040
1041
1042    def _get_config_oscontainer_source_password(self):
1043        return self.widget("install-oscontainer-source-passwd").get_text()
1044
1045
1046    def _get_config_oscontainer_isecure(self):
1047        return self.widget("install-oscontainer-source-insecure").get_active()
1048
1049
1050    def _get_config_oscontainer_root_password(self):
1051        return self.widget("install-oscontainer-rootpw").get_text()
1052
1053
1054    def _should_skip_disk_page(self):
1055        return self._get_config_install_page() in [INSTALL_PAGE_IMPORT,
1056                                                   INSTALL_PAGE_CONTAINER_APP,
1057                                                   INSTALL_PAGE_CONTAINER_OS,
1058                                                   INSTALL_PAGE_VZ_TEMPLATE]
1059
1060    def _get_config_local_media(self, store_media=False):
1061        return self._mediacombo.get_path(store_media=store_media)
1062
1063    def _get_config_detectable_media(self):
1064        instpage = self._get_config_install_page()
1065        cdrom = None
1066        location = None
1067
1068        if instpage == INSTALL_PAGE_ISO:
1069            cdrom = self._get_config_local_media()
1070        elif instpage == INSTALL_PAGE_URL:
1071            location = self.widget("install-url-entry").get_text()
1072
1073        return cdrom, location
1074
1075    def _get_config_url_info(self, store_media=False):
1076        media = self.widget("install-url-entry").get_text().strip()
1077        extra = self.widget("install-urlopts-entry").get_text().strip()
1078
1079        if media and store_media:
1080            self.config.add_media_url(media)
1081
1082        return (media, extra)
1083
1084    def _get_config_import_path(self):
1085        return self.widget("install-import-entry").get_text()
1086
1087    def _is_default_storage(self):
1088        return (self._addstorage.is_default_storage() and
1089                not self._should_skip_disk_page())
1090
1091    def _is_os_detect_active(self):
1092        return self.widget("install-detect-os").get_active()
1093
1094
1095    ################
1096    # UI Listeners #
1097    ################
1098
1099    def _close_requested(self, *ignore1, **ignore2):
1100        """
1101        When user tries to close the dialog, check for any disks that
1102        we should auto cleanup
1103        """
1104        if (not self._gdata or
1105            not self._gdata.failed_guest):
1106            self._close()
1107            return 1
1108
1109        def _cleanup_disks(asyncjob, _failed_guest):
1110            meter = asyncjob.get_meter()
1111            virtinst.Installer.cleanup_created_disks(_failed_guest, meter)
1112
1113        def _cleanup_disks_finished(error, details):
1114            if error:  # pragma: no cover
1115                log.debug("Error cleaning up disk images:"
1116                    "\nerror=%s\ndetails=%s", error, details)
1117            self.idle_add(self._close)
1118
1119        progWin = vmmAsyncJob(
1120            _cleanup_disks, [self._gdata.failed_guest],
1121            _cleanup_disks_finished, [],
1122            _("Removing disk images"),
1123            _("Removing disk images we created for this virtual machine."),
1124            self.topwin)
1125        progWin.run()
1126
1127        return 1
1128
1129
1130    # Intro page listeners
1131    def _conn_changed(self, src):
1132        uri = uiutil.get_list_selection(src)
1133        newconn = None
1134        connmanager = vmmConnectionManager.get_instance()
1135        if uri:
1136            newconn = connmanager.conns[uri]
1137
1138        # If we aren't visible, let reset_state handle this for us, which
1139        # has a better chance of reporting error
1140        if not self.is_visible():
1141            return
1142
1143        if self.conn is not newconn:
1144            self._set_conn(newconn)
1145
1146    def _method_changed(self, src):
1147        ignore = src
1148        # Reset the page number, since the total page numbers depend
1149        # on the chosen install method
1150        self._set_page_num_text(0)
1151
1152    def _machine_changed(self, ignore):
1153        self._set_caps_state()
1154
1155    def _xen_type_changed(self, ignore):
1156        os_type = uiutil.get_list_selection(self.widget("xen-type"), column=1)
1157        if not os_type:
1158            return
1159
1160        self._change_caps(os_type)
1161        self._populate_arch()
1162
1163    def _arch_changed(self, ignore):
1164        arch = uiutil.get_list_selection(self.widget("arch"), column=1)
1165        if not arch:
1166            return
1167
1168        self._change_caps(self._capsinfo.os_type, arch)
1169        self._populate_virt_type()
1170
1171    def _virt_type_changed(self, ignore):
1172        domtype = uiutil.get_list_selection(self.widget("virt-type"), column=1)
1173        if not domtype:
1174            return
1175
1176        self._change_caps(self._capsinfo.os_type, self._capsinfo.arch, domtype)
1177
1178    def _vz_virt_type_changed(self, ignore):
1179        is_hvm = self.widget("vz-virt-type-hvm").get_active()
1180        if is_hvm:
1181            self._change_caps("hvm")
1182        else:
1183            self._change_caps("exe")
1184
1185    # Install page listeners
1186    def _detectable_media_widget_changed(self, widget, checkfocus=True):
1187        self._os_already_detected_for_media = False
1188
1189        # If the text entry widget has focus, don't fire detect_media_os,
1190        # it means the user is probably typing. It will be detected
1191        # when the user activates the widget, or we try to switch pages
1192        if (checkfocus and
1193            hasattr(widget, "get_text") and widget.has_focus()):
1194            return
1195
1196        self._start_detect_os_if_needed()
1197
1198    def _url_changed(self, src):
1199        self._detectable_media_widget_changed(src)
1200    def _url_activated(self, src):
1201        self._detectable_media_widget_changed(src, checkfocus=False)
1202    def _iso_changed_cb(self, mediacombo, entry):
1203        self._detectable_media_widget_changed(entry)
1204    def _iso_activated_cb(self, mediacombo, entry):
1205        self._detectable_media_widget_changed(entry, checkfocus=False)
1206
1207    def _detect_os_toggled_cb(self, src):
1208        if not src.is_visible():
1209            return  # pragma: no cover
1210
1211        # We are only here if the user explicitly changed detection UI
1212        dodetect = src.get_active()
1213        self._change_os_detect(not dodetect)
1214        if dodetect:
1215            self._os_already_detected_for_media = False
1216            self._start_detect_os_if_needed()
1217
1218    def _browse_oscontainer(self, ignore):
1219        self._browse_file("install-oscontainer-fs", is_dir=True)
1220    def _browse_app(self, ignore):
1221        self._browse_file("install-app-entry")
1222    def _browse_import(self, ignore):
1223        self._browse_file("install-import-entry")
1224    def _browse_iso(self, ignore):
1225        def set_path(ignore, path):
1226            self._mediacombo.set_path(path)
1227        self._browse_file(None, cb=set_path, is_media=True)
1228
1229
1230    # Storage page listeners
1231    def _toggle_enable_storage(self, src):
1232        self.widget("storage-align").set_sensitive(src.get_active())
1233
1234
1235    # Summary page listeners
1236    def _name_changed(self, src):
1237        newname = src.get_text()
1238        if not src.is_visible():
1239            return
1240        if not newname:
1241            return
1242
1243        try:
1244            path, ignore = self._get_storage_path(newname, do_log=False)
1245            self._populate_summary_storage(path=path)
1246        except Exception:  # pragma: no cover
1247            log.debug("Error generating storage path on name change "
1248                "for name=%s", newname, exc_info=True)
1249
1250
1251    # Enable/Disable container source URL entry on checkbox click
1252    def _container_source_toggle(self, ignore):
1253        enable_src = self.widget("install-oscontainer-bootstrap").get_active()
1254        self.widget("install-oscontainer-source").set_sensitive(enable_src)
1255        self.widget("install-oscontainer-rootpw-box").set_sensitive(enable_src)
1256
1257        # Auto-generate a path if not specified
1258        if enable_src and not self.widget("install-oscontainer-fs").get_text():
1259            fs_dir = ['/var/lib/libvirt/filesystems/']
1260            if os.geteuid() != 0:
1261                fs_dir = [os.path.expanduser("~"),
1262                          '.local/share/libvirt/filesystems/']
1263
1264            guest = self._gdata.build_guest()
1265            default_name = virtinst.Guest.generate_name(guest)
1266            fs = fs_dir + [default_name]
1267            self.widget("install-oscontainer-fs").set_text(os.path.join(*fs))
1268
1269
1270    ########################
1271    # Misc helper routines #
1272    ########################
1273
1274    def _browse_file(self, cbwidget, cb=None, is_media=False, is_dir=False):
1275        if is_media:
1276            reason = self.config.CONFIG_DIR_ISO_MEDIA
1277        elif is_dir:
1278            reason = self.config.CONFIG_DIR_FS
1279        else:
1280            reason = self.config.CONFIG_DIR_IMAGE
1281
1282        if cb:
1283            callback = cb
1284        else:
1285            def callback(ignore, text):
1286                widget = cbwidget
1287                if isinstance(cbwidget, str):
1288                    widget = self.widget(cbwidget)
1289                widget.set_text(text)
1290
1291        if self._storage_browser and self._storage_browser.conn != self.conn:
1292            self._storage_browser.cleanup()
1293            self._storage_browser = None
1294        if self._storage_browser is None:
1295            self._storage_browser = vmmStorageBrowser(self.conn)
1296
1297        self._storage_browser.set_vm_name(self._get_config_name())
1298        self._storage_browser.set_finish_cb(callback)
1299        self._storage_browser.set_browse_reason(reason)
1300        self._storage_browser.show(self.topwin)
1301
1302
1303    ######################
1304    # Navigation methods #
1305    ######################
1306
1307    def _set_page_num_text(self, cur):
1308        """
1309        Set the 'page 1 of 4' style text in the wizard header
1310        """
1311        cur += 1
1312        final = PAGE_FINISH + 1
1313        if self._should_skip_disk_page():
1314            final -= 1
1315            cur = min(cur, final)
1316
1317        page_lbl = (_("Step %(current_page)d of %(max_page)d") %
1318                    {'current_page': cur, 'max_page': final})
1319
1320        self.widget("header-pagenum").set_markup(page_lbl)
1321
1322    def _change_os_detect(self, sensitive):
1323        self._os_list.set_sensitive(sensitive)
1324        if not sensitive and not self._os_list.get_selected_os():
1325            self._os_list.search_entry.set_text(
1326                    _("Waiting for install media / source"))
1327
1328    def _set_install_page(self):
1329        instpage = self._get_config_install_page()
1330
1331        # Setting OS value for container doesn't matter presently
1332        self.widget("install-os-distro-box").set_visible(
1333                not self._is_container_install())
1334
1335        enabledetect = False
1336        if instpage == INSTALL_PAGE_URL:
1337            enabledetect = True
1338        elif instpage == INSTALL_PAGE_ISO and not self.conn.is_remote():
1339            enabledetect = True
1340
1341        self.widget("install-detect-os-box").set_visible(enabledetect)
1342        dodetect = (enabledetect and
1343                self.widget("install-detect-os").get_active())
1344        self._change_os_detect(not dodetect)
1345
1346        # Manual installs have nothing to ask for
1347        has_install = instpage != INSTALL_PAGE_MANUAL
1348        self.widget("install-method-pages").set_visible(has_install)
1349        if not has_install:
1350            self._os_list.search_entry.grab_focus()
1351        self.widget("install-method-pages").set_current_page(instpage)
1352
1353    def _back_clicked(self, src_ignore):
1354        notebook = self.widget("create-pages")
1355        curpage = notebook.get_current_page()
1356        next_page = curpage - 1
1357
1358        if curpage == PAGE_FINISH and self._should_skip_disk_page():
1359            # Skip over storage page
1360            next_page -= 1
1361
1362        notebook.set_current_page(next_page)
1363
1364    def _get_next_pagenum(self, curpage):
1365        next_page = curpage + 1
1366
1367        if next_page == PAGE_STORAGE and self._should_skip_disk_page():
1368            # Skip storage page for import installs
1369            next_page += 1
1370
1371        return next_page
1372
1373    def _forward_clicked(self, src_ignore=None):
1374        notebook = self.widget("create-pages")
1375        curpage = notebook.get_current_page()
1376
1377        if curpage == PAGE_INSTALL:
1378            # Make sure we have detected the OS before validating the page
1379            did_start = self._start_detect_os_if_needed(
1380                forward_after_finish=True)
1381            if did_start:
1382                return
1383
1384        if self._validate(curpage) is not True:
1385            return
1386
1387        self.widget("create-forward").grab_focus()
1388        if curpage == PAGE_NAME:
1389            self._set_install_page()
1390
1391        next_page = self._get_next_pagenum(curpage)
1392        notebook.set_current_page(next_page)
1393
1394
1395    def _page_changed(self, ignore1, ignore2, pagenum):
1396        if pagenum == PAGE_FINISH:
1397            try:
1398                self._populate_summary()
1399            except Exception as e:  # pragma: no cover
1400                self.err.show_err(_("Error populating summary page: %s") %
1401                    str(e))
1402                return
1403
1404            self.widget("create-finish").grab_focus()
1405
1406        self.widget("create-back").set_sensitive(pagenum != PAGE_NAME)
1407        self.widget("create-forward").set_visible(pagenum != PAGE_FINISH)
1408        self.widget("create-finish").set_visible(pagenum == PAGE_FINISH)
1409
1410        # Hide all other pages, so the dialog isn't all stretched out
1411        # because of one large page.
1412        for nr in range(self.widget("create-pages").get_n_pages()):
1413            page = self.widget("create-pages").get_nth_page(nr)
1414            page.set_visible(nr == pagenum)
1415
1416        self._set_page_num_text(pagenum)
1417
1418
1419    ############################
1420    # Page validation routines #
1421    ############################
1422
1423    def _build_guestdata(self):
1424        gdata = _GuestData(self.conn.get_backend(), self._capsinfo)
1425
1426        gdata.default_graphics_type = self.config.get_graphics_type()
1427        gdata.x86_cpu_default = self.config.get_default_cpu_setting()
1428
1429        return gdata
1430
1431    def _validate(self, pagenum):
1432        try:
1433            if pagenum == PAGE_NAME:
1434                return self._validate_intro_page()
1435            elif pagenum == PAGE_INSTALL:
1436                return self._validate_install_page()
1437            elif pagenum == PAGE_MEM:
1438                return self._validate_mem_page()
1439            elif pagenum == PAGE_STORAGE:
1440                return self._validate_storage_page()
1441            elif pagenum == PAGE_FINISH:
1442                return self._validate_final_page()
1443        except Exception as e:  # pragma: no cover
1444            self.err.show_err(_("Uncaught error validating install "
1445                                "parameters: %s") % str(e))
1446            return
1447
1448    def _validate_intro_page(self):
1449        self._gdata.machine = self._get_config_machine()
1450        return bool(self._gdata.build_guest())
1451
1452    def _validate_oscontainer_bootstrap(self, fs, src_url, user, passwd):
1453        # Check if the source path was provided
1454        if not src_url:
1455            return self.err.val_err(_("Source URL is required"))
1456
1457        # Require username and password when authenticate
1458        # to source registry.
1459        if user and not passwd:
1460            msg = _("Please specify password for accessing source registry")
1461            return self.err.val_err(msg)
1462
1463        # Validate destination path
1464        if not os.path.exists(fs):
1465            return  # pragma: no cover
1466
1467        if not os.path.isdir(fs):
1468            msg = _("Destination path is not directory: %s") % fs
1469            return self.err.val_err(msg)
1470        if not os.access(fs, os.W_OK):
1471            msg = _("No write permissions for directory path: %s") % fs
1472            return self.err.val_err(msg)
1473        if os.listdir(fs) == []:
1474            return
1475
1476        # Show Yes/No dialog if the destination is not empty
1477        return self.err.yes_no(
1478            _("OS root directory is not empty"),
1479            _("Creating root file system in a non-empty "
1480              "directory might fail due to file conflicts.\n"
1481              "Would you like to continue?"))
1482
1483    def _validate_install_page(self):
1484        instmethod = self._get_config_install_page()
1485        installer = None
1486        location = None
1487        extra = None
1488        cdrom = None
1489        is_import = False
1490        init = None
1491        fs = None
1492        template = None
1493        osobj = self._os_list.get_selected_os()
1494
1495        if instmethod == INSTALL_PAGE_ISO:
1496            media = self._get_config_local_media()
1497            if not media:
1498                msg = _("An install media selection is required.")
1499                return self.err.val_err(msg)
1500            cdrom = media
1501
1502        elif instmethod == INSTALL_PAGE_URL:
1503            media, extra = self._get_config_url_info()
1504
1505            if not media:
1506                return self.err.val_err(_("An install tree is required."))
1507
1508            location = media
1509
1510        elif instmethod == INSTALL_PAGE_IMPORT:
1511            is_import = True
1512            import_path = self._get_config_import_path()
1513            if not import_path:
1514                msg = _("A storage path to import is required.")
1515                return self.err.val_err(msg)
1516
1517            if not virtinst.DeviceDisk.path_definitely_exists(
1518                                                self.conn.get_backend(),
1519                                                import_path):
1520                msg = _("The import path must point to an existing storage.")
1521                return self.err.val_err(msg)
1522
1523        elif instmethod == INSTALL_PAGE_CONTAINER_APP:
1524            init = self.widget("install-app-entry").get_text()
1525            if not init:
1526                return self.err.val_err(_("An application path is required."))
1527
1528        elif instmethod == INSTALL_PAGE_CONTAINER_OS:
1529            fs = self.widget("install-oscontainer-fs").get_text()
1530            if not fs:
1531                return self.err.val_err(_("An OS directory path is required."))
1532
1533            if self._get_config_oscontainer_bootstrap():
1534                src_url = self._get_config_oscontainer_source_url()
1535                user = self._get_config_oscontainer_source_username()
1536                passwd = self._get_config_oscontainer_source_password()
1537                ret = self._validate_oscontainer_bootstrap(
1538                        fs, src_url, user, passwd)
1539                if ret is False:
1540                    return False
1541
1542        elif instmethod == INSTALL_PAGE_VZ_TEMPLATE:
1543            template = self.widget("install-container-template").get_text()
1544            if not template:
1545                return self.err.val_err(_("A template name is required."))
1546
1547        if not self._is_container_install() and not osobj:
1548            msg = _("You must select an OS.")
1549            msg += "\n\n" + self._os_list.eol_text
1550            return self.err.val_err(msg)
1551
1552        # Build the installer and Guest instance
1553        try:
1554            if init:
1555                self._gdata.init = init
1556
1557            if fs:
1558                fsdev = virtinst.DeviceFilesystem(self._gdata.conn)
1559                fsdev.target = "/"
1560                fsdev.source = fs
1561                self._gdata.filesystem = fsdev
1562
1563            if template:
1564                fsdev = virtinst.DeviceFilesystem(self._gdata.conn)
1565                fsdev.target = "/"
1566                fsdev.type = "template"
1567                fsdev.source = template
1568                self._gdata.filesystem = fsdev
1569
1570            self._gdata.location = location
1571            self._gdata.cdrom = cdrom
1572            self._gdata.extra_args = extra
1573            self._gdata.livecd = False
1574            self._gdata.os_variant = osobj and osobj.name or None
1575            guest = self._gdata.build_guest()
1576            installer = self._gdata.build_installer()
1577        except Exception as e:
1578            msg = _("Error setting installer parameters.")
1579            return self.err.val_err(msg, e)
1580
1581        try:
1582            name = virtinst.Guest.generate_name(guest)
1583            virtinst.Guest.validate_name(self._gdata.conn, name)
1584            self._gdata.name = name
1585        except Exception as e:  # pragma: no cover
1586            return self.err.val_err(_("Error setting default name."), e)
1587
1588        self.widget("create-vm-name").set_text(self._gdata.name)
1589
1590        # Kind of wonky, run storage validation now, which will assign
1591        # the import path. Import installer skips the storage page.
1592        if is_import:
1593            if not self._validate_storage_page():
1594                return False
1595
1596        for path in installer.get_search_paths(guest):
1597            self._addstorage.check_path_search(
1598                self, self.conn, path)
1599
1600        res = guest.osinfo.get_recommended_resources()
1601        ram = res.get_recommended_ram(guest.os.arch)
1602        n_cpus = res.get_recommended_ncpus(guest.os.arch)
1603        storage = res.get_recommended_storage(guest.os.arch)
1604        log.debug("Recommended resources for os=%s: "
1605            "ram=%s ncpus=%s storage=%s",
1606            guest.osinfo.name, ram, n_cpus, storage)
1607
1608        # Change the default values suggested to the user.
1609        ram_size = DEFAULT_MEM
1610        if ram:
1611            ram_size = ram // (1024 ** 2)
1612        self.widget("mem").set_value(ram_size)
1613
1614        self.widget("cpus").set_value(n_cpus or 1)
1615
1616        if storage:
1617            storage_size = storage // (1024 ** 3)
1618            self._addstorage.widget("storage-size").set_value(storage_size)
1619
1620        # Validation passed, store the install path (if there is one) in
1621        # gsettings
1622        self._get_config_oscontainer_source_url(store_media=True)
1623        self._get_config_local_media(store_media=True)
1624        self._get_config_url_info(store_media=True)
1625        return True
1626
1627    def _validate_mem_page(self):
1628        cpus = self.widget("cpus").get_value()
1629        mem  = self.widget("mem").get_value()
1630
1631        self._gdata.vcpus = int(cpus)
1632        self._gdata.currentMemory = int(mem) * 1024
1633        self._gdata.memory = int(mem) * 1024
1634
1635        return True
1636
1637    def _get_storage_path(self, vmname, do_log):
1638        failed_disk = None
1639        if self._gdata.failed_guest:
1640            failed_disk = self._gdata.disk
1641
1642        path = None
1643        path_already_created = False
1644
1645        if self._get_config_install_page() == INSTALL_PAGE_IMPORT:
1646            path = self._get_config_import_path()
1647
1648        elif self._is_default_storage():
1649            if failed_disk:
1650                # Don't generate a new path if the install failed
1651                path = failed_disk.get_source_path()
1652                path_already_created = failed_disk.storage_was_created
1653                if do_log:
1654                    log.debug("Reusing failed disk path=%s "
1655                        "already_created=%s", path, path_already_created)
1656            else:
1657                path = self._addstorage.get_default_path(vmname)
1658                if do_log:
1659                    log.debug("Default storage path is: %s", path)
1660
1661        return path, path_already_created
1662
1663    def _validate_storage_page(self):
1664        path, path_already_created = self._get_storage_path(
1665            self._gdata.name, do_log=True)
1666
1667        disk = None
1668        storage_enabled = self.widget("enable-storage").get_active()
1669        try:
1670            if storage_enabled:
1671                disk = self._addstorage.build_device(
1672                        self._gdata.name, path=path)
1673
1674            if disk and self._addstorage.validate_device(disk) is False:
1675                return False
1676        except Exception as e:
1677            return self.err.val_err(_("Storage parameter error."), e)
1678
1679        if self._get_config_install_page() == INSTALL_PAGE_ISO:
1680            # CD/ISO install and no disks implies LiveCD
1681            self._gdata.livecd = not storage_enabled
1682
1683        self._gdata.disk = disk
1684        if not storage_enabled:
1685            return True
1686
1687        disk.storage_was_created = path_already_created
1688        return True
1689
1690
1691    def _validate_final_page(self):
1692        # HV + Arch selection
1693        name = self._get_config_name()
1694        if name != self._gdata.name:
1695            try:
1696                virtinst.Guest.validate_name(self._gdata.conn, name)
1697                self._gdata.name = name
1698            except Exception as e:
1699                return self.err.val_err(_("Invalid guest name"), str(e))
1700            if self._is_default_storage():
1701                log.debug("User changed VM name and using default "
1702                    "storage, re-validating with new default storage path.")
1703                if not self._validate_storage_page():
1704                    return False  # pragma: no cover
1705
1706        macaddr = virtinst.DeviceInterface.generate_mac(
1707            self.conn.get_backend())
1708
1709        net = self._netlist.build_device(macaddr)
1710
1711        self._netlist.validate_device(net)
1712        self._gdata.interface = net
1713        return True
1714
1715
1716    #############################
1717    # Distro detection handling #
1718    #############################
1719
1720    def _start_detect_os_if_needed(self, forward_after_finish=False):
1721        """
1722        Will kick off the OS detection thread if all conditions are met,
1723        like we actually have media to detect, detection isn't already
1724        in progress, etc.
1725
1726        Returns True if we actually start the detection process
1727        """
1728        is_install_page = (self.widget("create-pages").get_current_page() ==
1729            PAGE_INSTALL)
1730        cdrom, location = self._get_config_detectable_media()
1731
1732        if self._detect_os_in_progress:
1733            return  # pragma: no cover
1734        if not is_install_page:
1735            return  # pragma: no cover
1736        if not cdrom and not location:
1737            return
1738        if not self._is_os_detect_active():
1739            return
1740        if self._os_already_detected_for_media:
1741            return
1742
1743        self._do_start_detect_os(cdrom, location, forward_after_finish)
1744        return True
1745
1746    def _do_start_detect_os(self, cdrom, location, forward_after_finish):
1747        self._detect_os_in_progress = False
1748
1749        log.debug("Starting OS detection thread for cdrom=%s location=%s",
1750                cdrom, location)
1751        self.widget("create-forward").set_sensitive(False)
1752
1753        class ThreadResults(object):
1754            """
1755            Helper object to track results from the detection thread
1756            """
1757            _DETECT_FAILED = 1
1758            _DETECT_INPROGRESS = 2
1759            def __init__(self):
1760                self._results = self._DETECT_INPROGRESS
1761
1762            def in_progress(self):
1763                return self._results == self._DETECT_INPROGRESS
1764
1765            def set_failed(self):
1766                self._results = self._DETECT_FAILED
1767
1768            def set_distro(self, distro):
1769                self._results = distro
1770            def get_distro(self):
1771                if self._results == self._DETECT_FAILED:
1772                    return None
1773                return self._results
1774
1775        thread_results = ThreadResults()
1776        detectThread = threading.Thread(target=self._detect_thread_cb,
1777                                        name="Actual media detection",
1778                                        args=(cdrom, location, thread_results))
1779        detectThread.setDaemon(True)
1780        detectThread.start()
1781
1782        self._os_list.search_entry.set_text(_("Detecting..."))
1783        spin = self.widget("install-detect-os-spinner")
1784        spin.start()
1785
1786        self._report_detect_os_progress(0, thread_results,
1787                forward_after_finish)
1788
1789    def _detect_thread_cb(self, cdrom, location, thread_results):
1790        """
1791        Thread callback that does the actual detection
1792        """
1793        try:
1794            installer = virtinst.Installer(self.conn.get_backend(),
1795                                           cdrom=cdrom,
1796                                           location=location)
1797            distro = installer.detect_distro(self._gdata.build_guest())
1798            thread_results.set_distro(distro)
1799        except Exception:
1800            log.exception("Error detecting distro.")
1801            thread_results.set_failed()
1802
1803    def _report_detect_os_progress(self, idx, thread_results,
1804            forward_after_finish):
1805        """
1806        Checks detection progress via the _detect_os_results variable
1807        and updates the UI labels, counts the number of iterations,
1808        etc.
1809
1810        We set a hard time limit on the distro detection to avoid the
1811        chance of the detection hanging (like slow URL lookup)
1812        """
1813        try:
1814            if (thread_results.in_progress() and
1815                (idx < (DETECT_TIMEOUT * 2))):
1816                # Thread is still going and we haven't hit the timeout yet,
1817                # so update the UI labels and reschedule this function
1818                self.timeout_add(500, self._report_detect_os_progress,
1819                    idx + 1, thread_results, forward_after_finish)
1820                return
1821
1822            distro = thread_results.get_distro()
1823        except Exception:  # pragma: no cover
1824            distro = None
1825            log.exception("Error in distro detect timeout")
1826
1827        spin = self.widget("install-detect-os-spinner")
1828        spin.stop()
1829        log.debug("Finished UI OS detection.")
1830
1831        self.widget("create-forward").set_sensitive(True)
1832        self._os_already_detected_for_media = True
1833        self._detect_os_in_progress = False
1834
1835        if not self._is_os_detect_active():
1836            # If the user changed the OS detect checkbox in the meantime,
1837            # don't update the UI
1838            return  # pragma: no cover
1839
1840        if distro:
1841            self._os_list.select_os(virtinst.OSDB.lookup_os(distro))
1842        else:
1843            self._os_list.reset_state()
1844            self._os_list.search_entry.set_text(_("None detected"))
1845
1846        if forward_after_finish:
1847            self.idle_add(self._forward_clicked, ())
1848
1849
1850    ##########################
1851    # Guest install routines #
1852    ##########################
1853
1854    def _finish_clicked(self, src_ignore):
1855        # Validate the final page
1856        page = self.widget("create-pages").get_current_page()
1857        if self._validate(page) is not True:
1858            return
1859
1860        log.debug("Starting create finish() sequence")
1861        self._gdata.failed_guest = None
1862
1863        try:
1864            guest = self._gdata.build_guest()
1865            installer = self._gdata.build_installer()
1866            self.set_finish_cursor()
1867
1868            # This encodes all the virtinst defaults up front, so the customize
1869            # dialog actually shows disk buses, cache values, default devices,
1870            # etc. Not required for straight start_install but doesn't hurt.
1871            installer.set_install_defaults(guest)
1872
1873            if not self.widget("summary-customize").get_active():
1874                self._start_install(guest, installer)
1875                return
1876
1877            log.debug("User requested 'customize', launching dialog")
1878            self._show_customize_dialog(guest, installer)
1879        except Exception as e:  # pragma: no cover
1880            self.reset_finish_cursor()
1881            self.err.show_err(_("Error starting installation: %s") % str(e))
1882            return
1883
1884    def _cleanup_customize_window(self):
1885        if not self._customize_window:
1886            return
1887
1888        # We can re-enter this: cleanup() -> close() -> "details-closed"
1889        window = self._customize_window
1890        virtinst_domain = self._customize_window.vm
1891        self._customize_window = None
1892        window.cleanup()
1893        virtinst_domain.cleanup()
1894        virtinst_domain = None
1895
1896    def _show_customize_dialog(self, origguest, installer):
1897        orig_vdomain = vmmDomainVirtinst(self.conn,
1898                origguest, origguest.uuid, installer)
1899
1900        def customize_finished_cb(src, vdomain):
1901            if not self.is_visible():
1902                return  # pragma: no cover
1903            log.debug("User finished customize dialog, starting install")
1904            self._gdata.failed_guest = None
1905            self._start_install(vdomain.get_backend(), installer)
1906
1907        def config_canceled_cb(src):
1908            log.debug("User closed customize window, closing wizard")
1909            self._close_requested()
1910
1911        # We specifically don't use vmmVMWindow.get_instance here since
1912        # it's not a top level VM window
1913        self._cleanup_customize_window()
1914        self._customize_window = vmmVMWindow(orig_vdomain, self.topwin)
1915        self._customize_window.connect(
1916                "customize-finished", customize_finished_cb)
1917        self._customize_window.connect("closed", config_canceled_cb)
1918        self._customize_window.show()
1919
1920    def _install_finished_cb(self, error, details, guest, parentobj):
1921        self.reset_finish_cursor(parentobj.topwin)
1922
1923        if error:
1924            error = (_("Unable to complete install: '%s'") % error)
1925            parentobj.err.show_err(error, details=details)
1926            self._gdata.failed_guest = guest
1927            return
1928
1929        foundvm = None
1930        for vm in self.conn.list_vms():
1931            if vm.get_uuid() == guest.uuid:
1932                foundvm = vm
1933                break
1934
1935        self._close()
1936
1937        # Launch details dialog for new VM
1938        vmmVMWindow.get_instance(self, foundvm).show()
1939
1940
1941    def _start_install(self, guest, installer):
1942        """
1943        Launch the async job to start the install
1944        """
1945        bootstrap_args = {}
1946        # If creating new container and "container bootstrap" is enabled
1947        if (guest.os.is_container() and
1948            self._get_config_oscontainer_bootstrap()):
1949            bootstrap_arg_keys = {
1950                'src': self._get_config_oscontainer_source_url,
1951                'dest': self.widget("install-oscontainer-fs").get_text,
1952                'user': self._get_config_oscontainer_source_username,
1953                'passwd': self._get_config_oscontainer_source_password,
1954                'insecure': self._get_config_oscontainer_isecure,
1955                'root_password': self._get_config_oscontainer_root_password,
1956            }
1957            for key, getter in bootstrap_arg_keys.items():
1958                bootstrap_args[key] = getter()
1959
1960        parentobj = self._customize_window or self
1961        progWin = vmmAsyncJob(self._do_async_install,
1962                              [guest, installer, bootstrap_args],
1963                              self._install_finished_cb, [guest, parentobj],
1964                              _("Creating Virtual Machine"),
1965                              _("The virtual machine is now being "
1966                                "created. Allocation of disk storage "
1967                                "and retrieval of the installation "
1968                                "images may take a few minutes to "
1969                                "complete."),
1970                              parentobj.topwin)
1971        progWin.run()
1972
1973    def _do_async_install(self, asyncjob, guest, installer, bootstrap_args):
1974        """
1975        Kick off the actual install
1976        """
1977        meter = asyncjob.get_meter()
1978
1979        if bootstrap_args:
1980            # Start container bootstrap
1981            self._create_directory_tree(asyncjob, meter, bootstrap_args)
1982            if asyncjob.has_error():
1983                # Do not continue if virt-bootstrap failed
1984                return
1985
1986        # Build a list of pools we should refresh, if we are creating storage
1987        refresh_pools = []
1988        for disk in guest.devices.disk:
1989            if not disk.wants_storage_creation():
1990                continue
1991
1992            pool = disk.get_parent_pool()
1993            if not pool:
1994                continue  # pragma: no cover
1995
1996            poolname = pool.name()
1997            if poolname not in refresh_pools:
1998                refresh_pools.append(poolname)
1999
2000        log.debug("Starting background install process")
2001        installer.start_install(guest, meter=meter)
2002        log.debug("Install completed")
2003
2004        # Wait for VM to show up
2005        self.conn.schedule_priority_tick(pollvm=True)
2006        count = 0
2007        foundvm = None
2008        while count < 200:
2009            for vm in self.conn.list_vms():
2010                if vm.get_uuid() == guest.uuid:
2011                    foundvm = vm
2012            if foundvm:
2013                break
2014            count += 1
2015            time.sleep(.1)
2016
2017        if not foundvm:
2018            raise RuntimeError(  # pragma: no cover
2019                _("VM '%s' didn't show up after expected time.") % guest.name)
2020        vm = foundvm
2021
2022        if vm.is_shutoff():
2023            # Domain is already shutdown, but no error was raised.
2024            # Probably means guest had no 'install' phase, as in
2025            # for live cds. Try to restart the domain.
2026            vm.startup()  # pragma: no cover
2027        elif installer.has_install_phase():
2028            # Register a status listener, which will restart the
2029            # guest after the install has finished
2030            def cb():
2031                vm.connect_opt_out("state-changed",
2032                                   self._check_install_status)
2033                return False
2034            self.idle_add(cb)
2035
2036        # Kick off pool updates
2037        for poolname in refresh_pools:
2038            try:
2039                pool = self.conn.get_pool_by_name(poolname)
2040                self.idle_add(pool.refresh)
2041            except Exception:  # pragma: no cover
2042                log.debug("Error looking up pool=%s for refresh after "
2043                    "VM creation.", poolname, exc_info=True)
2044
2045
2046    def _check_install_status(self, vm):
2047        """
2048        Watch the domain that we are installing, waiting for the state
2049        to change, so we can restart it as needed
2050        """
2051        if vm.is_crashed():  # pragma: no cover
2052            log.debug("VM crashed, cancelling install plans.")
2053            return True
2054
2055        if not vm.is_shutoff():
2056            return  # pragma: no cover
2057
2058        if vm.get_install_abort():
2059            log.debug("User manually shutdown VM, not restarting "
2060                          "guest after install.")
2061            return True
2062
2063        # Hitting this from the test suite is hard because we can't force
2064        # the test driver VM to stop behind virt-manager's back
2065        try:  # pragma: no cover
2066            log.debug("Install should be completed, starting VM.")
2067            vm.startup()
2068        except Exception as e:  # pragma: no cover
2069            self.err.show_err(_("Error continuing install: %s") % str(e))
2070
2071        return True  # pragma: no cover
2072
2073
2074    def _create_directory_tree(self, asyncjob, meter, bootstrap_args):
2075        """
2076        Call bootstrap method from virtBootstrap and show logger messages
2077        as state/details.
2078        """
2079        import logging
2080        import virtBootstrap
2081
2082        meter.start(text=_("Bootstraping container"), size=100)
2083        def progress_update_cb(prog):
2084            meter.text = _(prog['status'])
2085            meter.update(prog['value'])
2086
2087        asyncjob.details_enable()
2088        # Use logging filter to show messages of the progreess on the GUI
2089        class SetStateFilter(logging.Filter):
2090            def filter(self, record):
2091                asyncjob.details_update("%s\n" % record.getMessage())
2092                return True
2093
2094        # Use string buffer to store log messages
2095        log_stream = io.StringIO()
2096
2097        # Get virt-bootstrap logger
2098        vbLogger = logging.getLogger('virtBootstrap')
2099        vbLogger.setLevel(logging.DEBUG)
2100        # Create handler to store log messages in the string buffer
2101        hdlr = logging.StreamHandler(log_stream)
2102        hdlr.setFormatter(logging.Formatter('%(message)s'))
2103        # Use logging filter to show messages on GUI
2104        hdlr.addFilter(SetStateFilter())
2105        vbLogger.addHandler(hdlr)
2106
2107        # Key word arguments to be passed
2108        kwargs = {'uri': bootstrap_args['src'],
2109                  'dest': bootstrap_args['dest'],
2110                  'not_secure': bootstrap_args['insecure'],
2111                  'progress_cb': progress_update_cb}
2112        if bootstrap_args['user'] and bootstrap_args['passwd']:
2113            kwargs['username'] = bootstrap_args['user']
2114            kwargs['password'] = bootstrap_args['passwd']
2115        if bootstrap_args['root_password']:
2116            kwargs['root_password'] = bootstrap_args['root_password']
2117        log.debug('Start container bootstrap')
2118        try:
2119            virtBootstrap.bootstrap(**kwargs)
2120            # Success - uncheck the 'install-oscontainer-bootstrap' checkbox
2121
2122            def cb():
2123                self.widget("install-oscontainer-bootstrap").set_active(False)
2124            self.idle_add(cb)
2125        except Exception as err:
2126            asyncjob.set_error("virt-bootstrap did not complete successfully",
2127                               '%s\n%s' % (err, log_stream.getvalue()))
2128