1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
|
#
# Copyright 2006-2009, 2013, 2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import os
from . import urldetect
from . import urlfetcher
from .installerinject import perform_initrd_injections
from .. import progress
from ..devices import DeviceDisk
from ..logger import log
from ..osdict import OSDB
# Enum of the various install media types we can have
(MEDIA_DIR,
MEDIA_ISO,
MEDIA_URL,
MEDIA_KERNEL) = range(1, 5)
def _is_url(url):
return (url.startswith("http://") or
url.startswith("https://") or
url.startswith("ftp://"))
class _LocationData(object):
def __init__(self, os_variant, kernel_pairs, os_media, os_tree):
self.os_variant = os_variant
self.kernel_pairs = kernel_pairs
self.os_media = os_media
self.os_tree = os_tree
self.kernel_url_arg = None
if self.os_variant:
osobj = OSDB.lookup_os(self.os_variant)
self.kernel_url_arg = osobj.get_kernel_url_arg()
class InstallerTreeMedia(object):
"""
Class representing --location Tree media. Can be one of
- A network URL: http://dl.fedoraproject.org/...
- A local directory
- A local .iso file, which will be accessed with isoinfo
"""
@staticmethod
def validate_path(conn, path):
try:
dev = DeviceDisk(conn)
dev.device = dev.DEVICE_CDROM
dev.set_source_path(path)
dev.validate()
return dev.get_source_path()
except Exception as e:
log.debug("Error validating install location", exc_info=True)
if path.startswith("nfs:"):
log.warning("NFS URL installs are no longer supported. "
"Access your install media over an alternate transport "
"like HTTP, or manually mount the NFS share and install "
"from the local directory mount point.")
msg = (_("Validating install media '%(media)s' failed: %(error)s") %
{"media": str(path), "error": str(e)})
raise ValueError(msg) from None
@staticmethod
def get_system_scratchdir(guest):
"""
Return the tmpdir that's accessible by VMs on system libvirt URIs
"""
if guest.conn.is_xen():
return "/var/lib/xen"
return "/var/lib/libvirt/boot"
@staticmethod
def make_scratchdir(guest):
"""
Determine the scratchdir for this URI, create it if necessary.
scratchdir is the directory that's accessible by VMs
"""
user_scratchdir = os.path.join(
guest.conn.get_app_cache_dir(), "boot")
system_scratchdir = InstallerTreeMedia.get_system_scratchdir(guest)
# If we are a session URI, or we don't have access to the system
# scratchdir, make sure the session scratchdir exists and use that.
if (guest.conn.is_unprivileged() or
not os.path.exists(system_scratchdir) or
not os.access(system_scratchdir, os.W_OK)):
os.makedirs(user_scratchdir, 0o751, exist_ok=True)
return user_scratchdir
return system_scratchdir # pragma: no cover
def __init__(self, conn, location, location_kernel, location_initrd,
install_kernel, install_initrd, install_kernel_args):
self.conn = conn
self.location = location
self._location_kernel = location_kernel
self._location_initrd = location_initrd
self._install_kernel = install_kernel
self._install_initrd = install_initrd
self._install_kernel_args = install_kernel_args
self._initrd_injections = []
self._extra_args = []
if location_kernel or location_initrd:
if not location:
raise ValueError(_("location kernel/initrd may only "
"be specified with a location URL/path"))
if not (location_kernel and location_initrd):
raise ValueError(_("location kernel/initrd must "
"be specified as a pair"))
self._cached_fetcher = None
self._cached_data = None
self._tmpfiles = []
if self._install_kernel or self._install_initrd:
self._media_type = MEDIA_KERNEL
elif (not self.conn.is_remote() and
os.path.exists(self.location) and
os.path.isdir(self.location)):
self.location = os.path.abspath(self.location)
self._media_type = MEDIA_DIR
elif _is_url(self.location):
self._media_type = MEDIA_URL
else:
self._media_type = MEDIA_ISO
if (self.conn.is_remote() and
not self._media_type == MEDIA_URL and
not self._media_type == MEDIA_KERNEL):
raise ValueError(_("Cannot access install tree on remote "
"connection: %s") % self.location)
if self._media_type == MEDIA_ISO:
InstallerTreeMedia.validate_path(self.conn, self.location)
########################
# Install preparations #
########################
def _get_fetcher(self, guest, meter):
meter = progress.ensure_meter(meter)
if not self._cached_fetcher:
scratchdir = InstallerTreeMedia.make_scratchdir(guest)
if self._media_type == MEDIA_KERNEL:
self._cached_fetcher = urlfetcher.DirectFetcher(
None, scratchdir, meter)
else:
self._cached_fetcher = urlfetcher.fetcherForURI(
self.location, scratchdir, meter)
self._cached_fetcher.meter = meter
return self._cached_fetcher
def _get_cached_data(self, guest, fetcher):
if self._cached_data:
return self._cached_data
store = None
os_variant = None
os_media = None
os_tree = None
kernel_paths = []
has_location_kernel = bool(
self._location_kernel and self._location_initrd)
if self._media_type == MEDIA_KERNEL:
kernel_paths = [
(self._install_kernel, self._install_initrd)]
else:
store = urldetect.getDistroStore(guest, fetcher,
skip_error=has_location_kernel)
if store:
kernel_paths = store.get_kernel_paths()
os_variant = store.get_osdict_info()
os_media = store.get_os_media()
os_tree = store.get_os_tree()
if has_location_kernel:
kernel_paths = [
(self._location_kernel, self._location_initrd)]
self._cached_data = _LocationData(os_variant, kernel_paths,
os_media, os_tree)
return self._cached_data
def _prepare_kernel_url(self, guest, cache, fetcher):
ignore = guest
def _check_kernel_pairs():
for kpath, ipath in cache.kernel_pairs:
if fetcher.hasFile(kpath) and fetcher.hasFile(ipath):
return kpath, ipath
raise RuntimeError( # pragma: no cover
_("Couldn't find kernel for install tree."))
kernelpath, initrdpath = _check_kernel_pairs()
kernel = fetcher.acquireFile(kernelpath)
self._tmpfiles.append(kernel)
initrd = fetcher.acquireFile(initrdpath)
self._tmpfiles.append(initrd)
perform_initrd_injections(initrd,
self._initrd_injections,
fetcher.scratchdir)
return kernel, initrd
##############
# Public API #
##############
def _prepare_unattended_data(self, scripts):
if not scripts:
return
for script in scripts:
expected_filename = script.get_expected_filename()
scriptpath = script.write()
self._tmpfiles.append(scriptpath)
self._initrd_injections.append((scriptpath, expected_filename))
def _prepare_kernel_url_arg(self, guest, cache):
os_variant = cache.os_variant or guest.osinfo.name
osobj = OSDB.lookup_os(os_variant)
return osobj.get_kernel_url_arg()
def _prepare_kernel_args(self, guest, cache, unattended_scripts):
install_args = None
if unattended_scripts:
args = []
for unattended_script in unattended_scripts:
cmdline = unattended_script.generate_cmdline()
if cmdline:
args.append(cmdline)
install_args = (" ").join(args)
log.debug("Generated unattended cmdline: %s", install_args)
elif self.is_network_url():
kernel_url_arg = self._prepare_kernel_url_arg(guest, cache)
if kernel_url_arg:
install_args = "%s=%s" % (kernel_url_arg, self.location)
if install_args:
self._extra_args.append(install_args)
if self._install_kernel_args:
ret = self._install_kernel_args
else:
ret = " ".join(self._extra_args)
if self._media_type == MEDIA_DIR and not ret:
log.warning(_("Directory tree installs typically do not work "
"unless extra kernel args are passed to point the "
"installer at a network accessible install tree."))
return ret
def prepare(self, guest, meter, unattended_scripts):
fetcher = self._get_fetcher(guest, meter)
cache = self._get_cached_data(guest, fetcher)
self._prepare_unattended_data(unattended_scripts)
kernel_args = self._prepare_kernel_args(guest, cache, unattended_scripts)
kernel, initrd = self._prepare_kernel_url(guest, cache, fetcher)
return kernel, initrd, kernel_args
def cleanup(self, guest):
ignore = guest
for f in self._tmpfiles:
log.debug("Removing %s", str(f))
os.unlink(f)
self._tmpfiles = []
def set_initrd_injections(self, initrd_injections):
self._initrd_injections = initrd_injections
def set_extra_args(self, extra_args):
self._extra_args = extra_args
def cdrom_path(self):
if self._media_type in [MEDIA_ISO]:
return self.location
def is_network_url(self):
if self._media_type in [MEDIA_URL]:
return self.location
def detect_distro(self, guest):
fetcher = self._get_fetcher(guest, None)
cache = self._get_cached_data(guest, fetcher)
return cache.os_variant
def get_os_media(self, guest, meter):
fetcher = self._get_fetcher(guest, meter)
cache = self._get_cached_data(guest, fetcher)
return cache.os_media
def get_os_tree(self, guest, meter):
fetcher = self._get_fetcher(guest, meter)
cache = self._get_cached_data(guest, fetcher)
return cache.os_tree
def requires_internet(self, guest, meter):
if self._media_type in [MEDIA_URL, MEDIA_DIR]:
return True
os_media = self.get_os_media(guest, meter)
if os_media:
return os_media.is_netinst()
return False
|