2
# Common code for all guests
4
# Copyright 2006-2009 Red Hat, Inc.
5
# Jeremy Katz <katzj@redhat.com>
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
31
import XMLBuilderDomain
32
from XMLBuilderDomain import _xml_property
33
from virtinst import CapabilitiesParser
34
from virtinst import _virtinst as _
35
from VirtualDisk import VirtualDisk
38
XEN_SCRATCH = "/var/lib/xen"
39
LIBVIRT_SCRATCH = "/var/lib/libvirt/boot"
41
def _get_scratchdir(typ):
43
if platform.system() == 'SunOS':
47
if typ == "xen" and os.path.exists(XEN_SCRATCH):
49
elif os.path.exists(LIBVIRT_SCRATCH):
50
scratch = LIBVIRT_SCRATCH
53
scratch = os.path.expanduser("~/.virtinst/boot")
54
if not os.path.exists(scratch):
55
os.makedirs(scratch, 0751)
56
_util.selinux_restorecon(scratch)
60
class Installer(XMLBuilderDomain.XMLBuilderDomain):
62
Installer classes attempt to encapsulate all the parameters needed
63
to 'install' a guest: essentially, booting the guest with the correct
64
media for the OS install phase (if there is one), and setting up the
65
guest to boot to the correct media for all subsequent runs.
67
Some of the actual functionality:
69
- Determining what type of install media has been requested, and
70
representing it correctly to the Guest
72
- Fetching install kernel/initrd or boot.iso from a URL
74
- Setting the boot device as appropriate depending on whether we
75
are booting into an OS install, or booting post-install
77
Some of the information that the Installer needs to know to accomplish
80
- Install media location (could be a URL, local path, ...)
81
- Virtualization type (parameter 'os_type') ('xen', 'hvm', etc.)
82
- Hypervisor name (parameter 'type') ('qemu', 'kvm', 'xen', etc.)
83
- Guest architecture ('i686', 'x86_64')
86
_dumpxml_xpath = "/domain/os"
87
def __init__(self, type="xen", location=None, boot=None,
88
extraargs=None, os_type=None, conn=None,
89
parsexml=None, parsexmlnode=None, caps=None):
90
XMLBuilderDomain.XMLBuilderDomain.__init__(self, conn, parsexml,
91
parsexmlnode, caps=caps)
95
self._initrd_injections = []
98
self._scratchdir = None
102
self._install_bootconfig = Boot(self.conn)
103
self._bootconfig = Boot(self.conn, parsexml, parsexmlnode)
105
# Devices created/added during the prepare() stage
106
self.install_devices = []
111
# FIXME: Better solution? Skip validating this since we may not be
112
# able to install a VM of the host arch
114
self._arch = self._get_caps().host.arch
120
if not os_type is None:
121
self.os_type = os_type
124
if not location is None:
125
self.location = location
129
self.extraargs = extraargs
135
conn = property(get_conn)
137
def _get_bootconfig(self):
138
return self._bootconfig
139
bootconfig = property(_get_bootconfig)
141
# Hypervisor name (qemu, kvm, xen, lxc, etc.)
144
def set_type(self, val):
146
type = _xml_property(get_type, set_type,
149
# Virtualization type ('xen' == xen paravirt, or 'hvm)
150
def get_os_type(self):
152
def set_os_type(self, val):
153
# Older libvirt back compat: if user specifies 'linux', convert
154
# internally to newer equivalent value 'xen'
158
# XXX: Need to validate this: have some whitelist based on caps?
160
os_type = _xml_property(get_os_type, set_os_type,
165
def set_arch(self, val):
166
# XXX: Sanitize to a consisten value (i368 -> i686)
167
# XXX: Validate against caps
169
arch = _xml_property(get_arch, set_arch,
170
xpath="./os/type/@arch")
172
def _get_machine(self):
174
def _set_machine(self, val):
176
machine = _xml_property(_get_machine, _set_machine,
177
xpath="./os/type/@machine")
179
def _get_loader(self):
181
def _set_loader(self, val):
183
loader = _xml_property(_get_loader, _set_loader,
186
def get_scratchdir(self):
187
if not self.scratchdir_required():
190
if not self._scratchdir:
191
self._scratchdir = _get_scratchdir(self.type)
192
return self._scratchdir
193
scratchdir = property(get_scratchdir)
197
def set_cdrom(self, enable):
198
if enable not in [True, False]:
199
raise ValueError(_("Guest.cdrom must be a boolean type"))
201
cdrom = property(get_cdrom, set_cdrom)
203
def get_location(self):
204
return self._location
205
def set_location(self, val):
207
location = property(get_location, set_location)
209
def get_initrd_injections(self):
210
return self._initrd_injections
211
def set_initrd_injections(self, val):
212
self._initrd_injections = val
213
initrd_injections = property(get_initrd_injections, set_initrd_injections)
215
# kernel + initrd pair to use for installing as opposed to using a location
217
return {"kernel" : self._install_bootconfig.kernel,
218
"initrd" : self._install_bootconfig.initrd}
219
def set_boot(self, val):
222
if type(val) == tuple:
224
raise ValueError(_("Must pass both a kernel and initrd"))
226
boot = {"kernel": k, "initrd": i}
228
elif type(val) == dict:
229
if "kernel" not in val or "initrd" not in val:
230
raise ValueError(_("Must pass both a kernel and initrd"))
233
elif type(val) == list:
235
raise ValueError(_("Must pass both a kernel and initrd"))
236
boot = {"kernel": val[0], "initrd": val[1]}
239
raise ValueError(_("Kernel and initrd must be specified by "
240
"a list, dict, or tuple."))
242
self._install_bootconfig.kernel = boot.get("kernel")
243
self._install_bootconfig.initrd = boot.get("initrd")
245
boot = property(get_boot, set_boot)
247
# extra arguments to pass to the guest installer
248
def get_extra_args(self):
249
return self._install_bootconfig.kernel_args
250
def set_extra_args(self, val):
251
self._install_bootconfig.kernel_args = val
252
extraargs = property(get_extra_args, set_extra_args)
255
# Public helper methods
256
def scratchdir_required(self):
258
Returns true if scratchdir is needed for the passed install parameters.
259
Apps can use this to determine if they should attempt to ensure
260
scratchdir permissions are adequate
265
def _get_bootdev(self, isinstall, guest):
266
raise NotImplementedError
268
def _build_boot_order(self, isinstall, guest):
269
bootorder = [self._get_bootdev(isinstall, guest)]
271
# If guest has an attached disk, always have 'hd' in the boot
272
# list, so disks are marked as bootable/installable (needed for
273
# windows virtio installs, and booting local disk from PXE)
274
for disk in guest.get_devices("disk"):
275
if disk.device == disk.DEVICE_DISK:
276
bootdev = self.bootconfig.BOOT_DEVICE_HARDDISK
277
if bootdev not in bootorder:
278
bootorder.append(bootdev)
283
def _get_osblob_helper(self, guest, isinstall, bootconfig):
284
ishvm = self.os_type == "hvm"
288
if not loader and ishvm and self.type == "xen":
289
loader = "/usr/lib/xen/boot/hvmloader"
291
if not isinstall and not ishvm and not self.bootconfig.kernel:
292
return "<bootloader>%s</bootloader>" % _util.pygrub_path(conn)
296
os_type = self.os_type
297
# Hack for older libvirt: use old value 'linux' for best back compat,
298
# new libvirt will adjust the value accordingly.
299
if os_type == "xen" and self.type == "xen":
304
osblob += " arch='%s'" % arch
306
osblob += " machine='%s'" % self.machine
307
osblob += ">%s</type>\n" % os_type
310
osblob += " <loader>%s</loader>\n" % loader
312
osblob += bootconfig.get_xml_config()
313
osblob = _util.xml_append(osblob, " </os>")
320
def _get_xml_config(self, guest, isinstall):
322
Generate the portion of the guest xml that determines boot devices
323
and parameters. (typically the <os></os> block)
325
@param guest: Guest instance we are installing
326
@type guest: L{Guest}
327
@param isinstall: Whether we want xml for the 'install' phase or the
328
'post-install' phase.
329
@type isinstall: C{bool}
332
bootconfig = self._install_bootconfig
334
bootconfig = self.bootconfig
336
if isinstall and not self.has_install_phase():
339
bootorder = self._build_boot_order(isinstall, guest)
340
bootconfig = copy.copy(bootconfig)
341
if not bootconfig.bootorder:
342
bootconfig.bootorder = bootorder
344
return self._get_osblob_helper(guest, isinstall, bootconfig)
346
def has_install_phase(self):
348
Return True if the requested setup is actually installing an OS
349
into the guest. Things like LiveCDs, Import, or a manually specified
350
bootorder do not have an install phase.
356
Remove any temporary files retrieved during installation
358
for f in self._tmpfiles:
359
logging.debug("Removing " + f)
362
self.install_devices = []
364
def prepare(self, guest, meter):
366
Fetch any files needed for installation.
367
@param guest: guest instance being installed
369
@param meter: progress meter
370
@type Urlgrabber ProgressMeter
372
raise NotImplementedError("Must be implemented in subclass")
374
def post_install_check(self, guest):
376
Attempt to verify that installing to disk was successful.
377
@param guest: guest instance that was installed
381
if _util.is_uri_remote(guest.conn.getURI()):
382
# XXX: Use block peek for this?
385
if (len(guest.disks) == 0 or
386
guest.disks[0].device != VirtualDisk.DEVICE_DISK):
389
disk = guest.disks[0]
391
if _util.is_vdisk(disk.path):
394
if (disk.driver_type and
395
disk.driver_type not in [disk.DRIVER_TAP_RAW,
396
disk.DRIVER_QEMU_RAW]):
397
# Might be a non-raw format
400
# Check for the 0xaa55 signature at the end of the MBR
402
fd = os.open(disk.path, os.O_RDONLY)
403
except OSError, (err, msg):
404
logging.debug("Failed to open guest disk: %s" % msg)
405
if err == errno.EACCES and os.geteuid() != 0:
406
return True # non root might not have access to block devices
410
buf = os.read(fd, 512)
412
return (len(buf) == 512 and
413
struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,))
415
def detect_distro(self):
417
Attempt to detect the distro for the Installer's 'location'. If
418
an error is encountered in the detection process (or if detection
419
is not relevant for the Installer type), (None, None) is returned
421
@returns: (distro type, distro variant) tuple
425
def guest_from_installer(self):
427
Return a L{Guest} instance wrapping the current installer.
429
If all the appropriate values are present in the installer
430
(conn, type, os_type, arch, machine), we have everything we need
431
to determine what L{Guest} class is expected and what default values
432
to pass it. This is a convenience method to save the API user from
433
having to enter all these known details twice.
437
raise ValueError(_("A connection must be specified."))
439
guest, domain = CapabilitiesParser.guest_lookup(conn=self.conn,
440
caps=self._get_caps(),
441
os_type=self.os_type,
444
machine=self.machine)
446
if self.os_type not in ["xen", "hvm"]:
447
raise ValueError(_("No 'Guest' class for virtualization type '%s'"
450
gobj = virtinst.Guest(installer=self, connection=self.conn)
451
gobj.arch = guest.arch
452
gobj.emulator = domain.emulator
453
self.loader = domain.loader
458
Installer.get_install_xml = Installer.get_xml_config