mirror of
https://github.com/virt-manager/virt-manager.git
synced 2024-12-24 21:34:47 +03:00
5584863d18
urlgrabber is largely dead upstream and isn't going to be ported to python3 AFAIK. So we will need to move off of it eventually. Use requests for http handling which is the most common library nowadays, and just plain old urllib2 for ftp fetching.
1230 lines
38 KiB
Python
1230 lines
38 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 requests
|
|
import stat
|
|
import StringIO
|
|
import subprocess
|
|
import tempfile
|
|
import urllib2
|
|
import urlparse
|
|
|
|
from .osdict import OSDB
|
|
|
|
|
|
#########################################################################
|
|
# Backends for the various URL types we support (http, ftp, nfs, local) #
|
|
#########################################################################
|
|
|
|
class _URLFetcher(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
|
|
"""
|
|
_block_size = 16384
|
|
|
|
def __init__(self, location, scratchdir, meter):
|
|
self.location = location
|
|
self.scratchdir = scratchdir
|
|
self.meter = meter
|
|
|
|
self._srcdir = None
|
|
|
|
logging.debug("Using scratchdir=%s", scratchdir)
|
|
|
|
|
|
####################
|
|
# Internal helpers #
|
|
####################
|
|
|
|
def _make_full_url(self, filename):
|
|
"""
|
|
Generate a full fetchable URL from the passed filename, which
|
|
is relative to the self.location
|
|
"""
|
|
ret = self._srcdir or self.location
|
|
if not filename:
|
|
return ret
|
|
|
|
if not ret.endswith("/"):
|
|
ret += "/"
|
|
return ret + filename
|
|
|
|
def _grabURL(self, filename, fileobj):
|
|
"""
|
|
Download the filename from self.location, and write contents to
|
|
fileobj
|
|
"""
|
|
url = self._make_full_url(filename)
|
|
|
|
try:
|
|
urlobj, size = self._grabber(url)
|
|
except Exception, e:
|
|
raise ValueError(_("Couldn't acquire file %s: %s") %
|
|
(url, str(e)))
|
|
|
|
logging.debug("Fetching URI: %s", url)
|
|
self.meter.start(
|
|
text=_("Retrieving file %s...") % os.path.basename(filename),
|
|
size=size)
|
|
|
|
total = self._write(urlobj, fileobj)
|
|
self.meter.end(total)
|
|
|
|
def _write(self, urlobj, fileobj):
|
|
"""
|
|
Write the contents of urlobj to python file like object fileobj
|
|
"""
|
|
total = 0
|
|
while 1:
|
|
buff = urlobj.read(self._block_size)
|
|
if not buff:
|
|
break
|
|
fileobj.write(buff)
|
|
total += len(buff)
|
|
self.meter.update(total)
|
|
return total
|
|
|
|
def _grabber(self, url):
|
|
"""
|
|
Returns the urlobj, size for the passed URL. urlobj is whatever
|
|
data needs to be passed to self._write
|
|
"""
|
|
raise NotImplementedError("must be implemented in subclass")
|
|
|
|
|
|
##############
|
|
# Public API #
|
|
##############
|
|
|
|
def prepareLocation(self):
|
|
"""
|
|
Perform any necessary setup
|
|
"""
|
|
pass
|
|
|
|
def cleanupLocation(self):
|
|
"""
|
|
Perform any necessary cleanup
|
|
"""
|
|
pass
|
|
|
|
def hasFile(self, filename):
|
|
"""
|
|
Return True if self.location has the passed filename
|
|
"""
|
|
raise NotImplementedError("Must be implemented in subclass")
|
|
|
|
def acquireFile(self, filename):
|
|
"""
|
|
Grab the passed filename from self.location and save it to
|
|
a temporary file, returning the temp filename
|
|
"""
|
|
prefix = "virtinst-" + os.path.basename(filename) + "."
|
|
|
|
if "VIRTINST_TEST_SUITE" in os.environ:
|
|
fn = os.path.join("/tmp", prefix)
|
|
fileobj = file(fn, "w")
|
|
else:
|
|
fileobj = tempfile.NamedTemporaryFile(
|
|
dir=self.scratchdir, prefix=prefix, delete=False)
|
|
fn = fileobj.name
|
|
|
|
self._grabURL(filename, fileobj)
|
|
logging.debug("Saved file to " + fn)
|
|
return fn
|
|
|
|
def acquireFileContent(self, filename):
|
|
"""
|
|
Grab the passed filename from self.location and return it as a string
|
|
"""
|
|
fileobj = StringIO.StringIO()
|
|
self._grabURL(filename, fileobj)
|
|
return fileobj.getvalue()
|
|
|
|
|
|
class _HTTPURLFetcher(_URLFetcher):
|
|
def hasFile(self, filename):
|
|
"""
|
|
We just do a HEAD request to see if the file exists
|
|
"""
|
|
url = self._make_full_url(filename)
|
|
try:
|
|
response = requests.head(url)
|
|
response.raise_for_status()
|
|
except Exception, e:
|
|
logging.debug("HTTP hasFile: didn't find %s: %s", url, str(e))
|
|
return False
|
|
return True
|
|
|
|
def _grabber(self, url):
|
|
"""
|
|
Use requests for this
|
|
"""
|
|
response = requests.get(url, stream=True)
|
|
response.raise_for_status()
|
|
size = response.headers.get('content-length')
|
|
return response, size.isdigit() and int(size) or None
|
|
|
|
def _write(self, urlobj, fileobj):
|
|
"""
|
|
The requests object doesn't have a file-like read() option, so
|
|
we need to implemente it ourselves
|
|
"""
|
|
total = 0
|
|
for data in urlobj.iter_content(chunk_size=self._block_size):
|
|
fileobj.write(data)
|
|
total += len(data)
|
|
self.meter.update(total)
|
|
return total
|
|
|
|
|
|
class _FTPURLFetcher(_URLFetcher):
|
|
_ftp = None
|
|
|
|
def prepareLocation(self):
|
|
if self._ftp:
|
|
return
|
|
|
|
try:
|
|
server = urlparse.urlparse(self.location)[1]
|
|
self._ftp = ftplib.FTP(server)
|
|
self._ftp.login()
|
|
except Exception, e:
|
|
raise ValueError(_("Opening URL %s failed: %s.") %
|
|
(self.location, str(e)))
|
|
|
|
def _grabber(self, url):
|
|
"""
|
|
Use urllib2 and ftplib to grab the file
|
|
"""
|
|
request = urllib2.Request(url)
|
|
urlobj = urllib2.urlopen(request)
|
|
size = self._ftp.size(urlparse.urlparse(url)[2])
|
|
return urlobj, size
|
|
|
|
|
|
def cleanupLocation(self):
|
|
if not self._ftp:
|
|
return
|
|
|
|
try:
|
|
self._ftp.quit()
|
|
except:
|
|
logging.debug("Error quitting ftp connection", exc_info=True)
|
|
|
|
self._ftp = None
|
|
|
|
def hasFile(self, filename):
|
|
url = self._make_full_url(filename)
|
|
path = urlparse.urlparse(url)[2]
|
|
|
|
try:
|
|
try:
|
|
# If it's a file
|
|
self._ftp.size(path)
|
|
except ftplib.all_errors:
|
|
# If it's a dir
|
|
self._ftp.cwd(path)
|
|
except ftplib.all_errors, e:
|
|
logging.debug("FTP hasFile: couldn't access %s: %s",
|
|
url, str(e))
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
class _LocalURLFetcher(_URLFetcher):
|
|
"""
|
|
For grabbing files from a local directory
|
|
"""
|
|
def hasFile(self, filename):
|
|
url = self._make_full_url(filename)
|
|
ret = os.path.exists(url)
|
|
if not ret:
|
|
logging.debug("local hasFile: Couldn't find %s", url)
|
|
return ret
|
|
|
|
def _grabber(self, url):
|
|
urlobj = file(url, "r")
|
|
size = os.path.getsize(url)
|
|
return urlobj, size
|
|
|
|
|
|
class _MountedURLFetcher(_LocalURLFetcher):
|
|
"""
|
|
Fetcher capable of extracting files from a NFS server
|
|
or loopback mounted file, or local CDROM device
|
|
"""
|
|
_in_test_suite = bool("VIRTINST_TEST_SUITE" in os.environ)
|
|
_mounted = False
|
|
|
|
def prepareLocation(self):
|
|
if self._mounted:
|
|
return
|
|
|
|
if self._in_test_suite:
|
|
self._srcdir = os.environ["VIRTINST_TEST_URL_DIR"]
|
|
else:
|
|
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]
|
|
|
|
logging.debug("mount cmd: %s", cmd)
|
|
if not self._in_test_suite:
|
|
ret = subprocess.call(cmd)
|
|
if ret != 0:
|
|
self.cleanupLocation()
|
|
raise ValueError(_("Mounting location '%s' failed") %
|
|
(self.location))
|
|
|
|
self._mounted = True
|
|
|
|
def cleanupLocation(self):
|
|
if not self._mounted:
|
|
return
|
|
|
|
logging.debug("Cleaning up mount at " + self._srcdir)
|
|
try:
|
|
if not self._in_test_suite:
|
|
cmd = ["/bin/umount", self._srcdir]
|
|
subprocess.call(cmd)
|
|
try:
|
|
os.rmdir(self._srcdir)
|
|
except:
|
|
pass
|
|
finally:
|
|
self._mounted = False
|
|
|
|
|
|
def fetcherForURI(uri, *args, **kwargs):
|
|
if uri.startswith("http://") or uri.startswith("https://"):
|
|
fclass = _HTTPURLFetcher
|
|
elif uri.startswith("ftp://"):
|
|
fclass = _FTPURLFetcher
|
|
elif uri.startswith("nfs:"):
|
|
fclass = _MountedURLFetcher
|
|
elif os.path.isdir(uri):
|
|
# Pointing to a local tree
|
|
fclass = _LocalURLFetcher
|
|
else:
|
|
# Pointing to a path, like an .iso to mount
|
|
fclass = _MountedURLFetcher
|
|
return fclass(uri, *args, **kwargs)
|
|
|
|
|
|
###############################################
|
|
# Helpers for detecting distro from given URL #
|
|
###############################################
|
|
|
|
def _grabTreeinfo(fetcher):
|
|
"""
|
|
See if the URL has treeinfo, and if so return it as a ConfigParser
|
|
object.
|
|
"""
|
|
try:
|
|
tmptreeinfo = fetcher.acquireFile(".treeinfo")
|
|
except ValueError:
|
|
return None
|
|
|
|
try:
|
|
treeinfo = ConfigParser.SafeConfigParser()
|
|
treeinfo.read(tmptreeinfo)
|
|
finally:
|
|
os.unlink(tmptreeinfo)
|
|
|
|
try:
|
|
treeinfo.get("general", "family")
|
|
except ConfigParser.NoSectionError:
|
|
logging.debug("Did not find 'family' section in treeinfo")
|
|
return None
|
|
|
|
return treeinfo
|
|
|
|
|
|
def _distroFromSUSEContent(fetcher, arch, vmtype=None):
|
|
# Parse content file for the 'LABEL' field containing the distribution name
|
|
# None if no content, GenericDistro if unknown label type.
|
|
try:
|
|
cbuf = fetcher.acquireFileContent("content")
|
|
except ValueError:
|
|
return None
|
|
|
|
distribution = None
|
|
distro_version = None
|
|
distro_summary = None
|
|
distro_distro = None
|
|
distro_arch = None
|
|
|
|
lines = cbuf.splitlines()[1:]
|
|
for line in lines:
|
|
if line.startswith("LABEL "):
|
|
distribution = line.split(' ', 1)
|
|
elif line.startswith("DISTRO "):
|
|
distro_distro = line.rsplit(',', 1)
|
|
elif line.startswith("VERSION "):
|
|
distro_version = line.split(' ', 1)
|
|
elif line.startswith("SUMMARY "):
|
|
distro_summary = line.split(' ', 1)
|
|
elif line.startswith("BASEARCHS "):
|
|
distro_arch = line.split(' ', 1)
|
|
elif line.startswith("DEFAULTBASE "):
|
|
distro_arch = line.split(' ', 1)
|
|
elif line.startswith("REPOID "):
|
|
distro_arch = line.rsplit('/', 1)
|
|
if distribution and distro_version and distro_arch:
|
|
break
|
|
|
|
if not distribution:
|
|
if distro_summary:
|
|
distribution = distro_summary
|
|
elif distro_distro:
|
|
distribution = distro_distro
|
|
if distro_arch:
|
|
arch = distro_arch[1].strip()
|
|
# Fix for 13.2 official oss repo
|
|
if arch.find("i586-x86_64") != -1:
|
|
arch = "x86_64"
|
|
else:
|
|
if cbuf.find("x86_64") != -1:
|
|
arch = "x86_64"
|
|
elif cbuf.find("i586") != -1:
|
|
arch = "i586"
|
|
elif cbuf.find("s390x") != -1:
|
|
arch = "s390x"
|
|
|
|
dclass = GenericDistro
|
|
if distribution:
|
|
if re.match(".*SUSE Linux Enterprise Server*", distribution[1]) or \
|
|
re.match(".*SUSE SLES*", distribution[1]):
|
|
dclass = SLESDistro
|
|
if distro_version is None:
|
|
distro_version = ['VERSION', distribution[1].strip().rsplit(' ')[4]]
|
|
elif re.match(".*SUSE Linux Enterprise Desktop*", distribution[1]):
|
|
dclass = SLEDDistro
|
|
if distro_version is None:
|
|
distro_version = ['VERSION', distribution[1].strip().rsplit(' ')[4]]
|
|
elif re.match(".*openSUSE.*", distribution[1]):
|
|
dclass = OpensuseDistro
|
|
if distro_version is None:
|
|
distro_version = ['VERSION', distribution[0].strip().rsplit(':')[4]]
|
|
# For tumbleweed we only have an 8 character date string so default to 13.2
|
|
if distro_version[1] and len(distro_version[1]) == 8:
|
|
distro_version = ['VERSION', '13.2']
|
|
|
|
if distro_version is None:
|
|
return None
|
|
|
|
ob = dclass(fetcher, arch, vmtype)
|
|
if dclass != GenericDistro:
|
|
ob.version_from_content = distro_version
|
|
|
|
# Explictly call this, so we populate os_type/variant info
|
|
ob.isValidStore()
|
|
|
|
return ob
|
|
|
|
|
|
def getDistroStore(guest, fetcher):
|
|
stores = []
|
|
logging.debug("Finding distro store for location=%s", fetcher.location)
|
|
|
|
arch = guest.os.arch
|
|
_type = guest.os.os_type
|
|
|
|
urldistro = None
|
|
if guest.os_variant:
|
|
urldistro = OSDB.lookup_os(guest.os_variant).urldistro
|
|
|
|
treeinfo = _grabTreeinfo(fetcher)
|
|
if not treeinfo:
|
|
dist = _distroFromSUSEContent(fetcher, arch, _type)
|
|
if dist:
|
|
return dist
|
|
|
|
stores = _allstores[:]
|
|
|
|
# If user manually specified an os_distro, bump it's URL class
|
|
# to the top of the list
|
|
if urldistro:
|
|
for store in stores:
|
|
if store.urldistro == urldistro:
|
|
logging.debug("Prioritizing distro store=%s", store)
|
|
stores.remove(store)
|
|
stores.insert(0, store)
|
|
break
|
|
|
|
if treeinfo:
|
|
stores.sort(key=lambda x: not x.uses_treeinfo)
|
|
|
|
for sclass in stores:
|
|
store = sclass(fetcher, arch, _type)
|
|
store.treeinfo = treeinfo
|
|
if store.isValidStore():
|
|
logging.debug("Detected distro name=%s osvariant=%s",
|
|
store.name, store.os_variant)
|
|
return store
|
|
|
|
# No distro was detected. See if the URL even resolves, and if not
|
|
# give the user a hint that maybe they mistyped. This won't always
|
|
# be true since some webservers don't allow directory listing.
|
|
# http://www.redhat.com/archives/virt-tools-list/2014-December/msg00048.html
|
|
extramsg = ""
|
|
if not fetcher.hasFile(""):
|
|
extramsg = (": " +
|
|
_("The URL could not be accessed, maybe you mistyped?"))
|
|
|
|
raise ValueError(
|
|
_("Could not find an installable distribution at '%s'%s\n\n"
|
|
"The location must be the root directory of an install tree.\n"
|
|
"See virt-install man page for various distro examples." %
|
|
(fetcher.location, extramsg)))
|
|
|
|
|
|
##################
|
|
# Distro classes #
|
|
##################
|
|
|
|
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 = None
|
|
urldistro = None
|
|
uses_treeinfo = False
|
|
|
|
# osdict variant value
|
|
os_variant = None
|
|
|
|
_boot_iso_paths = []
|
|
_hvm_kernel_paths = []
|
|
_xen_kernel_paths = []
|
|
version_from_content = None
|
|
|
|
def __init__(self, fetcher, arch, vmtype):
|
|
self.fetcher = fetcher
|
|
self.type = vmtype
|
|
self.arch = arch
|
|
|
|
self.uri = fetcher.location
|
|
|
|
# This is set externally
|
|
self.treeinfo = None
|
|
|
|
def isValidStore(self):
|
|
"""Determine if uri points to a tree of the store's distro"""
|
|
raise NotImplementedError
|
|
|
|
def acquireKernel(self, guest):
|
|
kernelpath = None
|
|
initrdpath = None
|
|
if self.treeinfo:
|
|
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 self.fetcher.hasFile(kpath) and self.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(guest, kernelpath, initrdpath)
|
|
|
|
def acquireBootDisk(self, guest):
|
|
ignore = guest
|
|
|
|
if self.treeinfo:
|
|
return self.fetcher.acquireFile(self._getTreeinfoMedia("boot.iso"))
|
|
|
|
for path in self._boot_iso_paths:
|
|
if self.fetcher.hasFile(path):
|
|
return self.fetcher.acquireFile(path)
|
|
raise RuntimeError(_("Could not find boot.iso in %s tree." %
|
|
self.name))
|
|
|
|
def _check_osvariant_valid(self, os_variant):
|
|
return OSDB.lookup_os(os_variant) is not None
|
|
|
|
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 self._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 _get_method_arg(self):
|
|
return "method"
|
|
|
|
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, filename, regex):
|
|
# Fetch 'filename' and return True/False if it matches the regex
|
|
try:
|
|
content = self.fetcher.acquireFileContent(filename)
|
|
except ValueError:
|
|
return False
|
|
|
|
for line in content.splitlines():
|
|
if re.match(regex, line):
|
|
return True
|
|
|
|
return False
|
|
|
|
def _kernelFetchHelper(self, guest, kernelpath, initrdpath):
|
|
# Simple helper for fetching kernel + initrd and performing
|
|
# cleanup if necessary
|
|
kernel = self.fetcher.acquireFile(kernelpath)
|
|
args = ''
|
|
|
|
if not self.fetcher.location.startswith("/"):
|
|
args += "%s=%s" % (self._get_method_arg(), self.fetcher.location)
|
|
|
|
if guest.installer.extraargs:
|
|
args += " " + guest.installer.extraargs
|
|
|
|
try:
|
|
initrd = self.fetcher.acquireFile(initrdpath)
|
|
return kernel, initrd, args
|
|
except:
|
|
os.unlink(kernel)
|
|
raise
|
|
|
|
|
|
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):
|
|
if self.treeinfo:
|
|
# 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):
|
|
try:
|
|
self._valid_kernel_path = (
|
|
self._getTreeinfoMedia("kernel"),
|
|
self._getTreeinfoMedia("initrd"))
|
|
except (ConfigParser.NoSectionError,
|
|
ConfigParser.NoOptionError), e:
|
|
logging.debug(e)
|
|
|
|
if self.treeinfo.has_section(isoSection):
|
|
try:
|
|
self._valid_iso_path = self.treeinfo.get(isoSection,
|
|
"boot.iso")
|
|
except ConfigParser.NoOptionError, e:
|
|
logging.debug(e)
|
|
|
|
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
|
|
self.fetcher.hasFile(kern) and
|
|
self.fetcher.hasFile(init)):
|
|
self._valid_kernel_path = (kern, init)
|
|
break
|
|
|
|
for iso in self._iso_paths:
|
|
if (self._valid_iso_path is None and
|
|
self.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):
|
|
if self._valid_kernel_path is None:
|
|
raise ValueError(_("Could not find a kernel path for virt type "
|
|
"'%s'" % self.type))
|
|
|
|
return self._kernelFetchHelper(guest,
|
|
self._valid_kernel_path[0],
|
|
self._valid_kernel_path[1])
|
|
|
|
def acquireBootDisk(self, guest):
|
|
if self._valid_iso_path is None:
|
|
raise ValueError(_("Could not find a boot iso path for this tree."))
|
|
|
|
return self.fetcher.acquireFile(self._valid_iso_path)
|
|
|
|
|
|
class RedHatDistro(Distro):
|
|
"""
|
|
Base image store for any Red Hat related distros which have
|
|
a common layout
|
|
"""
|
|
uses_treeinfo = True
|
|
os_variant = "linux"
|
|
_version_number = None
|
|
|
|
_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):
|
|
raise NotImplementedError()
|
|
|
|
def _get_method_arg(self):
|
|
if (self._version_number is not None and
|
|
((self.urldistro is "rhel" and self._version_number >= 7) or
|
|
(self.urldistro is "fedora" and self._version_number >= 19))):
|
|
return "inst.repo"
|
|
return "method"
|
|
|
|
|
|
# Fedora distro check
|
|
class FedoraDistro(RedHatDistro):
|
|
name = "Fedora"
|
|
urldistro = "fedora"
|
|
|
|
def _latestFedoraVariant(self):
|
|
"""
|
|
Search osdict list, find newest fedora version listed
|
|
"""
|
|
latest = OSDB.latest_fedora_version()
|
|
return latest, int(latest[6:])
|
|
|
|
def isValidStore(self):
|
|
if not self.treeinfo:
|
|
return self.fetcher.hasFile("Fedora")
|
|
|
|
if not re.match(".*Fedora.*", self.treeinfo.get("general", "family")):
|
|
return False
|
|
|
|
lateststr, latestnum = self._latestFedoraVariant()
|
|
ver = self.treeinfo.get("general", "version")
|
|
if not ver:
|
|
return False
|
|
|
|
if ver == "development" or ver == "rawhide":
|
|
self._version_number = latestnum
|
|
self.os_variant = lateststr
|
|
return
|
|
|
|
if "_" in ver:
|
|
ver = ver.split("_")[0]
|
|
vernum = int(str(ver).split("-")[0])
|
|
if vernum > latestnum:
|
|
self.os_variant = lateststr
|
|
else:
|
|
self.os_variant = "fedora" + str(vernum)
|
|
|
|
self._version_number = vernum
|
|
return True
|
|
|
|
|
|
# Red Hat Enterprise Linux distro check
|
|
class RHELDistro(RedHatDistro):
|
|
name = "Red Hat Enterprise Linux"
|
|
urldistro = "rhel"
|
|
|
|
def isValidStore(self):
|
|
if self.treeinfo:
|
|
m = re.match(".*Red Hat Enterprise Linux.*",
|
|
self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
|
|
if ret:
|
|
self._variantFromVersion()
|
|
return ret
|
|
|
|
if (self.fetcher.hasFile("Server") or
|
|
self.fetcher.hasFile("Client")):
|
|
self.os_variant = "rhel5"
|
|
return True
|
|
return self.fetcher.hasFile("RedHat")
|
|
|
|
|
|
################################
|
|
# osdict autodetection helpers #
|
|
################################
|
|
|
|
def _parseTreeinfoVersion(self, verstr):
|
|
def _safeint(c):
|
|
try:
|
|
val = int(c)
|
|
except:
|
|
val = 0
|
|
return val
|
|
|
|
version = _safeint(verstr[0])
|
|
update = 0
|
|
|
|
# RHEL has version=5.4, scientific linux=54
|
|
updinfo = verstr.split(".")
|
|
if len(updinfo) > 1:
|
|
update = _safeint(updinfo[1])
|
|
elif len(verstr) > 1:
|
|
update = _safeint(verstr[1])
|
|
|
|
return version, update
|
|
|
|
def _variantFromVersion(self):
|
|
ver = self.treeinfo.get("general", "version")
|
|
name = None
|
|
if self.treeinfo.has_option("general", "name"):
|
|
name = self.treeinfo.get("general", "name")
|
|
if not ver:
|
|
return
|
|
|
|
if name and name.startswith("Red Hat Enterprise Linux Server for ARM"):
|
|
# Kind of a hack, but good enough for the time being
|
|
version = 7
|
|
update = 0
|
|
else:
|
|
version, update = self._parseTreeinfoVersion(ver)
|
|
|
|
self._version_number = version
|
|
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 self._check_osvariant_valid(tryvar):
|
|
update -= 1
|
|
continue
|
|
|
|
ret = tryvar
|
|
break
|
|
|
|
if not ret:
|
|
# Try plain rhel5, rhel6, whatev
|
|
if self._check_osvariant_valid(base):
|
|
ret = base
|
|
|
|
if ret:
|
|
self.os_variant = ret
|
|
|
|
|
|
# CentOS distro check
|
|
class CentOSDistro(RHELDistro):
|
|
name = "CentOS"
|
|
urldistro = "centos"
|
|
|
|
def isValidStore(self):
|
|
if not self.treeinfo:
|
|
return self.fetcher.hasFile("CentOS")
|
|
|
|
m = re.match(".*CentOS.*", self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
if ret:
|
|
self._variantFromVersion()
|
|
if self.os_variant:
|
|
new_variant = self.os_variant.replace("rhel", "centos")
|
|
if self._check_osvariant_valid(new_variant):
|
|
self.os_variant = new_variant
|
|
return ret
|
|
|
|
|
|
# Scientific Linux distro check
|
|
class SLDistro(RHELDistro):
|
|
name = "Scientific Linux"
|
|
urldistro = None
|
|
|
|
_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):
|
|
if self.treeinfo:
|
|
m = re.match(".*Scientific Linux.*",
|
|
self.treeinfo.get("general", "family"))
|
|
ret = (m is not None)
|
|
|
|
if ret:
|
|
self._variantFromVersion()
|
|
return ret
|
|
|
|
return self.fetcher.hasFile("SL")
|
|
|
|
|
|
class SuseDistro(Distro):
|
|
name = "SUSE"
|
|
|
|
_boot_iso_paths = ["boot/boot.iso"]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
Distro.__init__(self, *args, **kwargs)
|
|
if re.match(r'i[4-9]86', self.arch):
|
|
self.arch = 'i386'
|
|
|
|
oldkern = "linux"
|
|
oldinit = "initrd"
|
|
if self.arch == "x86_64":
|
|
oldkern += "64"
|
|
oldinit += "64"
|
|
|
|
if self.arch == "s390x":
|
|
self._hvm_kernel_paths = [("boot/%s/linux" % self.arch,
|
|
"boot/%s/initrd" % self.arch)]
|
|
# No Xen on s390x
|
|
self._xen_kernel_paths = []
|
|
else:
|
|
# 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 _variantFromVersion(self):
|
|
distro_version = self.version_from_content[1].strip()
|
|
version = distro_version.split('.', 1)[0].strip()
|
|
self.os_variant = self.urldistro
|
|
if int(version) >= 10:
|
|
if self.os_variant.startswith(("sles", "sled")):
|
|
sp_version = None
|
|
if len(distro_version.split('.', 1)) == 2:
|
|
sp_version = 'sp' + distro_version.split('.', 1)[1].strip()
|
|
self.os_variant += version
|
|
if sp_version:
|
|
self.os_variant += sp_version
|
|
else:
|
|
self.os_variant += distro_version
|
|
else:
|
|
self.os_variant += "9"
|
|
|
|
def isValidStore(self):
|
|
# self.version_from_content is the VERSION line from the contents file
|
|
if self.version_from_content is None or \
|
|
self.version_from_content[1] is None:
|
|
return False
|
|
|
|
self._variantFromVersion()
|
|
|
|
self.os_variant = self._detect_osdict_from_url()
|
|
|
|
# Reset kernel name for sle11 source on s390x
|
|
if self.arch == "s390x":
|
|
if self.os_variant == "sles11" or self.os_variant == "sled11":
|
|
self._hvm_kernel_paths = [("boot/%s/vmrdr.ikr" % self.arch,
|
|
"boot/%s/initrd" % self.arch)]
|
|
|
|
return True
|
|
|
|
def _get_method_arg(self):
|
|
return "install"
|
|
|
|
################################
|
|
# osdict autodetection helpers #
|
|
################################
|
|
|
|
def _detect_osdict_from_url(self):
|
|
root = "opensuse"
|
|
oses = [n for n in OSDB.list_os() if n.name.startswith(root)]
|
|
|
|
for osobj in oses:
|
|
codename = osobj.name[len(root):]
|
|
if re.search("/%s/" % codename, self.uri):
|
|
return osobj.name
|
|
return self.os_variant
|
|
|
|
|
|
class SLESDistro(SuseDistro):
|
|
urldistro = "sles"
|
|
|
|
|
|
class SLEDDistro(SuseDistro):
|
|
urldistro = "sled"
|
|
|
|
|
|
# Suse image store is harder - we fetch the kernel RPM and a helper
|
|
# RPM and then munge bits together to generate a initrd
|
|
class OpensuseDistro(SuseDistro):
|
|
urldistro = "opensuse"
|
|
|
|
|
|
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"
|
|
urldistro = "debian"
|
|
os_variant = "linux"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
Distro.__init__(self, *args, **kwargs)
|
|
|
|
# Pull the tree's arch out of the URL text
|
|
self._treeArch = "i386"
|
|
for pattern in ["^.*/installer-(\w+)/?$",
|
|
"^.*/daily-images/(\w+)/?$"]:
|
|
arch = re.findall(pattern, self.uri)
|
|
if arch:
|
|
self._treeArch = arch[0]
|
|
break
|
|
|
|
self._url_prefix = 'current/images'
|
|
self._installer_dirname = self.name.lower() + "-installer"
|
|
self._set_media_paths()
|
|
|
|
def _set_media_paths(self):
|
|
self._boot_iso_paths = ["%s/netboot/mini.iso" % self._url_prefix]
|
|
|
|
hvmroot = "%s/netboot/%s/%s/" % (self._url_prefix,
|
|
self._installer_dirname,
|
|
self._treeArch)
|
|
initrd_basename = "initrd.gz"
|
|
kernel_basename = "linux"
|
|
if self._treeArch in ["ppc64el"]:
|
|
kernel_basename = "vmlinux"
|
|
self._hvm_kernel_paths = [
|
|
(hvmroot + kernel_basename, hvmroot + initrd_basename)]
|
|
|
|
xenroot = "%s/netboot/xen/" % self._url_prefix
|
|
self._xen_kernel_paths = [(xenroot + "vmlinuz", xenroot + "initrd.gz")]
|
|
|
|
def isValidStore(self):
|
|
if self.fetcher.hasFile("%s/MANIFEST" % self._url_prefix):
|
|
# For regular trees
|
|
pass
|
|
elif self.fetcher.hasFile("daily/MANIFEST"):
|
|
# For daily trees
|
|
self._url_prefix = "daily"
|
|
self._set_media_paths()
|
|
else:
|
|
return False
|
|
|
|
filename = "%s/MANIFEST" % self._url_prefix
|
|
regex = ".*%s.*" % self._installer_dirname
|
|
if not self._fetchAndMatchRegex(filename, regex):
|
|
logging.debug("Regex didn't match, not a %s distro", self.name)
|
|
return False
|
|
|
|
self.os_variant = self._detect_osdict_from_url()
|
|
return True
|
|
|
|
|
|
################################
|
|
# osdict autodetection helpers #
|
|
################################
|
|
|
|
def _detect_osdict_from_url(self):
|
|
root = self.name.lower()
|
|
oses = [n for n in OSDB.list_os() if n.name.startswith(root)]
|
|
|
|
if self._url_prefix == "daily":
|
|
return oses[0].name
|
|
|
|
for osobj in oses:
|
|
# name looks like 'Debian Sarge'
|
|
if " " not in osobj.label:
|
|
continue
|
|
|
|
codename = osobj.label.lower().split()[1]
|
|
if ("/%s/" % codename) in self.uri:
|
|
return osobj.name
|
|
return self.os_variant
|
|
|
|
|
|
class UbuntuDistro(DebianDistro):
|
|
# http://archive.ubuntu.com/ubuntu/dists/natty/main/installer-amd64/
|
|
name = "Ubuntu"
|
|
urldistro = "ubuntu"
|
|
|
|
def isValidStore(self):
|
|
if self.fetcher.hasFile("%s/MANIFEST" % self._url_prefix):
|
|
# For regular trees
|
|
filename = "%s/MANIFEST" % self._url_prefix
|
|
regex = ".*%s.*" % self._installer_dirname
|
|
elif self.fetcher.hasFile("install/netboot/version.info"):
|
|
# For trees based on ISO's
|
|
self._url_prefix = "install"
|
|
self._set_media_paths()
|
|
filename = "%s/netboot/version.info" % self._url_prefix
|
|
regex = "%s*" % self.name
|
|
else:
|
|
return False
|
|
|
|
if not self._fetchAndMatchRegex(filename, regex):
|
|
logging.debug("Regex didn't match, not a %s distro", self.name)
|
|
return False
|
|
|
|
self.os_variant = self._detect_osdict_from_url()
|
|
return True
|
|
|
|
|
|
class MandrivaDistro(Distro):
|
|
# ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2007.1/x86_64/
|
|
name = "Mandriva/Mageia"
|
|
urldistro = "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):
|
|
# 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 self.fetcher.hasFile("VERSION"):
|
|
return False
|
|
|
|
for name in ["Mandriva", "Mageia"]:
|
|
if self._fetchAndMatchRegex("VERSION", ".*%s.*" % name):
|
|
return True
|
|
|
|
logging.debug("Regex didn't match, not a %s distro", self.name)
|
|
return False
|
|
|
|
|
|
class ALTLinuxDistro(Distro):
|
|
# altlinux doesn't have installable URLs, so this is just for a
|
|
# mounted ISO
|
|
name = "ALT Linux"
|
|
urldistro = "altlinux"
|
|
os_variant = "linux"
|
|
|
|
_boot_iso_paths = [("altinst", "live")]
|
|
_hvm_kernel_paths = [("syslinux/alt0/vmlinuz", "syslinux/alt0/full.cz")]
|
|
_xen_kernel_paths = []
|
|
|
|
def isValidStore(self):
|
|
# Don't support any paravirt installs
|
|
if self.type is not None and self.type != "hvm":
|
|
return False
|
|
|
|
if not self.fetcher.hasFile(".disk/info"):
|
|
return False
|
|
|
|
if self._fetchAndMatchRegex(".disk/info", ".*%s.*" % self.name):
|
|
return True
|
|
|
|
logging.debug("Regex didn't match, not a %s distro", self.name)
|
|
return False
|
|
|
|
|
|
# Build list of all *Distro classes
|
|
def _build_distro_list():
|
|
allstores = []
|
|
for obj in globals().values():
|
|
if type(obj) is type and issubclass(obj, Distro) and obj.name:
|
|
allstores.append(obj)
|
|
|
|
seen_urldistro = []
|
|
for obj in allstores:
|
|
if obj.urldistro and obj.urldistro in seen_urldistro:
|
|
raise RuntimeError("programming error: duplicate urldistro=%s" %
|
|
obj.urldistro)
|
|
seen_urldistro.append(obj.urldistro)
|
|
|
|
# Always stick GenericDistro at the end, since it's a catchall
|
|
allstores.remove(GenericDistro)
|
|
allstores.append(GenericDistro)
|
|
|
|
return allstores
|
|
|
|
_allstores = _build_distro_list()
|