mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-02-14 09:23:40 -06:00
In case there was '.treeinfo' file available, virt-install was forcibly getting image information out of the file. For some distributions (especially older releases, e.g. RHEL 4), there might be '.treeinfo' file available, but with incomplete information. Allow fallback to default values even when '.treeinfo' file is found. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=954262
1252 lines
42 KiB
Python
1252 lines
42 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 logging
|
|
import os
|
|
import gzip
|
|
import re
|
|
import tempfile
|
|
import socket
|
|
import ConfigParser
|
|
|
|
import virtinst
|
|
from virtinst import osdict
|
|
from virtinst import util
|
|
|
|
from virtinst.ImageFetcher import MountedImageFetcher
|
|
from virtinst.ImageFetcher import FTPImageFetcher
|
|
from virtinst.ImageFetcher import HTTPImageFetcher
|
|
from virtinst.ImageFetcher import DirectImageFetcher
|
|
|
|
|
|
def safeint(c):
|
|
try:
|
|
val = int(c)
|
|
except:
|
|
val = 0
|
|
return val
|
|
|
|
|
|
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 = virtinst.OSDistro.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 == "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.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_type, 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_type, 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_ostype_valid(os_type):
|
|
return bool(os_type in osdict.sort_helper(osdict.OS_TYPES))
|
|
|
|
|
|
def _check_osvariant_valid(os_type, os_variant):
|
|
return bool(_check_ostype_valid(os_type) and
|
|
os_variant in osdict.sort_helper(osdict.OS_TYPES[os_type]["variants"]))
|
|
|
|
# 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 os_type/variant info
|
|
ob.isValidStore(fetcher, progresscb)
|
|
|
|
return ob
|
|
|
|
|
|
# An image store is a base class for retrieving either a bootable
|
|
# ISO image, or a kernel+initrd pair for a particular OS distribution
|
|
class Distro:
|
|
|
|
name = ""
|
|
|
|
# osdict type and variant values
|
|
os_type = None
|
|
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_type:
|
|
return (None, None)
|
|
|
|
if not _check_ostype_valid(self.os_type):
|
|
logging.debug("%s set os_type to %s, which is not in osdict.",
|
|
self, self.os_type)
|
|
return (None, None)
|
|
|
|
if not self.os_variant:
|
|
return (self.os_type, None)
|
|
|
|
if not _check_osvariant_valid(self.os_type, self.os_variant):
|
|
logging.debug("%s set os_variant to %s, which is not in osdict"
|
|
" for distro %s.",
|
|
self, self.os_variant, self.os_type)
|
|
return (self.os_type, None)
|
|
|
|
return (self.os_type, 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 neccessary
|
|
kernel = fetcher.acquireFile(kernelpath, progresscb)
|
|
args = ''
|
|
|
|
if not fetcher.location.startswith("/"):
|
|
args += "%s=%s" % (self.method_arg, fetcher.location)
|
|
|
|
if guest.extraargs:
|
|
args += " " + guest.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_type = "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)
|
|
|
|
|
|
# Base image store for any Red Hat related distros which have
|
|
# a common layout
|
|
class RedHatDistro(Distro):
|
|
|
|
name = "Red Hat"
|
|
os_type = "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":
|
|
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 var in osdict.sort_helper(osdict.OS_TYPES["linux"]["variants"]):
|
|
if var.startswith("fedora"):
|
|
# First fedora* occurence should be the newest
|
|
ret = var
|
|
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):
|
|
if not _check_ostype_valid(self.os_type):
|
|
return
|
|
|
|
base = "rhel" + str(version)
|
|
if update < 0:
|
|
update = 0
|
|
|
|
ret = None
|
|
while update >= 0:
|
|
tryvar = base + ".%s" % update
|
|
if not _check_osvariant_valid(self.os_type, tryvar):
|
|
update -= 1
|
|
continue
|
|
|
|
ret = tryvar
|
|
break
|
|
|
|
if not ret:
|
|
# Try plain rhel5, rhel6, whatev
|
|
if _check_osvariant_valid(self.os_type, 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_type = "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
|
|
|
|
def acquireKernel(self, guest, fetcher, progresscb):
|
|
# If installing a fullvirt guest
|
|
if self.type is None or self.type == "hvm" or \
|
|
fetcher.hasFile("boot/%s/vmlinuz-xen" % self.arch):
|
|
return Distro.acquireKernel(self, guest, fetcher, progresscb)
|
|
|
|
# For Opensuse <= 10.2, we need to perform some heinous stuff
|
|
logging.debug("Trying Opensuse 10 PV rpm hacking")
|
|
return self._findXenRPMS(fetcher, progresscb)
|
|
|
|
|
|
def _findXenRPMS(self, fetcher, progresscb):
|
|
kernelrpm = None
|
|
installinitrdrpm = None
|
|
filelist = None
|
|
try:
|
|
# There is no predictable filename for kernel/install-initrd RPMs
|
|
# so we have to grok the filelist and find them
|
|
filelist = fetcher.acquireFile("ls-lR.gz", progresscb)
|
|
(kernelrpmname, initrdrpmname) = self._extractRPMNames(filelist)
|
|
|
|
# Now fetch the two RPMs we want
|
|
kernelrpm = fetcher.acquireFile(kernelrpmname, progresscb)
|
|
installinitrdrpm = fetcher.acquireFile(initrdrpmname, progresscb)
|
|
|
|
# Process the RPMs to extract the kernel & generate an initrd
|
|
return self._buildKernelInitrd(fetcher, kernelrpm, installinitrdrpm, progresscb)
|
|
finally:
|
|
if filelist is not None:
|
|
os.unlink(filelist)
|
|
if kernelrpm is not None:
|
|
os.unlink(kernelrpm)
|
|
if installinitrdrpm is not None:
|
|
os.unlink(installinitrdrpm)
|
|
|
|
# We need to parse the ls-lR.gz file, looking for the kernel &
|
|
# install-initrd RPM entries - capturing the directory they are
|
|
# in and the version'd filename.
|
|
def _extractRPMNames(self, filelist):
|
|
filelistData = gzip.GzipFile(filelist, mode="r")
|
|
try:
|
|
arches = [self.arch]
|
|
# On i686 arch, we also look under i585 and i386 dirs
|
|
# in case the RPM is built for a lesser arch. We also
|
|
# need the PAE variant (for Fedora dom0 at least)
|
|
if self.arch == "i386":
|
|
arches.append("i586")
|
|
arches.append("i686")
|
|
kernelname = "kernel-xenpae"
|
|
else:
|
|
kernelname = "kernel-xen"
|
|
|
|
installinitrdrpm = None
|
|
kernelrpm = None
|
|
dirname = None
|
|
while 1:
|
|
data = filelistData.readline()
|
|
if not data:
|
|
break
|
|
if dirname is None:
|
|
for arch in arches:
|
|
wantdir = "/suse/" + arch
|
|
if data == "." + wantdir + ":\n":
|
|
dirname = wantdir
|
|
break
|
|
else:
|
|
if data == "\n":
|
|
dirname = None
|
|
else:
|
|
if data[:5] != "total":
|
|
filename = re.split("\s+", data)[8]
|
|
|
|
if filename[:14] == "install-initrd":
|
|
installinitrdrpm = dirname + "/" + filename
|
|
elif filename[:len(kernelname)] == kernelname:
|
|
kernelrpm = dirname + "/" + filename
|
|
|
|
if kernelrpm is None:
|
|
raise Exception(_("Unable to determine kernel RPM path"))
|
|
if installinitrdrpm is None:
|
|
raise Exception(_("Unable to determine install-initrd RPM path"))
|
|
return (kernelrpm, installinitrdrpm)
|
|
finally:
|
|
filelistData.close()
|
|
|
|
# We have a kernel RPM and a install-initrd RPM with a generic initrd in it
|
|
# Now we have to merge the two together to build an initrd capable of
|
|
# booting the installer.
|
|
#
|
|
# Yes, this is crazy ass stuff :-)
|
|
def _buildKernelInitrd(self, fetcher, kernelrpm, installinitrdrpm, progresscb):
|
|
progresscb.start(text=_("Building initrd"), size=11)
|
|
progresscb.update(1)
|
|
cpiodir = tempfile.mkdtemp(prefix="virtinstcpio.", dir=self.scratchdir)
|
|
try:
|
|
# Extract the kernel RPM contents
|
|
os.mkdir(cpiodir + "/kernel")
|
|
cmd = "cd " + cpiodir + "/kernel && (rpm2cpio " + kernelrpm + " | cpio --quiet -idm)"
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.update(2)
|
|
|
|
# Determine the raw kernel version
|
|
kernelinfo = None
|
|
for f in os.listdir(cpiodir + "/kernel/boot"):
|
|
if f.startswith("System.map-"):
|
|
kernelinfo = re.split("-", f)
|
|
kernel_override = kernelinfo[1] + "-override-" + kernelinfo[3]
|
|
kernel_version = kernelinfo[1] + "-" + kernelinfo[2] + "-" + kernelinfo[3]
|
|
logging.debug("Got kernel version " + str(kernelinfo))
|
|
|
|
# Build a list of all .ko files
|
|
modpaths = {}
|
|
for root, ignore, files in os.walk(cpiodir + "/kernel/lib/modules", topdown=False):
|
|
for name in files:
|
|
if name.endswith(".ko"):
|
|
modpaths[name] = os.path.join(root, name)
|
|
progresscb.update(3)
|
|
|
|
# Extract the install-initrd RPM contents
|
|
os.mkdir(cpiodir + "/installinitrd")
|
|
cmd = "cd " + cpiodir + "/installinitrd && (rpm2cpio " + installinitrdrpm + " | cpio --quiet -idm)"
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.update(4)
|
|
|
|
# Read in list of mods required for initrd
|
|
modnames = []
|
|
fn = open(cpiodir + "/installinitrd/usr/lib/install-initrd/" + kernelinfo[3] + "/module.list", "r")
|
|
try:
|
|
while 1:
|
|
line = fn.readline()
|
|
if not line:
|
|
break
|
|
line = line[:len(line) - 1]
|
|
modnames.append(line)
|
|
finally:
|
|
fn.close()
|
|
progresscb.update(5)
|
|
|
|
# Uncompress the basic initrd
|
|
cmd = "gunzip -c " + cpiodir + "/installinitrd/usr/lib/install-initrd/initrd-base.gz > " + cpiodir + "/initrd.img"
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.update(6)
|
|
|
|
# Create temp tree to hold stuff we're adding to initrd
|
|
moddir = cpiodir + "/initrd/lib/modules/" + kernel_override + "/initrd/"
|
|
moddepdir = cpiodir + "/initrd/lib/modules/" + kernel_version
|
|
os.makedirs(moddir)
|
|
os.makedirs(moddepdir)
|
|
os.symlink("../" + kernel_override, moddepdir + "/updates")
|
|
os.symlink("lib/modules/" + kernel_override + "/initrd", cpiodir + "/initrd/modules")
|
|
cmd = "cp " + cpiodir + "/installinitrd/usr/lib/install-initrd/" + kernelinfo[3] + "/module.config" + " " + moddir
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.update(7)
|
|
|
|
# Copy modules we need into initrd staging dir
|
|
for modname in modnames:
|
|
if modname in modpaths:
|
|
src = modpaths[modname]
|
|
dst = moddir + "/" + modname
|
|
os.system("cp " + src + " " + dst)
|
|
progresscb.update(8)
|
|
|
|
# Run depmod across the staging area
|
|
cmd = "depmod -a -b " + cpiodir + "/initrd -F " + cpiodir + "/kernel/boot/System.map-" + kernel_version + " " + kernel_version
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.update(9)
|
|
|
|
# Add the extra modules to the basic initrd
|
|
cmd = "cd " + cpiodir + "/initrd && (find . | cpio --quiet -o -H newc -A -F " + cpiodir + "/initrd.img)"
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.update(10)
|
|
|
|
# Compress the final initrd
|
|
cmd = "gzip -f9N " + cpiodir + "/initrd.img"
|
|
logging.debug("Running " + cmd)
|
|
os.system(cmd)
|
|
progresscb.end(11)
|
|
|
|
# Save initrd & kernel to temp files for booting...
|
|
initrdname = fetcher.saveTemp(open(cpiodir + "/initrd.img.gz", "r"), "initrd.img")
|
|
logging.debug("Saved " + initrdname)
|
|
try:
|
|
kernelname = fetcher.saveTemp(open(cpiodir + "/kernel/boot/vmlinuz-" + kernel_version, "r"), "vmlinuz")
|
|
logging.debug("Saved " + kernelname)
|
|
return (kernelname, initrdname, "install=" + fetcher.location)
|
|
except:
|
|
os.unlink(initrdname)
|
|
finally:
|
|
# pass
|
|
os.system("rm -rf " + cpiodir)
|
|
|
|
|
|
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_type = "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):
|
|
name = "Ubuntu"
|
|
# regular tree:
|
|
# http://archive.ubuntu.com/ubuntu/dists/natty/main/installer-amd64/
|
|
|
|
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):
|
|
# Ex. ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2007.1/x86_64/
|
|
|
|
name = "Mandriva"
|
|
os_type = "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"
|
|
|
|
# Solaris and OpenSolaris distros
|
|
|
|
|
|
class SunDistro(Distro):
|
|
|
|
name = "Solaris"
|
|
os_type = "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(guest.nics[0].bridge)
|
|
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_type = "other"
|
|
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, "", "")
|