# # Copyright (C) 2006 Red Hat, Inc. # Copyright (C) 2006 Hugh O. Brock # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # import gobject import gtk import gtk.gdk import gtk.glade import xeninst import os, sys import subprocess import urlgrabber.grabber as grabber import tempfile import logging import dbus from rhpl.exception import installExceptionHandler from rhpl.translate import _, N_, textdomain, utf8 from virtManager.asyncjob import vmmAsyncJob VM_PARAVIRT = 1 VM_FULLY_VIRT = 2 VM_INSTALL_FROM_ISO = 1 VM_INSTALL_FROM_CD = 2 VM_STORAGE_PARTITION = 1 VM_STORAGE_FILE = 2 DEFAULT_STORAGE_FILE_SIZE = 500 class vmmCreate(gobject.GObject): __gsignals__ = { "action-show-console": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str,str)), "action-show-terminal": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str,str)), } def __init__(self, config, connection): self.__gobject_init__() self.config = config self.connection = connection self.window = gtk.glade.XML(config.get_glade_file(), "vmm-create") self.topwin = self.window.get_widget("vmm-create") self.topwin.hide() # Get a connection to the SYSTEM bus self.bus = dbus.SystemBus() # Get a handle to the HAL service hal_object = self.bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') self.hal_iface = dbus.Interface(hal_object, 'org.freedesktop.Hal.Manager') self.window.signal_autoconnect({ "on_create_pages_switch_page" : self.page_changed, "on_create_cancel_clicked" : self.close, "on_vmm_create_delete_event" : self.close, "on_create_back_clicked" : self.back, "on_create_forward_clicked" : self.forward, "on_create_finish_clicked" : self.finish, "on_create_vm_name_focus_out_event" : self.set_name, "on_virt_method_toggled" : self.set_virt_method, "on_media_toggled" : self.set_install_from, "on_fv_iso_location_browse_clicked" : self.browse_iso_location, "on_fv_iso_location_focus_out_event" : self.set_media_address, "on_pv_media_url_focus_out_event" : self.set_media_address, "on_pv_ks_url_focus_out_event" : self.set_kickstart_address, "on_storage_partition_address_focus_out_event" : self.set_storage_partition_address, "on_storage_file_address_focus_out_event" : self.set_storage_file_address, "on_storage_partition_address_browse_clicked" : self.browse_storage_partition_address, "on_storage_file_address_browse_clicked" : self.browse_storage_file_address, "on_storage_toggled" : self.set_storage_type, "on_storage_file_size_changed" : self.set_storage_file_size, "on_create_memory_max_value_changed" : self.set_max_memory, "on_create_memory_startup_value_changed" : self.set_startup_memory, "on_create_vcpus_changed" : self.set_vcpus, "on_cd_focus_out_event" : self.choose_media_location, "on_cd_path_changed" : self.choose_media_location, }) self.set_initial_state() def show(self): self.vm_added_handle = self.connection.connect("vm-added", self.open_vm_console) self.topwin.show() def _init_members(self): #the dahta self.vm_name = None self.virt_method = VM_PARAVIRT self.install_fv_media_type = VM_INSTALL_FROM_ISO self.install_media_address = None self.install_kickstart_address = None self.storage_method = VM_STORAGE_PARTITION self.storage_partition_address = None self.storage_file_address = None self.storage_file_size = DEFAULT_STORAGE_FILE_SIZE self.max_memory = 0 self.startup_memory = 0 self.vcpus = 1 self.vm_uuid = None self.vm_added_handle = None self.install_error = None def set_initial_state(self): notebook = self.window.get_widget("create-pages") notebook.set_show_tabs(False) #XXX I don't think I should have to go through and set a bunch of background colors # in code, but apparently I do... black = gtk.gdk.color_parse("#000") self.window.get_widget("intro-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page1-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page2-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page3-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page3a-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page4-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page5-title").modify_bg(gtk.STATE_NORMAL,black) self.window.get_widget("page6-title").modify_bg(gtk.STATE_NORMAL,black) # set up the list for the cd-path widget self.opt_media_list = self.window.get_widget("cd-path") model = gtk.ListStore(str) self.opt_media_list.set_model(model) text = gtk.CellRendererText() self.opt_media_list.pack_start(text, True) self.opt_media_list.add_attribute(text, 'text', 0) self.reset_state() def reset_state(self): self._init_members() notebook = self.window.get_widget("create-pages") notebook.set_current_page(0) # Hide the "finish" button until the appropriate time self.window.get_widget("create-finish").hide() self.window.get_widget("create-forward").show() self.window.get_widget("create-back").set_sensitive(False) self.window.get_widget("storage-file-size").set_sensitive(False) def forward(self, ignore=None): notebook = self.window.get_widget("create-pages") if(self.validate(notebook.get_current_page()) != True): return if (notebook.get_current_page() == 2 and self.virt_method == VM_PARAVIRT): notebook.set_current_page(4) elif (notebook.get_current_page() == 3 and self.virt_method == VM_FULLY_VIRT): notebook.set_current_page(5) else: notebook.next_page() def back(self, ignore=None): notebook = self.window.get_widget("create-pages") # do this always, since there's no "leaving a notebook page" event. self.window.get_widget("create-finish").hide() self.window.get_widget("create-forward").show() if notebook.get_current_page() == 4 and self.virt_method == VM_PARAVIRT: notebook.set_current_page(2) elif notebook.get_current_page() == 5 and self.virt_method == VM_FULLY_VIRT: notebook.set_current_page(3) else: notebook.prev_page() def page_changed(self, notebook, page, page_number): # would you like some spaghetti with your salad, sir? if page_number == 0: #set up the front page self.window.get_widget("create-back").set_sensitive(False) elif page_number == 1: #set up the system-name page name_widget = self.window.get_widget("create-vm-name") if self.vm_name != None: name_widget.set_text(self.vm_name) else: name_widget.set_text("") name_widget.grab_focus() elif page_number == 2: #set up the virt method page if self.virt_method == VM_PARAVIRT: self.window.get_widget("virt-method-pv").set_active(True) else: self.window.get_widget("virt-method-fv").set_active(True) elif page_number == 3: #set up the fv install media page model = self.opt_media_list.get_model() model.clear() #make sure the model has one empty item model.append() devs = self._get_optical_devices() for dev in devs: model.append([dev]) if self.install_media_address != None: self.window.get_widget("fv-iso-location").set_text(self.install_media_address) else: self.window.get_widget("fv-iso-location").set_text("") if self.install_fv_media_type == VM_INSTALL_FROM_ISO: self.window.get_widget("media-iso-image").set_active(True) self.window.get_widget("fv-iso-location-box").set_sensitive(True) else: self.window.get_widget("media-physical").set_active(True) self.window.get_widget("fv-iso-location-box").set_sensitive(False) elif page_number == 4: #set up the pv install media page url_widget = self.window.get_widget("pv-media-url") ks_widget = self.window.get_widget("pv-ks-url") if self.install_media_address != None: url_widget.set_text(self.install_media_address) else: url_widget.set_text("") if self.install_kickstart_address != None: ks_widget.set_text(self.install_kickstart_address) else: ks_widget.set_text("") url_widget.grab_focus() elif page_number == 5: #set up the storage space page partwidget = self.window.get_widget("storage-partition-address") filewidget = self.window.get_widget("storage-file-address") if self.storage_partition_address != None: partwidget.set_text(self.storage_partition_address) else: partwidget.set_text("") if self.storage_file_address != None: filewidget.set_text(self.storage_file_address) else: filewidget.set_text("") if self.storage_method == VM_STORAGE_PARTITION: self.window.get_widget("storage-partition").set_active(True) self.window.get_widget("storage-partition-box").set_sensitive(True) self.window.get_widget("storage-file-box").set_sensitive(False) else: self.window.get_widget("storage-file-backed").set_active(True) self.window.get_widget("storage-partition-box").set_sensitive(False) self.window.get_widget("storage-file-box").set_sensitive(True) elif page_number == 6: # memory stuff max_mem = self.connection.host_memory_size()/1024 # in megabytes from henceforth #avoid absurdity, hopefully if self.max_memory == 0: self.max_memory = int(max_mem / 2) if self.startup_memory > self.max_memory: self.startup_memory = self.max_memory max_mem_slider = self.window.get_widget("create-memory-max") self.window.get_widget("create-host-memory").set_text("%d MB" % max_mem) max_mem_slider.get_adjustment().upper = max_mem max_mem_slider.get_adjustment().value = self.max_memory startup_mem_slider = self.window.get_widget("create-memory-startup") startup_mem_slider.get_adjustment().upper = self.max_memory startup_mem_slider.get_adjustment().value = self.startup_memory #vcpu stuff max_cpus = self.connection.host_maximum_processor_count() self.window.get_widget("create-cpus-physical").set_text(`max_cpus`) cpu_spinbox = self.window.get_widget("create-vcpus").get_adjustment() cpu_spinbox.upper = max_cpus cpu_spinbox.value = self.vcpus elif page_number == 7: #set up the congrats page congrats = self.window.get_widget("create-congrats-label") # XXX the validation doesn't really go here if self.vm_name == None: self.vm_name = "No Name" congrats.set_text(_("Congratulations, you have successfully created a new virtual system, \"%s\". \nYou'll now be able to view and work with \"%s\" in the virtual machine manager.") % (self.vm_name, self.vm_name) ) congrats.set_use_markup(True) self.window.get_widget("create-forward").hide() self.window.get_widget("create-finish").show() def close(self, ignore1=None,ignore2=None): self.connection.disconnect(int(self.vm_added_handle)) self.vm_added_handle = None self.topwin.hide() return 1 def finish(self, ignore=None): #begin DEBUG STUFF if self.install_kickstart_address == None: ks = "None" else: ks = self.install_kickstart_address if self.storage_file_size==None: sfs = "Preset" else: sfs = `self.storage_file_size/1024` if self.storage_method == VM_STORAGE_PARTITION: saddr = self.storage_partition_address else: saddr = self.storage_file_address logging.debug("your vm properties: \n Name=" + self.vm_name + \ "\n Virt method: " + `self.virt_method` + \ "\n Install media type (fv): " + `self.install_fv_media_type` + \ "\n Install media address: " + self.install_media_address + \ "\n Install kickstart address: " + ks + \ "\n Install storage type: " + `self.storage_method` + \ "\n Install storage address: " + saddr + \ "\n Install storage file size: " + sfs + \ "\n Install max kernel memory: " + `int(self.max_memory)` + \ "\n Install startup kernel memory: " + `int(self.startup_memory)` + \ "\n Install vcpus: " + `int(self.vcpus)`) # end DEBUG STUFF # first things first, are we trying to create a fully virt guest? if self.virt_method == VM_FULLY_VIRT: guest = xeninst.FullVirtGuest() try: guest.cdrom = self.install_media_address except ValueError, e: self._validation_error_box(_("Invalid FV media address"),e.args[0]) self.install_media_address = None else: guest = xeninst.ParaVirtGuest() try: guest.location = self.install_media_address except ValueError, e: self._validation_error_box(_("Invalid PV media address"), e.args[0]) self.install_media_address = None return # we got this far, now let's see if the location is really valid valid = self._validate_pv_url(guest.location) if valid != None: self._validation_error_box(_("Error locating PV install image"), valid) return # also check the kickstart URL if it exists if self.install_kickstart_address != None and self.install_kickstart_address != "": valid = self._validate_pv_ks_url(self.install_kickstart_address) if valid != None: self._validation_error_box(_("Error locating PV kickstart file"),valid) return guest.extraargs = "ks=%s" % self.install_kickstart_address # set the name try: guest.name = self.vm_name except ValueError, e: self._validation_error_box(_("Invalid system name"), e.args[0]) self.vm_name = None return # set the memory try: guest.memory = int(self.max_memory) except ValueError: self._validation_error_box(_("Invalid memory setting"), e.args[0]) self.max_memory = None return # set vcpus guest.vcpus = int(self.vcpus) # disks if self.storage_method == VM_STORAGE_PARTITION: saddr = self.storage_partition_address else: saddr = self.storage_file_address filesize = None if self.storage_file_size != None: filesize = int(self.storage_file_size/1024) try: d = xeninst.XenDisk(saddr, filesize) except ValueError, e: self._validation_error_box(_("Invalid storage address"), e.args[0]) self.storage_partition_address = self.storage_file_address = self.storage_file_size = None return guest.disks.append(d) # network n = xeninst.XenNetworkInterface(None) guest.nics.append(n) #grab the uuid before we start self.vm_uuid = xeninst.util.uuidToString(xeninst.util.randomUUID()) guest.set_uuid(self.vm_uuid) #let's go self.install_error = None progWin = vmmAsyncJob(self.config, self.do_install, [guest], title=_("Creating Virtual Machine")) progWin.run() if self.install_error != None: self._validation_error_box(_("Guest Install Error"), self.install_error) return self.close() def do_install(self, guest): try: guest.start_install(False) except RuntimeError, e: self.install_error = "ERROR: %s" % e logging.exception(e) return def set_name(self, src, ignore=None): self.vm_name = src.get_text() def set_virt_method(self, button): if button.get_active(): if button.name == "virt-method-pv": self.virt_method = VM_PARAVIRT else: self.virt_method = VM_FULLY_VIRT self.install_media_address = None def set_install_from(self, button): if button.get_active(): if button.name == "media-iso-image": self.install_fv_media_type = VM_INSTALL_FROM_ISO self.window.get_widget("fv-iso-location-box").set_sensitive(True) self.opt_media_list.set_sensitive(False) elif button.name == "media-physical": self.install_fv_media_type = VM_INSTALL_FROM_CD self.window.get_widget("fv-iso-location-box").set_sensitive(False) self.opt_media_list.set_sensitive(True) self.opt_media_list.set_active(0) def choose_media_location(self, src): model = self.opt_media_list.get_model() logging.debug("User chose: " + model.get_value(self.opt_media_list.get_active_iter(), 0)) self.install_media_address = model.get_value(self.opt_media_list.get_active_iter(), 0) def browse_iso_location(self, ignore1=None, ignore2=None): self.install_media_address = self._browse_file(_("Locate ISO Image")) if self.install_media_address != None: self.window.get_widget("fv-iso-location").set_text(self.install_media_address) def _browse_file(self, dialog_name, folder=None): # user wants to browse for an ISO fcdialog = gtk.FileChooserDialog(dialog_name, self.window.get_widget("vmm-create"), gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT), None) if folder != None: fcdialog.set_current_folder(folder) response = fcdialog.run() fcdialog.hide() if(response == gtk.RESPONSE_ACCEPT): filename = fcdialog.get_filename() fcdialog.destroy() return filename else: fcdialog.destroy() return None def set_media_address(self, src, ignore=None): self.install_media_address = src.get_text() def set_kickstart_address(self, src, ignore=None): self.install_kickstart_address = src.get_text() def set_storage_partition_address(self, src, ignore=None): self.storage_partition_address = src.get_text() def set_storage_file_address(self, src, ignore=None): self.storage_file_address = src.get_text() def browse_storage_partition_address(self, src, ignore=None): self.storage_partition_address = self._browse_file(_("Locate Storage Partition"), "/dev") if self.storage_partition_address != None: self.window.get_widget("storage-partition-address").set_text(self.storage_partition_address) def browse_storage_file_address(self, src, ignore=None): # Reset the storage_file_size value if self.storage_file_size == None: self.storage_file_size = STORAGE_FILE_SIZE self.window.get_widget("storage-file-size").set_sensitive(True) fcdialog = gtk.FileChooserDialog(_("Locate or Create New Storage File"), self.window.get_widget("vmm-create"), gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT), None) fcdialog.set_do_overwrite_confirmation(True) fcdialog.connect("confirm-overwrite", self.confirm_overwrite_callback) response = fcdialog.run() fcdialog.hide() if(response == gtk.RESPONSE_ACCEPT): self.storage_file_address = fcdialog.get_filename() if self.storage_file_address != None: self.window.get_widget("storage-file-address").set_text(self.storage_file_address) def confirm_overwrite_callback(self, chooser): # Only called when the user has chosen an existing file self.window.get_widget("storage-file-size").set_sensitive(False) self.storage_file_size = None return gtk.FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME def set_storage_type(self, button): if button.get_active(): if button.name == "storage-partition": self.storage_method = VM_STORAGE_PARTITION self.window.get_widget("storage-partition-box").set_sensitive(True) self.window.get_widget("storage-file-box").set_sensitive(False) else: self.storage_method = VM_STORAGE_FILE self.window.get_widget("storage-partition-box").set_sensitive(False) self.window.get_widget("storage-file-box").set_sensitive(True) def set_storage_file_size(self, src): self.storage_file_size = src.get_adjustment().value def set_max_memory(self, src): self.max_memory = src.get_adjustment().value startup_mem_adjustment = self.window.get_widget("create-memory-startup").get_adjustment() if startup_mem_adjustment.value > self.max_memory: startup_mem_adjustment.value = self.max_memory startup_mem_adjustment.upper = self.max_memory def set_startup_memory(self, src): self.startup_memory = src.get_adjustment().value def set_vcpus(self, src): self.vcpus = src.get_adjustment().value def validate(self, page_num): if page_num == 1: # the system name page name = self.window.get_widget("create-vm-name").get_text() if len(name) > 50 or " " in name or len(name) == 0: self._validation_error_box(_("Invalid System Name"), \ _("System name must be non-blank, less than 50 characters, and contain no spaces")) return False elif page_num == 2: # the virt method page if self.virt_method == VM_FULLY_VIRT and not xeninst.util.is_hvm_capable(): self._validation_error_box(_("Hardware Support Required"), \ _("Your hardware does not appear to support full virtualization. Only paravirtualized guests will be available on this hardware.")) return False elif page_num == 3: # the fully virt media page if self.install_fv_media_type == VM_INSTALL_FROM_ISO and \ (self.install_media_address == None or len(self.install_media_address) == 0): self._validation_error_box(_("ISO Location Required"), \ _("You must specify an ISO location for the guest install image")) return False elif page_num == 4: # the paravirt media page if self.install_media_address == None or len(self.install_media_address) == 0: self._validation_error_box(_("URL Required"), \ _("You must specify a URL for the install image for the guest install")) return False elif page_num == 5: # the storage page if (self.storage_partition_address == None or len(self.storage_partition_address) == 0) and (self.storage_file_address == None or len(self.storage_file_address) == 0): self._validation_error_box(_("Storage Address Required"), \ _("You must specify a partition or a file for storage for the guest install")) return False # do this always, since there's no "leaving a notebook page" event. self.window.get_widget("create-back").set_sensitive(True) return True def _validation_error_box(self, text1, text2=None): message_box = gtk.MessageDialog(self.window.get_widget("vmm-create"), \ 0, \ gtk.MESSAGE_ERROR, \ gtk.BUTTONS_OK, \ text1) if text2 != None: message_box.format_secondary_text(text2) message_box.run() message_box.destroy() def open_vm_console(self,ignore,uri,uuid): if uuid == self.vm_uuid: if (self.virt_method == VM_PARAVIRT): self.emit("action-show-terminal", self.connection.get_uri(), self.vm_uuid) else: self.emit("action-show-console", self.connection.get_uri(), self.vm_uuid) def _validate_pv_url(self, url): if url.startswith("http://") or \ url.startswith("ftp://"): try: kernel = grabber.urlopen("%s/images/xen/vmlinuz" %(url,)) except IOError, e: return "Invalid URL location given: %s" % e.args[1] try: initrd = grabber.urlopen("%s/images/xen/initrd.img" %(url,)) except IOError, e: kernel.close() return "Invalid URL location given: %s" % e.args[1] kernel.close() initrd.close() elif url.startswith("nfs:"): nfsmntdir = tempfile.mkdtemp(prefix="xennfs.", dir="/var/lib/xen") cmd = ["mount", "-o", "ro", url[4:], nfsmntdir] ret = subprocess.call(cmd) if ret != 0: return "Unable to mount NFS location %s" % url try: kernel = open("%s/images/xen/vmlinuz" %(nfsmntdir,), "r") except IOError, e: logging.exception(e) self._unmount_nfs_dir(nfsmntdir) return "Invalid NFS location given: %s" % e.args[1] try: initrd = open("%s/images/xen/initrd.img" %(nfsmntdir,), "r") except IOError, e: logging.exception(e) kernel.close() self._unmount_nfs_dir(nfsmntdir) return "Invalid NFS location given: %s" % e.args[1] kernel.close() initrd.close() self._unmount_nfs_dir(nfsmntdir) return None def _validate_pv_ks_url(self, url): if url.startswith("http://") or \ url.startswith("ftp://"): try: ks = grabber.urlopen(url) except IOError, e: logging.exception(e) return "Invalid URL location given: %s" % e.args[1] ks.close() elif url.startswith("nfs:"): nfsmntdir = tempfile.mkdtemp(prefix="xennfs.", dir="/var/lib/xen") cmd = ["mount", "-o", "ro", url[4:], nfsmntdir] ret = subprocess.call(cmd) if ret != 0: return "Unable to mount NFS location %s" % url try: ks = open(url, "r") except IOError, e: logging.exception(e) self._unmount_nfs_dir(nfsmntdir) return "Invalid NFS location given: %s" % e.args[1] ks.close() self._unmount_nfs_dir(nfsmntdir) return None def _unmount_nfs_dir(self, nfsmntdir): # and unmount cmd = ["umount", nfsmntdir] ret = subprocess.call(cmd) os.rmdir(nfsmntdir) def _get_optical_devices(self): # get a list of optical devices with data discs in, for FV installs optical_device_list = [] for d in self.hal_iface.FindDeviceByCapability("volume"): dev = self.bus.get_object("org.freedesktop.Hal", d) if dev.GetPropertyBoolean("volume.is_disc") and \ dev.GetPropertyBoolean("volume.disc.has_data") and \ dev.GetPropertyBoolean("volume.is_mounted"): optical_device_list.append(dev.GetProperty("volume.mount_point")) return optical_device_list