1235 lines
39 KiB
Python
1235 lines
39 KiB
Python
#
|
|
# Represents OS distribution specific install data
|
|
#
|
|
# Copyright 2006-2007, 2013 Red Hat, Inc.
|
|
# Daniel P. Berrange <berrange@redhat.com>
|
|
#
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston,
|
|
# MA 02110-1301 USA.
|
|
|
|
import ConfigParser
|
|
import ftplib
|
|
import logging
|
|
import os
|
|
import re
|
|
import socket
|
|
import stat
|
|
import subprocess
|
|
import tempfile
|
|
import urllib2
|
|
import urlparse
|
|
|
|
import urlgrabber.grabber as grabber
|
|
|
|
from virtinst import osdict
|
|
from virtinst import util
|
|
|
|
|
|
def safeint(c):
|
|
try:
|
|
val = int(c)
|
|
except:
|
|
val = 0
|
|
return val
|
|
|
|
|
|
class _ImageFetcher(object):
|
|
"""
|
|
This is a generic base class for fetching/extracting files from
|
|
a media source, such as CD ISO, NFS server, or HTTP/FTP server
|
|
"""
|
|
def __init__(self, location, scratchdir):
|
|
self.location = location
|
|
self.scratchdir = scratchdir
|
|
|
|
def _make_path(self, filename):
|
|
if hasattr(self, "srcdir"):
|
|
path = getattr(self, "srcdir")
|
|
else:
|
|
path = self.location
|
|
|
|
if filename:
|
|
if not path.endswith("/"):
|
|
path += "/"
|
|
path += filename
|
|
|
|
return path
|
|
|
|
def saveTemp(self, fileobj, prefix):
|
|
if not os.path.exists(self.scratchdir):
|
|
os.makedirs(self.scratchdir, 0750)
|
|
(fd, fn) = tempfile.mkstemp(prefix="virtinst-" + prefix,
|
|
dir=self.scratchdir)
|
|
block_size = 16384
|
|
try:
|
|
while 1:
|
|
buff = fileobj.read(block_size)
|
|
if not buff:
|
|
break
|
|
os.write(fd, buff)
|
|
finally:
|
|
os.close(fd)
|
|
return fn
|
|
|
|
def prepareLocation(self):
|
|
return True
|
|
|
|
def cleanupLocation(self):
|
|
pass
|
|
|
|
def acquireFile(self, filename, progresscb):
|
|
# URLGrabber works for all network and local cases
|
|
|
|
f = None
|
|
try:
|
|
path = self._make_path(filename)
|
|
base = os.path.basename(filename)
|
|
logging.debug("Fetching URI: %s", path)
|
|
|
|
try:
|
|
f = grabber.urlopen(path,
|
|
progress_obj=progresscb,
|
|
text=_("Retrieving file %s...") % base)
|
|
except Exception, e:
|
|
raise ValueError(_("Couldn't acquire file %s: %s") %
|
|
(path, str(e)))
|
|
|
|
tmpname = self.saveTemp(f, prefix=base + ".")
|
|
logging.debug("Saved file to " + tmpname)
|
|
return tmpname
|
|
finally:
|
|
if f:
|
|
f.close()
|
|
|
|
|
|
def hasFile(self, src):
|
|
raise NotImplementedError("Must be implemented in subclass")
|
|
|
|
|
|
class _URIImageFetcher(_ImageFetcher):
|
|
"""
|
|
Base class for downloading from FTP / HTTP
|
|
"""
|
|
def hasFile(self, filename):
|
|
raise NotImplementedError
|
|
|
|
def prepareLocation(self):
|
|
if not self.hasFile(""):
|
|
raise ValueError(_("Opening URL %s failed.") %
|
|
(self.location))
|
|
|
|
|
|
class _HTTPImageFetcher(_URIImageFetcher):
|
|
def hasFile(self, filename):
|
|
try:
|
|
path = self._make_path(filename)
|
|
request = urllib2.Request(path)
|
|
request.get_method = lambda: "HEAD"
|
|
urllib2.urlopen(request)
|
|
except Exception, e:
|
|
logging.debug("HTTP hasFile: didn't find %s: %s", path, str(e))
|
|
return False
|
|
return True
|
|
|
|
|
|
class _FTPImageFetcher(_URIImageFetcher):
|
|
def __init__(self, location, scratchdir):
|
|
_URIImageFetcher.__init__(self, location, scratchdir)
|
|
|
|
self.ftp = None
|
|
|
|
def prepareLocation(self):
|
|
url = urlparse.urlparse(self._make_path(""))
|
|
self.ftp = ftplib.FTP(url[1])
|
|
self.ftp.login()
|
|
|
|
def hasFile(self, filename):
|
|
path = self._make_path(filename)
|
|
url = urlparse.urlparse(path)
|
|
|
|
try:
|
|
try:
|
|
# If it's a file
|
|
self.ftp.size(url[2])
|
|
except ftplib.all_errors:
|
|
# If it's a dir
|
|
self.ftp.cwd(url[2])
|
|
except ftplib.all_errors, e:
|
|
logging.debug("FTP hasFile: couldn't access %s: %s",
|
|
path, str(e))
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
class _LocalImageFetcher(_ImageFetcher):
|
|
def __init__(self, location, scratchdir, srcdir=None):
|
|
_ImageFetcher.__init__(self, location, scratchdir)
|
|
self.srcdir = srcdir
|
|
|
|
def hasFile(self, filename):
|
|
src = self._make_path(filename)
|
|
if os.path.exists(src):
|
|
return True
|
|
else:
|
|
logging.debug("local hasFile: Couldn't find %s", src)
|
|
return False
|
|
|
|
|
|
class _MountedImageFetcher(_LocalImageFetcher):
|
|
"""
|
|
Fetcher capable of extracting files from a NFS server
|
|
or loopback mounted file, or local CDROM device
|
|
"""
|
|
def prepareLocation(self):
|
|
cmd = None
|
|
self.srcdir = tempfile.mkdtemp(prefix="virtinstmnt.",
|
|
dir=self.scratchdir)
|
|
mountcmd = "/bin/mount"
|
|
|
|
logging.debug("Preparing mount at " + self.srcdir)
|
|
if self.location.startswith("nfs:"):
|
|
cmd = [mountcmd, "-o", "ro", self.location[4:], self.srcdir]
|
|
else:
|
|
if stat.S_ISBLK(os.stat(self.location)[stat.ST_MODE]):
|
|
mountopt = "ro"
|
|
else:
|
|
mountopt = "ro,loop"
|
|
cmd = [mountcmd, "-o", mountopt, self.location, self.srcdir]
|
|
ret = subprocess.call(cmd)
|
|
if ret != 0:
|
|
self.cleanupLocation()
|
|
raise ValueError(_("Mounting location '%s' failed") %
|
|
(self.location))
|
|
return True
|
|
|
|
def cleanupLocation(self):
|
|
logging.debug("Cleaning up mount at " + self.srcdir)
|
|
cmd = ["/bin/umount", self.srcdir]
|
|
subprocess.call(cmd)
|
|
try:
|
|
os.rmdir(self.srcdir)
|
|
except:
|
|
pass
|
|
|
|
|
|
class _DirectImageFetcher(_LocalImageFetcher):
|
|
def prepareLocation(self):
|
|
self.srcdir = self.location
|
|
|
|
|
|
def _fetcherForURI(uri, scratchdir=None):
|
|
if uri.startswith("http://"):
|
|
fclass = _HTTPImageFetcher
|
|
elif uri.startswith("ftp://"):
|
|
fclass = _FTPImageFetcher
|
|
elif uri.startswith("nfs://"):
|
|
fclass = _MountedImageFetcher
|
|
else:
|
|
if os.path.isdir(uri):
|
|
fclass = _DirectImageFetcher
|
|
else:
|
|
fclass = _MountedImageFetcher
|
|
return fclass(uri, scratchdir)
|
|
|
|
|
|
def _storeForDistro(fetcher, baseuri, typ, progresscb, arch, distro=None,
|
|
scratchdir=None):
|
|
stores = []
|
|
skip_treeinfo = False
|
|
logging.debug("Attempting to detect distro:")
|
|
|
|
dist = distroFromTreeinfo(fetcher, progresscb, baseuri,
|
|
arch, typ, scratchdir)
|
|
if dist:
|
|
return dist
|
|
skip_treeinfo = True
|
|
|
|
# FIXME: This 'distro ==' doesn't cut it. 'distro' is from our os
|
|
# dictionary, so would look like 'fedora9' or 'rhel5', so this needs
|
|
# to be a bit more intelligent
|
|
if distro == "fedora" or distro is None:
|
|
stores.append(FedoraDistro)
|
|
if distro == "rhel" or distro is None:
|
|
stores.append(RHELDistro)
|
|
if distro == "centos" or distro is None:
|
|
stores.append(CentOSDistro)
|
|
if distro == "sl" or distro is None:
|
|
stores.append(SLDistro)
|
|
if distro == "suse" or distro is None:
|
|
stores.append(SuseDistro)
|
|
if distro == "debian" or distro is None:
|
|
stores.append(DebianDistro)
|
|
if distro == "ubuntu" or distro is None:
|
|
stores.append(UbuntuDistro)
|
|
if distro == "mandriva" or distro is None:
|
|
stores.append(MandrivaDistro)
|
|
if distro == "mageia" or distro is None:
|
|
stores.append(MageiaDistro)
|
|
if distro == "altlinux" or distro is None:
|
|
stores.append(ALTLinuxDistro)
|
|
if distro == "solaris" or distro is None:
|
|
stores.append(SolarisDistro)
|
|
if distro == "solaris" or distro is None:
|
|
stores.append(OpenSolarisDistro)
|
|
if distro == "netware" or distro is None:
|
|
stores.append(NetWareDistro)
|
|
|
|
stores.append(GenericDistro)
|
|
|
|
for sclass in stores:
|
|
store = sclass(baseuri, arch, typ, scratchdir)
|
|
if skip_treeinfo:
|
|
store.uses_treeinfo = False
|
|
if store.isValidStore(fetcher, progresscb):
|
|
return store
|
|
|
|
raise ValueError(
|
|
_("Could not find an installable distribution at '%s'\n"
|
|
"The location must be the root directory of an install tree." %
|
|
baseuri))
|
|
|
|
|
|
def _locationCheckWrapper(guest, baseuri, progresscb,
|
|
scratchdir, _type, arch, callback):
|
|
fetcher = _fetcherForURI(baseuri, scratchdir)
|
|
if guest:
|
|
arch = guest.os.arch
|
|
|
|
try:
|
|
fetcher.prepareLocation()
|
|
except ValueError, e:
|
|
logging.exception("Error preparing install location")
|
|
raise ValueError(_("Invalid install location: ") + str(e))
|
|
|
|
try:
|
|
store = _storeForDistro(fetcher=fetcher, baseuri=baseuri, typ=_type,
|
|
progresscb=progresscb, scratchdir=scratchdir,
|
|
arch=arch)
|
|
|
|
return callback(store, fetcher)
|
|
finally:
|
|
fetcher.cleanupLocation()
|
|
|
|
|
|
def _acquireMedia(iskernel, guest, baseuri, progresscb,
|
|
scratchdir="/var/tmp", _type=None):
|
|
|
|
def media_cb(store, fetcher):
|
|
os_variant = store.get_osdict_info()
|
|
media = None
|
|
|
|
if iskernel:
|
|
media = store.acquireKernel(guest, fetcher, progresscb)
|
|
else:
|
|
media = store.acquireBootDisk(guest, fetcher, progresscb)
|
|
|
|
return [store, os_variant, media]
|
|
|
|
return _locationCheckWrapper(guest, baseuri, progresscb, scratchdir, _type,
|
|
None, media_cb)
|
|
|
|
|
|
# Helper method to lookup install media distro and fetch an install kernel
|
|
def getKernel(guest, baseuri, progresscb, scratchdir, typ):
|
|
iskernel = True
|
|
return _acquireMedia(iskernel, guest, baseuri, progresscb,
|
|
scratchdir, typ)
|
|
|
|
|
|
# Helper method to lookup install media distro and fetch a boot iso
|
|
def getBootDisk(guest, baseuri, progresscb, scratchdir):
|
|
iskernel = False
|
|
return _acquireMedia(iskernel, guest, baseuri, progresscb,
|
|
scratchdir)
|
|
|
|
|
|
def _check_osvariant_valid(os_variant):
|
|
return osdict.lookup_os(os_variant) is not None
|
|
|
|
|
|
# Attempt to detect the os type + variant for the passed location
|
|
def detectMediaDistro(location, arch):
|
|
import urlgrabber
|
|
progresscb = urlgrabber.progress.BaseMeter()
|
|
guest = None
|
|
baseuri = location
|
|
scratchdir = "/var/tmp"
|
|
_type = None
|
|
def media_cb(store, ignore):
|
|
return store
|
|
|
|
store = _locationCheckWrapper(guest, baseuri, progresscb, scratchdir,
|
|
_type, arch, media_cb)
|
|
|
|
return store.get_osdict_info()
|
|
|
|
|
|
def distroFromTreeinfo(fetcher, progresscb, uri, arch, vmtype=None,
|
|
scratchdir=None):
|
|
# Parse treeinfo 'family' field, and return the associated Distro class
|
|
# None if no treeinfo, GenericDistro if unknown family type.
|
|
if not fetcher.hasFile(".treeinfo"):
|
|
return None
|
|
|
|
tmptreeinfo = fetcher.acquireFile(".treeinfo", progresscb)
|
|
try:
|
|
treeinfo = ConfigParser.SafeConfigParser()
|
|
treeinfo.read(tmptreeinfo)
|
|
finally:
|
|
os.unlink(tmptreeinfo)
|
|
|
|
try:
|
|
fam = treeinfo.get("general", "family")
|
|
except ConfigParser.NoSectionError:
|
|
return None
|
|
|
|
if re.match(".*Fedora.*", fam):
|
|
dclass = FedoraDistro
|
|
elif re.match(".*CentOS.*", fam):
|
|
dclass = CentOSDistro
|
|
elif re.match(".*Red Hat Enterprise Linux.*", fam):
|
|
dclass = RHELDistro
|
|
elif re.match(".*Scientific Linux.*", fam):
|
|
dclass = SLDistro
|
|
else:
|
|
dclass = GenericDistro
|
|
|
|
ob = dclass(uri, arch, vmtype, scratchdir)
|
|
ob.treeinfo = treeinfo
|
|
|
|
# Explictly call this, so we populate variant info
|
|
ob.isValidStore(fetcher, progresscb)
|
|
|
|
return ob
|
|
|
|
|
|
class Distro(object):
|
|
"""
|
|
An image store is a base class for retrieving either a bootable
|
|
ISO image, or a kernel+initrd pair for a particular OS distribution
|
|
"""
|
|
name = ""
|
|
|
|
# osdict type and variant values
|
|
os_variant = None
|
|
|
|
_boot_iso_paths = []
|
|
_hvm_kernel_paths = []
|
|
_xen_kernel_paths = []
|
|
uses_treeinfo = False
|
|
method_arg = "method"
|
|
|
|
def __init__(self, uri, arch, vmtype=None, scratchdir=None):
|
|
self.uri = uri
|
|
self.type = vmtype
|
|
self.scratchdir = scratchdir
|
|
self.arch = arch
|
|
self.treeinfo = None
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
"""Determine if uri points to a tree of the store's distro"""
|
|
raise NotImplementedError
|
|
|
|
def acquireKernel(self, guest, fetcher, progresscb):
|
|
kernelpath = None
|
|
initrdpath = None
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
try:
|
|
kernelpath = self._getTreeinfoMedia("kernel")
|
|
initrdpath = self._getTreeinfoMedia("initrd")
|
|
except ConfigParser.NoSectionError:
|
|
pass
|
|
|
|
if not kernelpath or not initrdpath:
|
|
# fall back to old code
|
|
if self.type is None or self.type == "hvm":
|
|
paths = self._hvm_kernel_paths
|
|
else:
|
|
paths = self._xen_kernel_paths
|
|
|
|
for kpath, ipath in paths:
|
|
if fetcher.hasFile(kpath) and fetcher.hasFile(ipath):
|
|
kernelpath = kpath
|
|
initrdpath = ipath
|
|
|
|
if not kernelpath or not initrdpath:
|
|
raise RuntimeError(_("Couldn't find %(type)s kernel for "
|
|
"%(distro)s tree.") %
|
|
{"distro": self.name, "type" : self.type})
|
|
|
|
return self._kernelFetchHelper(fetcher, guest, progresscb, kernelpath,
|
|
initrdpath)
|
|
|
|
def acquireBootDisk(self, guest, fetcher, progresscb):
|
|
ignore = guest
|
|
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
return fetcher.acquireFile(self._getTreeinfoMedia("boot.iso"),
|
|
progresscb)
|
|
else:
|
|
for path in self._boot_iso_paths:
|
|
if fetcher.hasFile(path):
|
|
return fetcher.acquireFile(path, progresscb)
|
|
raise RuntimeError(_("Could not find boot.iso in %s tree." %
|
|
self.name))
|
|
|
|
def get_osdict_info(self):
|
|
"""
|
|
Return (distro, variant) tuple, checking to make sure they are valid
|
|
osdict entries
|
|
"""
|
|
if not self.os_variant:
|
|
return None
|
|
|
|
if not _check_osvariant_valid(self.os_variant):
|
|
logging.debug("%s set os_variant to %s, which is not in osdict.",
|
|
self, self.os_variant)
|
|
return None
|
|
|
|
return self.os_variant
|
|
|
|
def _hasTreeinfo(self, fetcher, progresscb):
|
|
# all Red Hat based distros should have .treeinfo, perhaps others
|
|
# will in time
|
|
if not (self.treeinfo is None):
|
|
return True
|
|
|
|
if not self.uses_treeinfo or not fetcher.hasFile(".treeinfo"):
|
|
return False
|
|
|
|
logging.debug("Detected .treeinfo file")
|
|
|
|
tmptreeinfo = fetcher.acquireFile(".treeinfo", progresscb)
|
|
try:
|
|
self.treeinfo = ConfigParser.SafeConfigParser()
|
|
self.treeinfo.read(tmptreeinfo)
|
|
finally:
|
|
os.unlink(tmptreeinfo)
|
|
return True
|
|
|
|
def _getTreeinfoMedia(self, mediaName):
|
|
if self.type == "xen":
|
|
t = "xen"
|
|
else:
|
|
t = self.treeinfo.get("general", "arch")
|
|
|
|
return self.treeinfo.get("images-%s" % t, mediaName)
|
|
|
|
def _fetchAndMatchRegex(self, fetcher, progresscb, filename, regex):
|
|
# Fetch 'filename' and return True/False if it matches the regex
|
|
local_file = None
|
|
try:
|
|
try:
|
|
local_file = fetcher.acquireFile(filename, progresscb)
|
|
except:
|
|
return False
|
|
|
|
f = open(local_file, "r")
|
|
try:
|
|
while 1:
|
|
buf = f.readline()
|
|
if not buf:
|
|
break
|
|
if re.match(regex, buf):
|
|
return True
|
|
finally:
|
|
f.close()
|
|
finally:
|
|
if local_file is not None:
|
|
os.unlink(local_file)
|
|
|
|
return False
|
|
|
|
def _kernelFetchHelper(self, fetcher, guest, progresscb, kernelpath,
|
|
initrdpath):
|
|
# Simple helper for fetching kernel + initrd and performing
|
|
# cleanup if necessary
|
|
kernel = fetcher.acquireFile(kernelpath, progresscb)
|
|
args = ''
|
|
|
|
if not fetcher.location.startswith("/"):
|
|
args += "%s=%s" % (self.method_arg, fetcher.location)
|
|
|
|
if guest.installer.extraargs:
|
|
args += " " + guest.installer.extraargs
|
|
|
|
try:
|
|
initrd = fetcher.acquireFile(initrdpath, progresscb)
|
|
return kernel, initrd, args
|
|
except:
|
|
os.unlink(kernel)
|
|
|
|
|
|
class GenericDistro(Distro):
|
|
"""
|
|
Generic distro store. Check well known paths for kernel locations
|
|
as a last resort if we can't recognize any actual distro
|
|
"""
|
|
|
|
name = "Generic"
|
|
os_variant = "linux"
|
|
uses_treeinfo = True
|
|
|
|
_xen_paths = [("images/xen/vmlinuz",
|
|
"images/xen/initrd.img"), # Fedora
|
|
]
|
|
_hvm_paths = [("images/pxeboot/vmlinuz",
|
|
"images/pxeboot/initrd.img"), # Fedora
|
|
]
|
|
_iso_paths = ["images/boot.iso", # RH/Fedora
|
|
"boot/boot.iso", # Suse
|
|
"current/images/netboot/mini.iso", # Debian
|
|
"install/images/boot.iso", # Mandriva
|
|
]
|
|
|
|
# Holds values to use when actually pulling down media
|
|
_valid_kernel_path = None
|
|
_valid_iso_path = None
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
# Use treeinfo to pull down media paths
|
|
if self.type == "xen":
|
|
typ = "xen"
|
|
else:
|
|
typ = self.treeinfo.get("general", "arch")
|
|
kernelSection = "images-%s" % typ
|
|
isoSection = "images-%s" % self.treeinfo.get("general", "arch")
|
|
|
|
if self.treeinfo.has_section(kernelSection):
|
|
self._valid_kernel_path = (self._getTreeinfoMedia("kernel"),
|
|
self._getTreeinfoMedia("initrd"))
|
|
if self.treeinfo.has_section(isoSection):
|
|
self._valid_iso_path = self.treeinfo.get(isoSection, "boot.iso")
|
|
|
|
if self.type == "xen":
|
|
kern_list = self._xen_paths
|
|
else:
|
|
kern_list = self._hvm_paths
|
|
|
|
# If validated media paths weren't found (no treeinfo), check against
|
|
# list of media location paths.
|
|
for kern, init in kern_list:
|
|
if self._valid_kernel_path is None \
|
|
and fetcher.hasFile(kern) and fetcher.hasFile(init):
|
|
self._valid_kernel_path = (kern, init)
|
|
break
|
|
for iso in self._iso_paths:
|
|
if self._valid_iso_path is None \
|
|
and fetcher.hasFile(iso):
|
|
self._valid_iso_path = iso
|
|
break
|
|
|
|
if self._valid_kernel_path or self._valid_iso_path:
|
|
return True
|
|
return False
|
|
|
|
def acquireKernel(self, guest, fetcher, progresscb):
|
|
if self._valid_kernel_path is None:
|
|
raise ValueError(_("Could not find a kernel path for virt type "
|
|
"'%s'" % self.type))
|
|
|
|
return self._kernelFetchHelper(fetcher, guest, progresscb,
|
|
self._valid_kernel_path[0],
|
|
self._valid_kernel_path[1])
|
|
|
|
def acquireBootDisk(self, guest, fetcher, progresscb):
|
|
if self._valid_iso_path is None:
|
|
raise ValueError(_("Could not find a boot iso path for this tree."))
|
|
|
|
return fetcher.acquireFile(self._valid_iso_path, progresscb)
|
|
|
|
|
|
class RedHatDistro(Distro):
|
|
"""
|
|
Base image store for any Red Hat related distros which have
|
|
a common layout
|
|
"""
|
|
name = "Red Hat"
|
|
os_variant = "linux"
|
|
|
|
uses_treeinfo = True
|
|
_boot_iso_paths = ["images/boot.iso"]
|
|
_hvm_kernel_paths = [("images/pxeboot/vmlinuz",
|
|
"images/pxeboot/initrd.img")]
|
|
_xen_kernel_paths = [("images/xen/vmlinuz",
|
|
"images/xen/initrd.img")]
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
raise NotImplementedError
|
|
|
|
|
|
# Fedora distro check
|
|
class FedoraDistro(RedHatDistro):
|
|
name = "Fedora"
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
m = re.match(".*Fedora.*", self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
|
|
if ret:
|
|
lateststr, latestnum = self._latestFedoraVariant()
|
|
ver = self.treeinfo.get("general", "version")
|
|
if ver == "development" or ver == "rawhide":
|
|
self.os_variant = self._latestFedoraVariant()
|
|
elif ver:
|
|
vernum = int(str(ver).split("-")[0])
|
|
if vernum > latestnum:
|
|
self.os_variant = lateststr
|
|
else:
|
|
self.os_variant = "fedora" + str(vernum)
|
|
|
|
|
|
return ret
|
|
else:
|
|
if fetcher.hasFile("Fedora"):
|
|
logging.debug("Detected a Fedora distro")
|
|
return True
|
|
return False
|
|
|
|
def _latestFedoraVariant(self):
|
|
ret = None
|
|
for osinfo in osdict.list_os(typename="linux"):
|
|
if osinfo.name.startswith("fedora"):
|
|
# First fedora* occurence should be the newest
|
|
ret = osinfo.name
|
|
break
|
|
|
|
return ret, int(ret[6:])
|
|
|
|
|
|
# Red Hat Enterprise Linux distro check
|
|
class RHELDistro(RedHatDistro):
|
|
name = "Red Hat Enterprise Linux"
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
m = re.match(".*Red Hat Enterprise Linux.*",
|
|
self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
|
|
if ret:
|
|
self._variantFromVersion()
|
|
return ret
|
|
else:
|
|
# fall back to old code
|
|
if fetcher.hasFile("Server"):
|
|
logging.debug("Detected a RHEL 5 Server distro")
|
|
self.os_variant = "rhel5"
|
|
return True
|
|
if fetcher.hasFile("Client"):
|
|
logging.debug("Detected a RHEL 5 Client distro")
|
|
self.os_variant = "rhel5"
|
|
return True
|
|
if fetcher.hasFile("RedHat"):
|
|
if fetcher.hasFile("dosutils"):
|
|
self.os_variant = "rhel3"
|
|
else:
|
|
self.os_variant = "rhel4"
|
|
|
|
logging.debug("Detected a %s distro", self.os_variant)
|
|
return True
|
|
return False
|
|
|
|
def _parseTreeinfoVersion(self, verstr):
|
|
version = safeint(verstr[0])
|
|
update = 0
|
|
|
|
updinfo = verstr.split(".")
|
|
if len(updinfo) > 1:
|
|
update = safeint(updinfo[1])
|
|
|
|
return version, update
|
|
|
|
def _variantFromVersion(self):
|
|
ver = self.treeinfo.get("general", "version")
|
|
if not ver:
|
|
return
|
|
|
|
version, update = self._parseTreeinfoVersion(ver)
|
|
self._setRHELVariant(version, update)
|
|
|
|
def _setRHELVariant(self, version, update):
|
|
base = "rhel" + str(version)
|
|
if update < 0:
|
|
update = 0
|
|
|
|
ret = None
|
|
while update >= 0:
|
|
tryvar = base + ".%s" % update
|
|
if not _check_osvariant_valid(tryvar):
|
|
update -= 1
|
|
continue
|
|
|
|
ret = tryvar
|
|
break
|
|
|
|
if not ret:
|
|
# Try plain rhel5, rhel6, whatev
|
|
if _check_osvariant_valid(base):
|
|
ret = base
|
|
|
|
if ret:
|
|
self.os_variant = ret
|
|
|
|
|
|
# CentOS distro check
|
|
class CentOSDistro(RHELDistro):
|
|
name = "CentOS"
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
m = re.match(".*CentOS.*", self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
|
|
if ret:
|
|
self._variantFromVersion()
|
|
return ret
|
|
else:
|
|
# fall back to old code
|
|
if fetcher.hasFile("CentOS"):
|
|
logging.debug("Detected a CentOS distro")
|
|
return True
|
|
return False
|
|
|
|
|
|
# Scientific Linux distro check
|
|
class SLDistro(RHELDistro):
|
|
name = "Scientific Linux"
|
|
|
|
_boot_iso_paths = RHELDistro._boot_iso_paths + ["images/SL/boot.iso"]
|
|
_hvm_kernel_paths = RHELDistro._hvm_kernel_paths + \
|
|
[("images/SL/pxeboot/vmlinuz",
|
|
"images/SL/pxeboot/initrd.img")]
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if self._hasTreeinfo(fetcher, progresscb):
|
|
m = re.match(".*Scientific Linux.*",
|
|
self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
|
|
if ret:
|
|
self._variantFromVersion()
|
|
return ret
|
|
else:
|
|
if fetcher.hasFile("SL"):
|
|
logging.debug("Detected a Scientific Linux distro")
|
|
return True
|
|
return False
|
|
|
|
def _parseTreeinfoVersion(self, verstr):
|
|
"""
|
|
Overrides method in RHELDistro
|
|
"""
|
|
version = safeint(verstr[0])
|
|
update = 0
|
|
|
|
if len(verstr) > 1:
|
|
update = safeint(verstr[1])
|
|
return version, update
|
|
|
|
|
|
# Suse image store is harder - we fetch the kernel RPM and a helper
|
|
# RPM and then munge bits together to generate a initrd
|
|
class SuseDistro(Distro):
|
|
name = "SUSE"
|
|
os_variant = "linux"
|
|
method_arg = "install"
|
|
_boot_iso_paths = ["boot/boot.iso"]
|
|
|
|
def __init__(self, uri, arch, vmtype=None, scratchdir=None):
|
|
Distro.__init__(self, uri, arch, vmtype, scratchdir)
|
|
if re.match(r'i[4-9]86', arch):
|
|
self.arch = 'i386'
|
|
|
|
oldkern = "linux"
|
|
oldinit = "initrd"
|
|
if arch == "x86_64":
|
|
oldkern += "64"
|
|
oldinit += "64"
|
|
|
|
# Tested with Opensuse >= 10.2, 11, and sles 10
|
|
self._hvm_kernel_paths = [("boot/%s/loader/linux" % self.arch,
|
|
"boot/%s/loader/initrd" % self.arch)]
|
|
# Tested with Opensuse 10.0
|
|
self._hvm_kernel_paths.append(("boot/loader/%s" % oldkern,
|
|
"boot/loader/%s" % oldinit))
|
|
|
|
# Matches Opensuse > 10.2 and sles 10
|
|
self._xen_kernel_paths = [("boot/%s/vmlinuz-xen" % self.arch,
|
|
"boot/%s/initrd-xen" % self.arch)]
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
# Suse distros always have a 'directory.yast' file in the top
|
|
# level of install tree, which we use as the magic check
|
|
if fetcher.hasFile("directory.yast"):
|
|
logging.debug("Detected a Suse distro.")
|
|
return True
|
|
return False
|
|
|
|
|
|
class DebianDistro(Distro):
|
|
# ex. http://ftp.egr.msu.edu/debian/dists/sarge/main/installer-i386/
|
|
# daily builds: http://d-i.debian.org/daily-images/amd64/
|
|
|
|
name = "Debian"
|
|
os_variant = "linux"
|
|
|
|
def __init__(self, uri, arch, vmtype=None, scratchdir=None):
|
|
Distro.__init__(self, uri, arch, vmtype, scratchdir)
|
|
if uri.count("i386"):
|
|
self._treeArch = "i386"
|
|
elif uri.count("amd64"):
|
|
self._treeArch = "amd64"
|
|
else:
|
|
self._treeArch = "i386"
|
|
|
|
if re.match(r'i[4-9]86', arch):
|
|
self.arch = 'i386'
|
|
|
|
self._installer_name = self.name.lower() + "-" + "installer"
|
|
self._prefix = 'current/images'
|
|
self._set_media_paths()
|
|
|
|
def _set_media_paths(self):
|
|
# Use self._prefix to set media paths
|
|
self._boot_iso_paths = ["%s/netboot/mini.iso" % self._prefix]
|
|
hvmroot = "%s/netboot/%s/%s/" % (self._prefix,
|
|
self._installer_name,
|
|
self._treeArch)
|
|
xenroot = "%s/netboot/xen/" % self._prefix
|
|
self._hvm_kernel_paths = [(hvmroot + "linux", hvmroot + "initrd.gz")]
|
|
self._xen_kernel_paths = [(xenroot + "vmlinuz",
|
|
xenroot + "initrd.gz")]
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if fetcher.hasFile("%s/MANIFEST" % self._prefix):
|
|
# For regular trees
|
|
pass
|
|
elif fetcher.hasFile("daily/MANIFEST"):
|
|
# For daily trees
|
|
self._prefix = "daily"
|
|
self._set_media_paths()
|
|
else:
|
|
return False
|
|
|
|
filename = "%s/MANIFEST" % self._prefix
|
|
regex = ".*%s.*" % self._installer_name
|
|
if self._fetchAndMatchRegex(fetcher, progresscb, filename, regex):
|
|
logging.debug("Detected a %s distro", self.name)
|
|
return True
|
|
|
|
logging.debug("MANIFEST didn't match regex, not a %s distro",
|
|
self.name)
|
|
return False
|
|
|
|
|
|
class UbuntuDistro(DebianDistro):
|
|
# http://archive.ubuntu.com/ubuntu/dists/natty/main/installer-amd64/
|
|
name = "Ubuntu"
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if fetcher.hasFile("%s/MANIFEST" % self._prefix):
|
|
# For regular trees
|
|
filename = "%s/MANIFEST" % self._prefix
|
|
regex = ".*%s.*" % self._installer_name
|
|
elif fetcher.hasFile("install/netboot/version.info"):
|
|
# For trees based on ISO's
|
|
self._prefix = "install"
|
|
self._set_media_paths()
|
|
filename = "%s/netboot/version.info" % self._prefix
|
|
regex = "%s*" % self.name
|
|
else:
|
|
logging.debug("Doesn't look like an %s Distro.", self.name)
|
|
return False
|
|
|
|
if self._fetchAndMatchRegex(fetcher, progresscb, filename, regex):
|
|
logging.debug("Detected an %s distro", self.name)
|
|
return True
|
|
|
|
logging.debug("Regex didn't match, not an %s distro", self.name)
|
|
return False
|
|
|
|
|
|
class MandrivaDistro(Distro):
|
|
# ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2007.1/x86_64/
|
|
name = "Mandriva"
|
|
os_variant = "linux"
|
|
_boot_iso_paths = ["install/images/boot.iso"]
|
|
# Kernels for HVM: valid for releases 2007.1, 2008.*, 2009.0
|
|
_hvm_kernel_paths = [("isolinux/alt0/vmlinuz", "isolinux/alt0/all.rdz")]
|
|
_xen_kernel_paths = []
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
# Don't support any paravirt installs
|
|
if self.type is not None and self.type != "hvm":
|
|
return False
|
|
|
|
# Mandriva websites / media appear to have a VERSION
|
|
# file in top level which we can use as our 'magic'
|
|
# check for validity
|
|
if not fetcher.hasFile("VERSION"):
|
|
return False
|
|
|
|
if self._fetchAndMatchRegex(fetcher, progresscb, "VERSION",
|
|
".*%s.*" % self.name):
|
|
logging.debug("Detected a %s distro", self.name)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
class MageiaDistro(MandrivaDistro):
|
|
name = "Mageia"
|
|
|
|
|
|
class ALTLinuxDistro(Distro):
|
|
name = "ALT Linux"
|
|
os_variant = "linux"
|
|
_boot_iso_paths = [("altinst", "live")]
|
|
_hvm_kernel_paths = [("syslinux/alt0/vmlinuz", "syslinux/alt0/full.cz")]
|
|
_xen_kernel_paths = []
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
# Don't support any paravirt installs
|
|
if self.type is not None and self.type != "hvm":
|
|
return False
|
|
|
|
if not fetcher.hasFile(".disk/info"):
|
|
return False
|
|
|
|
if self._fetchAndMatchRegex(fetcher, progresscb, ".disk/info",
|
|
".*%s.*" % self.name):
|
|
logging.debug("Detected a %s distro", self.name)
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
# Solaris and OpenSolaris distros
|
|
class SunDistro(Distro):
|
|
name = "Solaris"
|
|
os_variant = "solaris"
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
"""Determine if uri points to a tree of the store's distro"""
|
|
raise NotImplementedError
|
|
|
|
def acquireBootDisk(self, guest, fetcher, progresscb):
|
|
return fetcher.acquireFile("images/solarisdvd.iso", progresscb)
|
|
|
|
def process_extra_args(self, argstr):
|
|
"""Collect additional arguments."""
|
|
if not argstr:
|
|
return (None, None, None, None)
|
|
|
|
kopts = ''
|
|
kargs = ''
|
|
smfargs = ''
|
|
Bargs = ''
|
|
|
|
args = argstr.split()
|
|
i = 0
|
|
while i < len(args):
|
|
exarg = args[i]
|
|
if exarg == '-B':
|
|
i += 1
|
|
if i == len(args):
|
|
continue
|
|
|
|
if not Bargs:
|
|
Bargs = args[i]
|
|
else:
|
|
Bargs = ','.join([Bargs, args[i]])
|
|
|
|
elif exarg == '-m':
|
|
i += 1
|
|
if i == len(args):
|
|
continue
|
|
smfargs = args[i]
|
|
elif exarg.startswith('-'):
|
|
if kopts is None:
|
|
kopts = exarg[1:]
|
|
else:
|
|
kopts = kopts + exarg[1:]
|
|
else:
|
|
if kargs is None:
|
|
kargs = exarg
|
|
else:
|
|
kargs = kargs + ' ' + exarg
|
|
i += 1
|
|
|
|
return kopts, kargs, smfargs, Bargs
|
|
|
|
|
|
class SolarisDistro(SunDistro):
|
|
kernelpath = 'boot/platform/i86xpv/kernel/unix'
|
|
initrdpath = 'boot/x86.miniroot'
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if fetcher.hasFile(self.kernelpath):
|
|
logging.debug('Detected Solaris')
|
|
return True
|
|
return False
|
|
|
|
def install_args(self, guest):
|
|
"""Construct kernel cmdline args for the installer, consisting of:
|
|
the pathname of the kernel (32/64) to load, kernel options
|
|
and args, and '-B' boot properties."""
|
|
|
|
(kopts, kargs, ignore_smfargs, kbargs) = \
|
|
self.process_extra_args(guest.extraargs)
|
|
|
|
args = ['']
|
|
if kopts:
|
|
args += ['-%s' % kopts]
|
|
if kbargs:
|
|
args += ['-B', kbargs]
|
|
|
|
netmask = ''
|
|
# Yuck. Non-default netmasks require this option to be passed.
|
|
# It's distinctly not-trivial to work out the netmask to be used
|
|
# automatically.
|
|
if kargs:
|
|
for karg in kargs.split():
|
|
if karg.startswith('subnet-mask'):
|
|
netmask = karg.split('=')[1]
|
|
else:
|
|
args += [kargs]
|
|
|
|
iargs = ''
|
|
if not guest.graphics['enabled']:
|
|
iargs += 'nowin '
|
|
|
|
if guest.location.startswith('nfs:'):
|
|
try:
|
|
guestIP = socket.gethostbyaddr(guest.name)[2][0]
|
|
except:
|
|
iargs += ' dhcp'
|
|
else:
|
|
iserver = guest.location.split(':')[1]
|
|
ipath = guest.location.split(':')[2]
|
|
iserverIP = socket.gethostbyaddr(iserver)[2][0]
|
|
iargs += ' -B install_media=' + iserverIP + ':' + ipath
|
|
iargs += ',host-ip=' + guestIP
|
|
if netmask:
|
|
iargs += ',subnet-mask=%s' % netmask
|
|
droute = util.default_route()
|
|
if droute:
|
|
iargs += ',router-ip=' + droute
|
|
if guest.nics[0].macaddr:
|
|
en = guest.nics[0].macaddr.split(':')
|
|
for i in range(len(en)):
|
|
# remove leading '0' from mac address element
|
|
if len(en[i]) > 1 and en[i][0] == '0':
|
|
en[i] = en[i][1]
|
|
boot_mac = ':'.join(en)
|
|
iargs += ',boot-mac=' + boot_mac
|
|
else:
|
|
iargs += '-B install_media=cdrom'
|
|
|
|
args += ['-', iargs]
|
|
return ' '.join(args)
|
|
|
|
def acquireKernel(self, guest, fetcher, progresscb):
|
|
|
|
try:
|
|
kernel = fetcher.acquireFile(self.kernelpath, progresscb)
|
|
except:
|
|
raise RuntimeError("Solaris PV kernel not found at %s" %
|
|
self.kernelpath)
|
|
|
|
# strip boot from the kernel path
|
|
kpath = self.kernelpath.split('/')[1:]
|
|
args = "/" + "/".join(kpath) + self.install_args(guest)
|
|
|
|
try:
|
|
initrd = fetcher.acquireFile(self.initrdpath, progresscb)
|
|
return (kernel, initrd, args)
|
|
except:
|
|
os.unlink(kernel)
|
|
raise RuntimeError(_("Solaris miniroot not found at %s") %
|
|
self.initrdpath)
|
|
|
|
|
|
class OpenSolarisDistro(SunDistro):
|
|
os_variant = "opensolaris"
|
|
|
|
kernelpath = "platform/i86xpv/kernel/unix"
|
|
initrdpaths = ["platform/i86pc/boot_archive", "boot/x86.microroot"]
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if fetcher.hasFile(self.kernelpath):
|
|
logging.debug("Detected OpenSolaris")
|
|
return True
|
|
return False
|
|
|
|
def install_args(self, guest):
|
|
"""Construct kernel cmdline args for the installer, consisting of:
|
|
the pathname of the kernel (32/64) to load, kernel options
|
|
and args, and '-B' boot properties."""
|
|
|
|
(kopts, ignore_kargs, ignore_smfargs, kbargs) = \
|
|
self.process_extra_args(guest.extraargs)
|
|
|
|
args = ''
|
|
if kopts:
|
|
args += ' -' + kopts
|
|
if kbargs:
|
|
args += ' -B ' + kbargs
|
|
|
|
return args
|
|
|
|
def acquireKernel(self, guest, fetcher, progresscb):
|
|
|
|
try:
|
|
kernel = fetcher.acquireFile(self.kernelpath, progresscb)
|
|
except:
|
|
raise RuntimeError(_("OpenSolaris PV kernel not found at %s") %
|
|
self.kernelpath)
|
|
|
|
args = "/" + self.kernelpath + self.install_args(guest)
|
|
|
|
try:
|
|
initrd = fetcher.acquireFile(self.initrdpaths[0], progresscb)
|
|
return (kernel, initrd, args)
|
|
except Exception, e:
|
|
try:
|
|
initrd = fetcher.acquireFile(self.initrdpaths[1], progresscb)
|
|
return (kernel, initrd, args)
|
|
except:
|
|
os.unlink(kernel)
|
|
raise Exception("No OpenSolaris boot archive found: %s\n" % e)
|
|
|
|
|
|
# NetWare 6 PV
|
|
class NetWareDistro(Distro):
|
|
name = "NetWare"
|
|
os_variant = "netware6"
|
|
|
|
loaderpath = "STARTUP/XNLOADER.SYS"
|
|
|
|
def isValidStore(self, fetcher, progresscb):
|
|
if fetcher.hasFile(self.loaderpath):
|
|
logging.debug("Detected NetWare")
|
|
return True
|
|
return False
|
|
|
|
def acquireKernel(self, guest, fetcher, progresscb):
|
|
loader = fetcher.acquireFile(self.loaderpath, progresscb)
|
|
return (loader, "", "")
|