20
20
# MA 02110-1301 USA.
28
25
import urlgrabber.progress as progress
33
28
import CapabilitiesParser
29
import VirtualGraphics
37
from VirtualDisk import VirtualDisk
38
32
from virtinst import _virtinst as _
42
XEN_SCRATCH="/var/lib/xen"
43
LIBVIRT_SCRATCH="/var/lib/libvirt/boot"
45
class VirtualNetworkInterface(VirtualDevice.VirtualDevice):
47
TYPE_BRIDGE = "bridge"
48
TYPE_VIRTUAL = "network"
51
def __init__(self, macaddr=None, type=TYPE_BRIDGE, bridge=None,
52
network=None, model=None, conn=None):
53
VirtualDevice.VirtualDevice.__init__(self, conn)
55
if macaddr is not None and \
56
__builtin__.type(macaddr) is not __builtin__.type("string"):
57
raise ValueError, "MAC address must be a string."
59
if macaddr is not None:
60
form = re.match("^([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2}$",macaddr)
63
_("MAC address must be of the format AA:BB:CC:DD:EE:FF")
64
self.macaddr = macaddr
67
self.network = network
69
if self.type == self.TYPE_VIRTUAL:
71
raise ValueError, _("A network name was not provided")
72
elif self.type == self.TYPE_BRIDGE:
74
elif self.type == self.TYPE_USER:
77
raise ValueError, _("Unknown network type %s") % (type,)
79
def get_network(self):
81
def set_network(self, newnet):
82
def _is_net_active(netobj):
83
"""Apparently the 'info' command was never hooked up for
84
libvirt virNetwork python apis."""
87
return self.conn.listNetworks().count(netobj.name())
89
if newnet is not None and self.conn:
91
net = self.conn.networkLookupByName(newnet)
92
except libvirt.libvirtError, e:
93
raise ValueError(_("Virtual network '%s' does not exist: %s") \
95
if not _is_net_active(net):
96
raise ValueError(_("Virtual network '%s' has not been "
99
self._network = newnet
100
network = property(get_network, set_network)
102
def is_conflict_net(self, conn):
103
"""is_conflict_net: determines if mac conflicts with others in system
105
returns a two element tuple:
106
first element is True if fatal collision occured
107
second element is a string description of the collision.
108
Non fatal collisions (mac addr collides with inactive guest) will
109
return (False, "description of collision")"""
110
if self.macaddr is None:
112
# get Running Domains
113
ids = conn.listDomainsID()
117
vm = conn.lookupByID(i)
119
except libvirt.libvirtError:
120
# guest probably in process of dieing
121
logging.warn("conflict_net: Failed to lookup domain id %d" % i)
122
# get inactive Domains
124
names = conn.listDefinedDomains()
127
vm = conn.lookupByName(name)
128
inactive_vm.append(vm)
130
# guest probably in process of dieing
131
logging.warn("conflict_net: Failed to lookup domain %d" % name)
133
# get the Host's NIC MACaddress
134
hostdevs = _util.get_host_network_devices()
136
if self.countMACaddr(vms) > 0:
137
return (True, _("The MAC address you entered is already in use by another active virtual machine."))
138
for (dummy, dummy, dummy, dummy, host_macaddr) in hostdevs:
139
if self.macaddr.upper() == host_macaddr.upper():
140
return (True, _("The MAC address you entered conflicts with a device on the physical host."))
141
if self.countMACaddr(inactive_vm) > 0:
142
return (False, _("The MAC address you entered is already in use by another inactive virtual machine."))
145
def setup(self, conn):
146
if self.macaddr is None:
148
self.macaddr = _util.randomMAC(type=conn.getType().lower())
149
if self.is_conflict_net(conn)[1] is not None:
154
ret, msg = self.is_conflict_net(conn)
159
raise RuntimeError(msg)
161
if not self.bridge and self.type == "bridge":
162
self.bridge = _util.default_bridge()
164
def get_xml_config(self):
167
if self.type == self.TYPE_BRIDGE:
168
src_xml = " <source bridge='%s'/>\n" % self.bridge
169
elif self.type == self.TYPE_VIRTUAL:
170
src_xml = " <source network='%s'/>\n" % self.network
173
model_xml = " <model type='%s'/>\n" % self.model
175
return " <interface type='%s'>\n" % self.type + \
177
" <mac address='%s'/>\n" % self.macaddr + \
181
def countMACaddr(self, vms):
188
doc = libxml2.parseDoc(vm.XMLDesc(0))
191
ctx = doc.xpathNewContext()
193
for mac in ctx.xpathEval("/domain/devices/interface/mac"):
194
macaddr = mac.xpathEval("attribute::address")[0].content
195
if macaddr and _util.compareMAC(self.macaddr, macaddr) == 0:
199
ctx.xpathFreeContext()
204
class VirtualAudio(object):
206
MODELS = [ "es1370", "sb16", "pcspk" ]
208
def __init__(self, model):
213
def set_model(self, new_model):
214
if type(new_model) != str:
215
raise ValueError, _("'model' must be a string, "
216
" was '%s'." % type(new_model))
217
if not self.MODELS.count(new_model):
218
raise ValueError, _("Unsupported sound model '%s'" % new_model)
219
self._model = new_model
220
model = property(get_model, set_model)
222
def get_xml_config(self):
223
return " <sound model='%s'/>" % self.model
225
# Back compat class to avoid ABI break
226
class XenNetworkInterface(VirtualNetworkInterface):
229
class VirtualGraphics(object):
234
def __init__(self, type=TYPE_VNC, port=-1, listen=None, passwd=None,
237
if type != self.TYPE_VNC and type != self.TYPE_SDL:
238
raise ValueError(_("Unknown graphics type"))
241
self.set_keymap(keymap)
242
self.set_listen(listen)
243
self.set_passwd(passwd)
247
type = property(get_type)
249
def get_keymap(self):
251
def set_keymap(self, val):
253
val = _util.default_keymap()
254
if not val or type(val) != type("string"):
255
raise ValueError, _("Keymap must be a string")
257
raise ValueError, _("Keymap must be less than 16 characters")
258
if re.match("^[a-zA-Z0-9_-]*$", val) == None:
259
raise ValueError, _("Keymap can only contain alphanumeric, '_', or '-' characters")
261
keymap = property(get_keymap, set_keymap)
265
def set_port(self, val):
268
elif type(val) is not int \
269
or (val != -1 and (val < 5900 or val > 65535)):
270
raise ValueError, _("VNC port must be a number between 5900 and 65535, or -1 for auto allocation")
272
port = property(get_port, set_port)
274
def get_listen(self):
276
def set_listen(self, val):
278
listen = property(get_listen, set_listen)
280
def get_passwd(self):
282
def set_passwd(self, val):
284
passwd = property(get_passwd, set_passwd)
286
def get_xml_config(self):
287
if self._type == self.TYPE_SDL:
288
return " <graphics type='sdl'/>"
293
keymapxml = " keymap='%s'" % self._keymap
295
listenxml = " listen='%s'" % self._listen
297
passwdxml = " passwd='%s'" % self._passwd
298
xml = " <graphics type='vnc' " + \
299
"port='%(port)d'" % { "port" : self._port } + \
300
"%(keymapxml)s" % { "keymapxml" : keymapxml } + \
301
"%(listenxml)s" % { "listenxml" : listenxml } + \
302
"%(passwdxml)s" % { "passwdxml" : passwdxml } + \
306
class Installer(object):
307
def __init__(self, type = "xen", location = None, boot = None,
308
extraargs = None, os_type = None, conn = None):
309
self._location = None
310
self._extraargs = None
313
self._os_type = os_type
315
self._install_disk = None # VirtualDisk that contains install media
321
if not location is None:
322
self.location = location
325
if not extraargs is None:
326
self.extraargs = extraargs
330
def get_install_disk(self):
331
return self._install_disk
332
install_disk = property(get_install_disk)
336
conn = property(get_conn)
340
def set_type(self, val):
342
type = property(get_type, set_type)
344
def get_os_type(self):
346
def set_os_type(self, val):
348
os_type = property(get_os_type, set_os_type)
350
def get_scratchdir(self):
351
if platform.system() == 'SunOS':
353
if self.type == "xen" and os.path.exists(XEN_SCRATCH):
355
if os.geteuid() == 0 and os.path.exists(LIBVIRT_SCRATCH):
356
return LIBVIRT_SCRATCH
358
return os.path.expanduser("~/.virtinst/boot")
359
scratchdir = property(get_scratchdir)
363
def set_cdrom(self, enable):
364
if enable not in [True, False]:
365
raise ValueError, _("Guest.cdrom must be a boolean type")
367
cdrom = property(get_cdrom, set_cdrom)
369
def get_location(self):
370
return self._location
371
def set_location(self, val):
373
location = property(get_location, set_location)
375
# kernel + initrd pair to use for installing as opposed to using a location
378
def set_boot(self, val):
380
if type(val) == tuple:
382
raise ValueError, _("Must pass both a kernel and initrd")
384
self._boot = {"kernel": k, "initrd": i}
385
elif type(val) == dict:
386
if not val.has_key("kernel") or not val.has_key("initrd"):
387
raise ValueError, _("Must pass both a kernel and initrd")
389
elif type(val) == list:
391
raise ValueError, _("Must pass both a kernel and initrd")
392
self._boot = {"kernel": val[0], "initrd": val[1]}
394
raise ValueError, _("Kernel and initrd must be specified by a list, dict, or tuple.")
395
boot = property(get_boot, set_boot)
397
# extra arguments to pass to the guest installer
398
def get_extra_args(self):
399
return self._extraargs
400
def set_extra_args(self, val):
401
self._extraargs = val
402
extraargs = property(get_extra_args, set_extra_args)
406
def _get_osblob_helper(self, isinstall, ishvm, arch=None, loader=None,
407
conn=None, kernel=None, bootdev=None):
409
if not isinstall and not ishvm:
410
return "<bootloader>%s</bootloader>" % _util.pygrub_path(conn)
414
os_type = self.os_type
415
# Hack for older libvirt Xen driver
416
if os_type == "xen" and self.type == "xen":
420
osblob += " <type arch='%s'>%s</type>\n" % (arch, os_type)
422
osblob += " <type>%s</type>\n" % os_type
425
osblob += " <loader>%s</loader>\n" % loader
427
if isinstall and kernel and kernel["kernel"]:
428
osblob += " <kernel>%s</kernel>\n" % _util.xml_escape(kernel["kernel"])
429
osblob += " <initrd>%s</initrd>\n" % _util.xml_escape(kernel["initrd"])
430
osblob += " <cmdline>%s</cmdline>\n" % _util.xml_escape(kernel["extraargs"])
431
elif bootdev is not None:
432
osblob += " <boot dev='%s'/>\n" % bootdev
443
Remove any temporary files retrieved during installation
445
for f in self._tmpfiles:
446
logging.debug("Removing " + f)
450
def prepare(self, guest, meter, distro=None):
452
Fetch any files needed for installation.
453
@param guest: guest instance being installed
455
@param meter: progress meter
456
@type Urlgrabber ProgressMeter
457
@param distro: Name of distro being installed
458
@type C{str} name from Guest os dictionary
460
raise NotImplementedError("Must be implemented in subclass")
462
def post_install_check(self, guest):
464
Attempt to verify that installing to disk was successful.
465
@param guest: guest instance that was installed
469
if _util.is_uri_remote(guest.conn.getURI()):
470
# XXX: Use block peek for this?
473
if len(guest.disks) == 0 \
474
or guest.disks[0].device != VirtualDisk.DEVICE_DISK:
477
if _util.is_vdisk(guest.disks[0].path):
480
# Check for the 0xaa55 signature at the end of the MBR
482
fd = os.open(guest.disks[0].path, os.O_RDONLY)
483
except OSError, (err, msg):
484
logging.debug("Failed to open guest disk: %s" % msg)
485
if err == errno.EACCES and os.geteuid() != 0:
486
return True # non root might not have access to block devices
489
buf = os.read(fd, 512)
491
return (len(buf) == 512 and
492
struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,))
494
37
class Guest(object):
843
416
"""Get the sound device configuration in libvirt XML format."""
845
418
for sound_dev in self.sound_devs:
848
xml += sound_dev.get_xml_config()
419
xml = _util.xml_append(xml, sound_dev.get_xml_config())
422
def _get_hostdev_xml(self):
424
for hostdev in self.hostdevs:
425
xml = _util.xml_append(xml, hostdev.get_xml_config())
851
428
def _get_device_xml(self, install=True):
854
diskxml = self._get_disk_xml(install)
855
netxml = self._get_network_xml()
856
inputxml = self._get_input_xml()
857
graphicsxml = self._get_graphics_xml()
858
soundxml = self._get_sound_xml()
859
for devxml in [diskxml, netxml, inputxml, graphicsxml, soundxml]:
431
xml = _util.xml_append(xml, self._get_disk_xml(install))
432
xml = _util.xml_append(xml, self._get_network_xml())
433
xml = _util.xml_append(xml, self._get_input_xml())
434
xml = _util.xml_append(xml, self._get_graphics_xml())
435
xml = _util.xml_append(xml, self._get_sound_xml())
436
xml = _util.xml_append(xml, self._get_hostdev_xml())
439
def _get_features_xml(self):
441
Return features (pae, acpi, apic) xml (currently only releavnt for FV)
445
def _get_clock_xml(self):
447
Return <clock/> xml (currently only relevant for FV guests)
866
451
def _get_osblob(self, install):
867
452
"""Return os, features, and clock xml (Implemented in subclass)"""
868
raise NotImplementedError
455
osxml = self.installer.get_install_xml(self, install)
459
xml = _util.xml_append(xml,
460
self.installer.get_install_xml(self, install))
461
xml = _util.xml_append(xml, self._get_features_xml())
462
xml = _util.xml_append(xml, self._get_clock_xml())
870
467
def get_config_xml(self, install = True, disk_boot = False):
469
Return the full Guest xml configuration.
471
@param install: Whether we want the 'OS install' configuration or
472
the 'post-install' configuration. (Some Installers,
473
like the LiveCDInstaller may not have an 'install'
475
@type install: C{bool}
476
@param disk_boot: Whether we should boot off the harddisk, regardless
477
of our position in the install process (this is
478
used for 2 stage installs, where the second stage
479
boots off the disk. You probably don't need to touch
481
@type disk_boot: C{bool}
872
485
action = "destroy"