Solaris install support Add support for handling the Solaris "distro". Signed-off-by: John Danielson diff --git a/virt-install b/virt-install --- a/virt-install +++ b/virt-install @@ -1,4 +1,4 @@ -#!/usr/bin/python -tt +#!/bin/python -tt # # Script to set up a Xen guest and kick off an install # @@ -21,6 +21,7 @@ import logging import logging import libxml2 import urlgrabber.progress as progress +import platform import libvirt import virtinst @@ -44,9 +45,21 @@ def prompt_for_input(prompt = "", val = def check_xen(): - if not os.path.isdir("/proc/xen"): - print >> sys.stderr, "Can only install guests if running under a Xen kernel!" - sys.exit(1) + if platform.system() == "SunOS": + try: + sxc = open("/dev/xen/domcaps") + except IOError: + print >> sys.stderr, "Can only install guests if running under a Xen kernel!" + sys.exit(1) + ctlstring = sxc.read() + sxc.close() + if not ctlstring.startswith("control_d"): + print >> sys.stderr, "Can only install guests if running under a Xen kernel!" + sys.exit(1) + else: + if not os.path.isdir("/proc/xen"): + print >> sys.stderr, "Can only install guests if running under a Xen kernel!" + sys.exit(1) ### General input gathering functions def get_full_virt(): @@ -393,6 +406,9 @@ def parse_args(): # Misc options parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Print debugging information") + parser.add_option("", "--autocf", type="string", dest="autocf", default=None, + action="callback", callback=check_before_store, + help="kickstart/jumpstart path (nfs:host:/path)") (options,args) = parser.parse_args() @@ -435,14 +451,18 @@ def vnc_console(dom): if vncport == '-1' or vncport is None: print >> sys.stderr, "Unable to connect to graphical console; vnc port number not found." return None + if not os.environ.has_key("DISPLAY"): + print >> sys.stderr, "Unable to connect to graphical console; DISPLAY is not set. Please connect to %s:%d" %(vnchost, vncport) + return None vncport = int(vncport) vnchost = "localhost" + if platform.system() == "SunOS": + vncport = vncport - 5900 + vnchost = "127.0.0.1" if not os.path.exists("/usr/bin/vncviewer"): print >> sys.stderr, "Unable to connect to graphical console; vncviewer not installed. Please connect to %s:%d" %(vnchost, vncport) return None - if not os.environ.has_key("DISPLAY"): - print >> sys.stderr, "Unable to connect to graphical console; DISPLAY is not set. Please connect to %s:%d" %(vnchost, vncport) - return None + logging.debug("VNC Port: %d; VNC host: %s" % (vncport, vnchost)) child = os.fork() @@ -566,6 +586,13 @@ def main(): guest.set_os_variant(options.os_variant) continue_inst = guest.get_continue_inst() + if options.autocf is not None: + guest.autocf = options.autocf + + if options.autoconsole is False: + conscb = None + else: + conscb = show_console if options.autoconsole is False: conscb = None else: diff --git a/virtinst/DistroManager.py b/virtinst/DistroManager.py --- a/virtinst/DistroManager.py +++ b/virtinst/DistroManager.py @@ -14,7 +14,7 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import logging -import os +import os, sys import gzip import re import stat @@ -22,6 +22,9 @@ import urlgrabber.grabber as grabber import urlgrabber.grabber as grabber import urlgrabber.progress as progress import tempfile +import platform +from util import SunOS_loopmnt, SunOS_umount +import socket # This is a generic base class for fetching/extracting files from @@ -109,9 +112,16 @@ class MountedImageFetcher(ImageFetcher): cmd = ["mount", "-o", "ro", self.location[4:], self.mntdir] else: if stat.S_ISBLK(os.stat(self.location)[stat.ST_MODE]): - cmd = ["mount", "-o", "ro", self.location, self.mntdir] + if platform.system() == "SunOS": + cmd = ["mount", "-F", "hsfs", self.location, self.mntdir] + else: + cmd = ["mount", "-o", "ro", self.location, self.mntdir] else: - cmd = ["mount", "-o", "ro,loop", self.location, self.mntdir] + if platform.system() == "SunOS": + SunOS_loopmnt(self.location, self.mntdir) + return True + else: + cmd = ["mount", "-o", "ro,loop", self.location, self.mntdir] ret = subprocess.call(cmd) if ret != 0: self.cleanupLocation() @@ -122,6 +132,8 @@ class MountedImageFetcher(ImageFetcher): logging.debug("Cleaning up mount at " + self.mntdir) cmd = ["umount", self.mntdir] ret = subprocess.call(cmd) + if platform.system() == "SunOS": + SunOS_umount(self.mntdir) try: os.rmdir(self.mntdir) except: @@ -168,6 +180,7 @@ class ImageStore: def __init__(self, uri, type=None, scratchdir=None): self.uri = uri self.type = type + self.distroname = None self.scratchdir = scratchdir def acquireBootDisk(self, fetcher, progresscb): @@ -195,7 +208,7 @@ class RedHatImageStore(ImageStore): kernel = fetcher.acquireFile(kernelpath, progresscb) try: initrd = fetcher.acquireFile(initrdpath, progresscb) - return (kernel, initrd, "method=" + fetcher.location) + return (kernel, initrd, "method=" + fetcher.location, None) except: os.unlink(kernel) @@ -252,7 +265,7 @@ class SuseImageStore(ImageStore): installinitrdrpm = fetcher.acquireFile(installinitrdrpmname, progresscb) # Process the RPMs to extract the kernel & generate an initrd - return self.buildKernelInitrd(fetcher, kernelrpm, installinitrdrpm, progresscb) + return self.buildKernelInitrd(fetcher, kernelrpm, installinitrdrpm, progresscb, None) finally: if filelist is not None: os.unlink(filelist) @@ -417,7 +430,7 @@ class SuseImageStore(ImageStore): try: kernelname = fetcher.saveTemp(open(cpiodir + "/kernel/boot/vmlinuz-" + kernel_version, "r"), "vmlinuz") logging.debug("Saved " + kernelname) - return (kernelname, initrdname, "install=" + fetcher.location) + return (kernelname, initrdname, "install=" + fetcher.location, None) except: os.unlink(initrdname) finally: @@ -476,6 +489,55 @@ class DebianImageStore(ImageStore): # eg from http://ftp.egr.msu.edu/debian/dists/sarge/main/installer-i386/ return fetcher.acquireFile("current/images/netboot/mini.iso", progresscb) +# A Solaris distro contains support for 32-bit PAE-enabled and 64-bit +# platforms. +class SolarisImageStore(ImageStore): + + def acquireKernel(self, fetcher, progresscb): + + longmode = (sys.maxint != 2147483647L) + if not longmode: + longmode = os.uname()[4] == "x86_64" + if not longmode: + if (os.access("/usr/bin/isainfo", os.R_OK) and + os.popen("/usr/bin/isainfo -b").read() == "64\n"): + longmode = True + machstr = "" + if longmode: + machstr = "amd64/" + + kernelpath = "boot/platform/i86xpv/kernel/%sunix" % machstr + initrdpath = "boot/%sx86.miniroot" % machstr + # + # XXPV additional args processing in ParaVirtGuest.py + kernel = fetcher.acquireFile(kernelpath, progresscb) + kpl = kernelpath.split('/')[1:] + xargs = "/" + "/".join(kpl) + try: + initrd = fetcher.acquireFile(initrdpath, progresscb) + return (kernel, initrd, xargs, "solaris") + except: + os.unlink(kernel) + + def acquireBootDisk(self, fetcher, progresscb): + return fetcher.acquireFile("images/solarisdvd.iso", progresscb) + + def isValidStore(self, fetcher, progresscb): + + ignore = None + try: + try: + ignore = fetcher.acquireFile("boot/platform/i86xpv/kernel/unix", progresscb) + logging.debug("Detected a Solaris distro") + self.distroname = "solaris" + return True + except RuntimeError, e: + logging.debug("Doesn't look like a Solaris distro " + str(e)) + pass + finally: + if ignore is not None: + os.unlink(ignore) + return False class UbuntuImageStore(ImageStore): def isValidStore(self, fetcher, progresscb): @@ -548,6 +610,8 @@ def _storeForDistro(fetcher, baseuri, ty stores.append(GentooImageStore(baseuri, type, scratchdir)) if distro == "mandriva" or distro is None: stores.append(MandrivaImageStore(baseuri, type, scratchdir)) + if distro == "solaris" or distro is None: + stores.append(SolarisImageStore(baseuri, type, scratchdir)) for store in stores: if store.isValidStore(fetcher, progresscb): @@ -581,4 +645,3 @@ def acquireBootDisk(baseuri, progresscb, return store.acquireBootDisk(fetcher, progresscb) finally: fetcher.cleanupLocation() - diff --git a/virtinst/FullVirtGuest.py b/virtinst/FullVirtGuest.py --- a/virtinst/FullVirtGuest.py +++ b/virtinst/FullVirtGuest.py @@ -19,6 +19,7 @@ import DistroManager import DistroManager import logging import time +import platform class FullVirtGuest(Guest.XenGuest): @@ -206,10 +207,15 @@ class FullVirtGuest(Guest.XenGuest): return """ """ + Guest.Guest._get_device_xml(self, install) else: - return (""" %(emulator)s - + if platform.system() == "SunOS": + return (""" %(emulator)s """ % { "emulator": self.emulator }) + \ - Guest.Guest._get_device_xml(self, install) + Guest.Guest._get_device_xml(self, install) + else: + return (""" %(emulator)s + + """ % { "emulator": self.emulator }) + \ + Guest.Guest._get_device_xml(self, install) def validate_parms(self): if not self.location: @@ -281,3 +287,23 @@ class FullVirtGuest(Guest.XenGuest): # This should always work, because it'll lookup a config file # for inactive guest, or get the still running install.. return self.conn.lookupByName(self.name) + + def _get_disk_xml(self, install = True): + """Get the disk config in the libvirt XML format""" + ret = "" + count = 0 + for d in self.disks: + if d.transient and not install: + continue + if count > 4: + raise ValueError, "Can't use more than 4 disks on HVM guest" + if d.device == Guest.VirtualDisk.DEVICE_CDROM and count != 2: + disknode = "hdc" + else: + if count == 2 and d.device != Guest.VirtualDisk.DEVICE_CDROM: + # skip "hdc" + count += 1 + disknode = "hd%(dev)c" % { "dev": ord('a') + count } + ret += d.get_xml_config(disknode) + count += 1 + return ret diff --git a/virtinst/Guest.py b/virtinst/Guest.py --- a/virtinst/Guest.py +++ b/virtinst/Guest.py @@ -101,7 +101,7 @@ class VirtualDisk: try: fd = os.open(self.path, os.O_WRONLY | os.O_CREAT) if self.sparse: - os.lseek(fd, size_bytes, 0) + os.lseek(fd, size_bytes - 1, 0) os.write(fd, '\x00') progresscb.update(self.size) else: @@ -343,6 +343,7 @@ class Guest(object): self.disks = [] self.nics = [] self._location = None + self._autocf = None self._name = None self._uuid = None self._memory = None @@ -489,6 +490,16 @@ class Guest(object): raise ValueError, "NFS installations are only supported as root" self._location = val location = property(get_install_location, set_install_location) + + # location of auto-install config data (kickstart/jumpstart) for PV + # guests + def get_autocf(self): + return self._autocf + def set_autocf(self, val): + if not val.startswith("nfs:"): + raise ValueError, "Jumpstart location must be NFS" + self._autocf = val + autocf = property(get_autocf, set_autocf) # Legacy, deprecated @@ -555,26 +566,6 @@ class Guest(object): disk.setup(progresscb) for nic in self.nics: nic.setup(self.conn) - - def _get_disk_xml(self, install = True): - """Get the disk config in the libvirt XML format""" - ret = "" - count = 0 - for d in self.disks: - if d.transient and not install: - continue - if count > 4 and self.disknode == "hd": - raise ValueError, "Can't use more than 4 disks on HVM guest" - if d.device == VirtualDisk.DEVICE_CDROM and count != 2: - disknode = "%(disknode)s%(dev)c" % { "disknode": self.disknode, "dev": ord('a') + 2 } - else: - if count == 2 and d.device != VirtualDisk.DEVICE_CDROM and self.disknode == "hd": - # skip "hdc" - count += 1 - disknode = "%(disknode)s%(dev)c" % { "disknode": self.disknode, "dev": ord('a') + count } - ret += d.get_xml_config(disknode) - count += 1 - return ret def _get_network_xml(self, install = True): """Get the network config in the libvirt XML format""" diff --git a/virtinst/ParaVirtGuest.py b/virtinst/ParaVirtGuest.py --- a/virtinst/ParaVirtGuest.py +++ b/virtinst/ParaVirtGuest.py @@ -16,13 +16,25 @@ import libvirt import libvirt import Guest import DistroManager +import platform +import socket +import string +from util import plat_bootloader +from util import SunOS_installxargs class ParaVirtGuest(Guest.XenGuest): def __init__(self, type=None, connection=None, hypervisorURI=None): Guest.Guest.__init__(self, type=type, connection=connection, hypervisorURI=hypervisorURI) self.disknode = "xvd" + self._os_type = None def _get_install_xml(self): + + if self._os_type is 'solaris': + bargs = SunOS_installxargs(self) + else: + bargs = self.extraargs + return """ linux %(kernel)s @@ -31,11 +43,12 @@ class ParaVirtGuest(Guest.XenGuest): """ % \ { "kernel": self.kernel, \ "initrd": self.initrd, \ - "extra": self.extraargs } - + "extra": bargs } def _get_runtime_xml(self): - return """/usr/bin/pygrub""" + return """ + %s + """ % plat_bootloader() def _connectSerialConsole(self): # *sigh* would be nice to have a python version of xmconsole @@ -62,9 +75,10 @@ class ParaVirtGuest(Guest.XenGuest): else: # Need to fetch the kernel & initrd from a remote site, or # out of a loopback mounted disk image/device - (kernelfn,initrdfn,args) = DistroManager.acquireKernel(self.location, meter, scratchdir=self.scratchdir, type=self.type) + (kernelfn,initrdfn,args,distro) = DistroManager.acquireKernel(self.location, meter, scratchdir=self.scratchdir, type=self.type) self.kernel = kernelfn self.initrd = initrdfn + self._os_type = distro if self.extraargs is not None: self.extraargs = self.extraargs + " " + args else: @@ -75,6 +89,37 @@ class ParaVirtGuest(Guest.XenGuest): # If they're installing off a local file/device, we map it # through to a virtual harddisk if self.location is not None and self.location.startswith("/"): - self.disks.append(Guest.VirtualDisk(self.location, readOnly=True, transient=True)) + if distro is "solaris": + self.disks.append(Guest.VirtualDisk(self.location, device=Guest.VirtualDisk.DEVICE_CDROM, readOnly=True, transient=True)) + else: + self.disks.append(Guest.VirtualDisk(self.location, readOnly=True, transient=True)) + return tmpfiles - return tmpfiles + def _get_disk_xml(self, install = True): + """Get the disk config in the libvirt XML format""" + if self._os_type is 'solaris': + ret = "" + count = 0 + for d in self.disks: + if d.transient and not install: + continue + if d.device == Guest.VirtualDisk.DEVICE_CDROM: + disknode = "%d:cdrom" % count + else: + disknode = "%d" % count + ret += d.get_xml_config(disknode) + count += 1 + return ret + + ret = "" + count = 0 + for d in self.disks: + if d.transient and not install: + continue + if d.device == Guest.VirtualDisk.DEVICE_CDROM: + disknode = "xvd%(dev)c:cdrom" % { "dev": ord('a') + count } + else: + disknode = "xvd%(dev)c" % { "dev": ord('a') + count } + ret += d.get_xml_config(disknode) + count += 1 + return ret diff --git a/virtinst/util.py b/virtinst/util.py --- a/virtinst/util.py +++ b/virtinst/util.py @@ -15,8 +15,20 @@ import random import random import os.path from sys import stderr +import platform +import socket +import string +from subprocess import Popen, PIPE, call +import logging +import libvirt + +SunOS_loopmnts = {} def default_bridge(): + + if platform.system() == "SunOS": + return "" + route_file = "/proc/net/route" d = file(route_file) @@ -38,6 +50,9 @@ def default_bridge(): return "xenbr%d"%(defn) def get_cpu_flags(): + if platform.system() == "SunOS": + return [] + f = open("/proc/cpuinfo") lines = f.readlines() f.close() @@ -53,28 +68,46 @@ def get_cpu_flags(): def is_pae_capable(): """Determine if a machine is PAE capable or not.""" - flags = get_cpu_flags() - if "pae" in flags: - return True + + if platform.system() == 'SunOS': + conn = libvirt.open('xen') + caps = conn.getCapabilities() + if 'pae' in caps: + return True + else: + flags = get_cpu_flags() + if "pae" in flags: + return True return False def is_hvm_capable(): """Determine if a machine is HVM capable or not.""" - - caps = "" - if os.path.exists("/sys/hypervisor/properties/capabilities"): - caps = open("/sys/hypervisor/properties/capabilities").read() - if caps.find("hvm") != -1: - return True + if platform.system() == 'SunOS': + conn = libvirt.open('xen') + caps = conn.getCapabilities() + if 'vmx' in caps or 'svm' in caps: + return True + else: + caps = "" + if os.path.exists("/sys/hypervisor/properties/capabilities"): + caps = open("/sys/hypervisor/properties/capabilities").read() + if caps.find("hvm") != -1: + return True return False def is_kqemu_capable(): + if platform.system() == "SunOS": + return False return os.path.exists("/dev/kqemu") def is_kvm_capable(): + if platform.system() == "SunOS": + return False return os.path.exists("/dev/kvm") def is_blktap_capable(): + if platform.system() == "SunOS": + return False #return os.path.exists("/dev/xen/blktapctrl") f = open("/proc/modules") lines = f.readlines() @@ -84,6 +117,11 @@ def is_blktap_capable(): return True return False +def plat_bootloader(): + if platform.system() == "SunOS": + return "/usr/lib/xen/bin/pygrub" + else: + return "/usr/bin/pygrub" # this function is directly from xend/server/netif.py and is thus # available under the LGPL, @@ -142,3 +180,118 @@ def get_host_network_devices(): if words[i] == "hwaddr": device.append(words) return device + +# platform-specific utility functions +# +# mount(1M) in Solaris does not support the -o loop option. +# It uses lofiadm(1M) to create a block device layered on the +# 'filename', which which is then mounted at 'mountloc' +def SunOS_loopmnt(filename, mountloc): + + if not platform.system() == "SunOS": + return + + global SunOS_loopmnts + + logging.debug("loopmnt file " + filename) + logging.debug("mount at " + mountloc) + + if not os.path.exists(filename): + logging.debug(filename + " does not exist.") + return False + + if not os.path.getsize(filename) % 512 == 0: + logging.debug(filename + " size not a multiple of 512.") + return False + + if not os.path.isdir(mountloc): + logging.debug(mountloc + " does not exist or is not a directory.") + return False + + loficmd = "/usr/sbin/lofiadm" + try: + lofidev = Popen([loficmd, "-a", filename], stdout=PIPE).communicate()[0] + if lofidev == '': + logging.debug("lofiadm failed to open " + filename) + return False + + except OSError: + logging.debug("Failed to open " + filename) + return False + + try: + logging.debug("mounting " + lofidev[:-1] + mountloc) + status = call("/usr/sbin/mount -F hsfs -o ro " + lofidev[:-1] + " " + mountloc, shell=True) + if status < 0: + print "killed by signal" + + except OSError, e: + logging.debug("Failed to mount " + mountloc + " " + str(e)) + Popen([loficmd, "-d", lofidev]) + return False + + SunOS_loopmnts[mountloc] = lofidev[:-1] + return True + +def SunOS_umount(mountloc): + + global SunOS_loopmnts + + try: + lofidev = SunOS_loopmnts[mountloc] + except: + return + + try: + status = call("/usr/sbin/lofiadm -d " + lofidev, shell=True) + + except OSError: + logging.debug("lofiadm failed to unmount " + lofidev) + +def SunOS_installxargs(guest): + """ + Construct kernel cmdline args for the installer, consising of: + The pathname of the kernel (32/64) to load, console options, + install mechanism, and '-B' boot properties. + """ + + all_xargs = guest.extraargs.split(' ') + bargs = all_xargs[-1] + " -" + xcmd = ' '.join(all_xargs[0:-1]) + + if guest.graphics["enabled"] == False: + bargs += " nowin" + + if guest.autocf: + bargs += " install" + + bargs += " -B" + if guest.location.startswith("nfs:"): + ihost = guest.location.split(':')[1] + ipath = guest.location.split(':')[2] + ihostip = socket.gethostbyaddr(ihost)[2][0] + bargs += " install_media=" + ihostip + ":" + ipath + guest_ip = socket.gethostbyaddr(guest.name)[2][0] + bargs += ",host-ip=" + guest_ip + en = guest.nics[0].macaddr.split(':') + for i in range(len(en)): + if en[i][0] == '0': + en[i] = en[i][1] + boot_mac = ":".join(en) + bargs += ",boot-mac=" + boot_mac + for i in os.popen('/usr/bin/netstat -rn'): + line = i.split() + if len(line) > 0 and line[0] == 'default': + bargs += ",router-ip=" + line[1] + if guest.autocf: + acf_host = guest.autocf.split(":")[1] + acf_path = guest.autocf.split(":")[2] + acf_hostip = socket.gethostbyaddr(acf_host)[2][0] + bargs += ",sysid_config=" + acf_hostip + ":" + acf_path + bargs += ",install_config=" + acf_hostip + ":" + acf_path + else: + bargs += " install_media=cdrom" + + if xcmd: + bargs += "," + xcmd + return bargs