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