~ubuntu-branches/ubuntu/raring/virtinst/raring-proposed

« back to all changes in this revision

Viewing changes to virtinst/Guest.py

  • Committer: Bazaar Package Importer
  • Author(s): Guido Günther
  • Date: 2009-03-22 20:13:27 UTC
  • mto: (1.4.1 sid)
  • mto: This revision was merged to the branch mainline in revision 19.
  • Revision ID: james.westby@ubuntu.com-20090322201327-5ch3kqxe772e23zx
Tags: upstream-0.400.3
Import upstream version 0.400.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#
2
2
# Common code for all guests
3
3
#
4
 
# Copyright 2006-2007  Red Hat, Inc.
 
4
# Copyright 2006-2009  Red Hat, Inc.
5
5
# Jeremy Katz <katzj@redhat.com>
6
6
#
7
7
# This program is free software; you can redistribute it and/or modify
20
20
# MA 02110-1301 USA.
21
21
 
22
22
import os, os.path
23
 
import errno
24
 
import struct
25
23
import time
26
24
import re
27
 
import libxml2
28
25
import urlgrabber.progress as progress
29
26
import _util
30
27
import libvirt
31
 
import platform
32
 
import __builtin__
33
28
import CapabilitiesParser
34
 
import VirtualDevice
 
29
import VirtualGraphics
35
30
 
36
31
import osdict
37
 
from VirtualDisk import VirtualDisk
38
32
from virtinst import _virtinst as _
39
 
 
40
33
import logging
41
 
 
42
 
XEN_SCRATCH="/var/lib/xen"
43
 
LIBVIRT_SCRATCH="/var/lib/libvirt/boot"
44
 
 
45
 
class VirtualNetworkInterface(VirtualDevice.VirtualDevice):
46
 
 
47
 
    TYPE_BRIDGE  = "bridge"
48
 
    TYPE_VIRTUAL = "network"
49
 
    TYPE_USER    = "user"
50
 
 
51
 
    def __init__(self, macaddr=None, type=TYPE_BRIDGE, bridge=None,
52
 
                 network=None, model=None, conn=None):
53
 
        VirtualDevice.VirtualDevice.__init__(self, conn)
54
 
 
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."
58
 
 
59
 
        if macaddr is not None:
60
 
            form = re.match("^([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2}$",macaddr)
61
 
            if form is None:
62
 
                raise ValueError, \
63
 
                    _("MAC address must be of the format AA:BB:CC:DD:EE:FF")
64
 
        self.macaddr = macaddr
65
 
        self.type = type
66
 
        self.bridge = bridge
67
 
        self.network = network
68
 
        self.model = model
69
 
        if self.type == self.TYPE_VIRTUAL:
70
 
            if network is None:
71
 
                raise ValueError, _("A network name was not provided")
72
 
        elif self.type == self.TYPE_BRIDGE:
73
 
            pass
74
 
        elif self.type == self.TYPE_USER:
75
 
            pass
76
 
        else:
77
 
            raise ValueError, _("Unknown network type %s") % (type,)
78
 
 
79
 
    def get_network(self):
80
 
        return self._network
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."""
85
 
            if not self.conn:
86
 
                return True
87
 
            return self.conn.listNetworks().count(netobj.name())
88
 
 
89
 
        if newnet is not None and self.conn:
90
 
            try:
91
 
                net = self.conn.networkLookupByName(newnet)
92
 
            except libvirt.libvirtError, e:
93
 
                raise ValueError(_("Virtual network '%s' does not exist: %s") \
94
 
                                   % (newnet, str(e)))
95
 
            if not _is_net_active(net):
96
 
                raise ValueError(_("Virtual network '%s' has not been "
97
 
                                   "started.") % newnet)
98
 
 
99
 
        self._network = newnet
100
 
    network = property(get_network, set_network)
101
 
 
102
 
    def is_conflict_net(self, conn):
103
 
        """is_conflict_net: determines if mac conflicts with others in system
104
 
 
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:
111
 
            return (False, None)
112
 
        # get Running Domains
113
 
        ids = conn.listDomainsID()
114
 
        vms = []
115
 
        for i in ids:
116
 
            try:
117
 
                vm = conn.lookupByID(i)
118
 
                vms.append(vm)
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
123
 
        inactive_vm = []
124
 
        names = conn.listDefinedDomains()
125
 
        for name in names:
126
 
            try:
127
 
                vm = conn.lookupByName(name)
128
 
                inactive_vm.append(vm)
129
 
            except:
130
 
                # guest probably in process of dieing
131
 
                logging.warn("conflict_net: Failed to lookup domain %d" % name)
132
 
 
133
 
        # get the Host's NIC MACaddress
134
 
        hostdevs = _util.get_host_network_devices()
135
 
 
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."))
143
 
        return (False, None)
144
 
 
145
 
    def setup(self, conn):
146
 
        if self.macaddr is None:
147
 
            while 1:
148
 
                self.macaddr = _util.randomMAC(type=conn.getType().lower())
149
 
                if self.is_conflict_net(conn)[1] is not None:
150
 
                    continue
151
 
                else:
152
 
                    break
153
 
        else:
154
 
            ret, msg = self.is_conflict_net(conn)
155
 
            if msg is not None:
156
 
                if ret is False:
157
 
                    logging.warning(msg)
158
 
                else:
159
 
                    raise RuntimeError(msg)
160
 
 
161
 
        if not self.bridge and self.type == "bridge":
162
 
            self.bridge = _util.default_bridge()
163
 
 
164
 
    def get_xml_config(self):
165
 
        src_xml = ""
166
 
        model_xml = ""
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
171
 
 
172
 
        if self.model:
173
 
            model_xml = "      <model type='%s'/>\n" % self.model
174
 
 
175
 
        return "    <interface type='%s'>\n" % self.type + \
176
 
               src_xml + \
177
 
               "      <mac address='%s'/>\n" % self.macaddr + \
178
 
               model_xml + \
179
 
               "    </interface>"
180
 
 
181
 
    def countMACaddr(self, vms):
182
 
        if not self.macaddr:
183
 
            return
184
 
        count = 0
185
 
        for vm in vms:
186
 
            doc = None
187
 
            try:
188
 
                doc = libxml2.parseDoc(vm.XMLDesc(0))
189
 
            except:
190
 
                continue
191
 
            ctx = doc.xpathNewContext()
192
 
            try:
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:
196
 
                        count += 1
197
 
            finally:
198
 
                if ctx is not None:
199
 
                    ctx.xpathFreeContext()
200
 
                if doc is not None:
201
 
                    doc.freeDoc()
202
 
        return count
203
 
 
204
 
class VirtualAudio(object):
205
 
 
206
 
    MODELS = [ "es1370", "sb16", "pcspk" ]
207
 
 
208
 
    def __init__(self, model):
209
 
        self.model = model
210
 
 
211
 
    def get_model(self):
212
 
        return 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)
221
 
 
222
 
    def get_xml_config(self):
223
 
        return "    <sound model='%s'/>" % self.model
224
 
 
225
 
# Back compat class to avoid ABI break
226
 
class XenNetworkInterface(VirtualNetworkInterface):
227
 
    pass
228
 
 
229
 
class VirtualGraphics(object):
230
 
 
231
 
    TYPE_SDL = "sdl"
232
 
    TYPE_VNC = "vnc"
233
 
 
234
 
    def __init__(self, type=TYPE_VNC, port=-1, listen=None, passwd=None,
235
 
                 keymap=None):
236
 
 
237
 
        if type != self.TYPE_VNC and type != self.TYPE_SDL:
238
 
            raise ValueError(_("Unknown graphics type"))
239
 
        self._type   = type
240
 
        self.set_port(port)
241
 
        self.set_keymap(keymap)
242
 
        self.set_listen(listen)
243
 
        self.set_passwd(passwd)
244
 
 
245
 
    def get_type(self):
246
 
        return self._type
247
 
    type = property(get_type)
248
 
 
249
 
    def get_keymap(self):
250
 
        return self._keymap
251
 
    def set_keymap(self, val):
252
 
        if not val:
253
 
            val = _util.default_keymap()
254
 
        if not val or type(val) != type("string"):
255
 
            raise ValueError, _("Keymap must be a string")
256
 
        if len(val) > 16:
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")
260
 
        self._keymap = val
261
 
    keymap = property(get_keymap, set_keymap)
262
 
 
263
 
    def get_port(self):
264
 
        return self._port
265
 
    def set_port(self, val):
266
 
        if val is None:
267
 
            val = -1
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")
271
 
        self._port = val
272
 
    port = property(get_port, set_port)
273
 
 
274
 
    def get_listen(self):
275
 
        return self._listen
276
 
    def set_listen(self, val):
277
 
        self._listen = val
278
 
    listen = property(get_listen, set_listen)
279
 
 
280
 
    def get_passwd(self):
281
 
        return self._passwd
282
 
    def set_passwd(self, val):
283
 
        self._passwd = val
284
 
    passwd = property(get_passwd, set_passwd)
285
 
 
286
 
    def get_xml_config(self):
287
 
        if self._type == self.TYPE_SDL:
288
 
            return "    <graphics type='sdl'/>"
289
 
        keymapxml = ""
290
 
        listenxml = ""
291
 
        passwdxml = ""
292
 
        if self.keymap:
293
 
            keymapxml = " keymap='%s'" % self._keymap
294
 
        if self.listen:
295
 
            listenxml = " listen='%s'" % self._listen
296
 
        if self.passwd:
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 } + \
303
 
                   "/>"
304
 
        return xml
305
 
 
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
311
 
        self._boot = None
312
 
        self._cdrom = False
313
 
        self._os_type = os_type
314
 
        self._conn = conn
315
 
        self._install_disk = None   # VirtualDisk that contains install media
316
 
 
317
 
        if type is None:
318
 
            type = "xen"
319
 
        self.type = type
320
 
 
321
 
        if not location is None:
322
 
            self.location = location
323
 
        if not boot is None:
324
 
            self.boot = boot
325
 
        if not extraargs is None:
326
 
            self.extraargs = extraargs
327
 
 
328
 
        self._tmpfiles = []
329
 
 
330
 
    def get_install_disk(self):
331
 
        return self._install_disk
332
 
    install_disk = property(get_install_disk)
333
 
 
334
 
    def get_conn(self):
335
 
        return self._conn
336
 
    conn = property(get_conn)
337
 
 
338
 
    def get_type(self):
339
 
        return self._type
340
 
    def set_type(self, val):
341
 
        self._type = val
342
 
    type = property(get_type, set_type)
343
 
 
344
 
    def get_os_type(self):
345
 
        return self._os_type
346
 
    def set_os_type(self, val):
347
 
        self._os_type = val
348
 
    os_type = property(get_os_type, set_os_type)
349
 
 
350
 
    def get_scratchdir(self):
351
 
        if platform.system() == 'SunOS':
352
 
            return '/var/tmp'
353
 
        if self.type == "xen" and os.path.exists(XEN_SCRATCH):
354
 
            return XEN_SCRATCH
355
 
        if os.geteuid() == 0 and os.path.exists(LIBVIRT_SCRATCH):
356
 
            return LIBVIRT_SCRATCH
357
 
        else:
358
 
            return os.path.expanduser("~/.virtinst/boot")
359
 
    scratchdir = property(get_scratchdir)
360
 
 
361
 
    def get_cdrom(self):
362
 
        return self._cdrom
363
 
    def set_cdrom(self, enable):
364
 
        if enable not in [True, False]:
365
 
            raise ValueError, _("Guest.cdrom must be a boolean type")
366
 
        self._cdrom = enable
367
 
    cdrom = property(get_cdrom, set_cdrom)
368
 
 
369
 
    def get_location(self):
370
 
        return self._location
371
 
    def set_location(self, val):
372
 
        self._location = val
373
 
    location = property(get_location, set_location)
374
 
 
375
 
    # kernel + initrd pair to use for installing as opposed to using a location
376
 
    def get_boot(self):
377
 
        return self._boot
378
 
    def set_boot(self, val):
379
 
        self.cdrom = False
380
 
        if type(val) == tuple:
381
 
            if len(val) != 2:
382
 
                raise ValueError, _("Must pass both a kernel and initrd")
383
 
            (k, i) = val
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")
388
 
            self._boot = val
389
 
        elif type(val) == list:
390
 
            if len(val) != 2:
391
 
                raise ValueError, _("Must pass both a kernel and initrd")
392
 
            self._boot = {"kernel": val[0], "initrd": val[1]}
393
 
        else:
394
 
            raise ValueError, _("Kernel and initrd must be specified by a list, dict, or tuple.")
395
 
    boot = property(get_boot, set_boot)
396
 
 
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)
403
 
 
404
 
    # Private methods
405
 
 
406
 
    def _get_osblob_helper(self, isinstall, ishvm, arch=None, loader=None,
407
 
                           conn=None, kernel=None, bootdev=None):
408
 
        osblob = ""
409
 
        if not isinstall and not ishvm:
410
 
            return "<bootloader>%s</bootloader>" % _util.pygrub_path(conn)
411
 
 
412
 
        osblob = "<os>\n"
413
 
 
414
 
        os_type = self.os_type
415
 
        # Hack for older libvirt Xen driver
416
 
        if os_type == "xen" and self.type == "xen":
417
 
            os_type = "linux"
418
 
 
419
 
        if arch:
420
 
            osblob += "    <type arch='%s'>%s</type>\n" % (arch, os_type)
421
 
        else:
422
 
            osblob += "    <type>%s</type>\n" % os_type
423
 
 
424
 
        if loader:
425
 
            osblob += "    <loader>%s</loader>\n" % loader
426
 
 
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
433
 
 
434
 
        osblob += "  </os>"
435
 
 
436
 
        return osblob
437
 
 
438
 
 
439
 
    # Method definitions
440
 
 
441
 
    def cleanup(self):
442
 
        """
443
 
        Remove any temporary files retrieved during installation
444
 
        """
445
 
        for f in self._tmpfiles:
446
 
            logging.debug("Removing " + f)
447
 
            os.unlink(f)
448
 
        self._tmpfiles = []
449
 
 
450
 
    def prepare(self, guest, meter, distro=None):
451
 
        """
452
 
        Fetch any files needed for installation.
453
 
        @param guest: guest instance being installed
454
 
        @type L{Guest}
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
459
 
        """
460
 
        raise NotImplementedError("Must be implemented in subclass")
461
 
 
462
 
    def post_install_check(self, guest):
463
 
        """
464
 
        Attempt to verify that installing to disk was successful.
465
 
        @param guest: guest instance that was installed
466
 
        @type L{Guest}
467
 
        """
468
 
 
469
 
        if _util.is_uri_remote(guest.conn.getURI()):
470
 
            # XXX: Use block peek for this?
471
 
            return True
472
 
 
473
 
        if len(guest.disks) == 0 \
474
 
           or guest.disks[0].device != VirtualDisk.DEVICE_DISK:
475
 
            return True
476
 
 
477
 
        if _util.is_vdisk(guest.disks[0].path):
478
 
            return True
479
 
 
480
 
        # Check for the 0xaa55 signature at the end of the MBR
481
 
        try:
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
487
 
            else:
488
 
                raise
489
 
        buf = os.read(fd, 512)
490
 
        os.close(fd)
491
 
        return (len(buf) == 512 and
492
 
                struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,))
 
34
import signal
 
35
 
493
36
 
494
37
class Guest(object):
495
38
 
513
56
        return Guest._OS_TYPES[type]["variants"][variant]["label"]
514
57
    get_os_variant_label = staticmethod(get_os_variant_label)
515
58
 
 
59
 
516
60
    def __init__(self, type=None, connection=None, hypervisorURI=None,
517
61
                 installer=None):
518
62
        # We specifically ignore the 'type' parameter here, since
523
67
        self._uuid = None
524
68
        self._memory = None
525
69
        self._maxmemory = None
526
 
        self._vcpus = None
 
70
        self._vcpus = 1
527
71
        self._cpuset = None
528
72
        self._graphics_dev = None
 
73
        self._consolechild = None
529
74
 
530
75
        self._os_type = None
531
76
        self._os_variant = None
534
79
        self.disks = []
535
80
        self.nics = []
536
81
        self.sound_devs = []
 
82
        self.hostdevs = []
537
83
 
538
84
        # Device lists to use/alter during install process
539
85
        self._install_disks = []
540
86
        self._install_nics = []
541
87
 
 
88
        # The libvirt virDomain object we 'Create'
542
89
        self.domain = None
 
90
 
 
91
        # Default disk target prefix ('hd' or 'xvd'). Set in subclass
 
92
        self.disknode = None
 
93
 
543
94
        self.conn = connection
544
95
        if self.conn == None:
545
96
            logging.debug("No conn passed to Guest, opening URI '%s'" % \
548
99
        if self.conn == None:
549
100
            raise RuntimeError, _("Unable to connect to hypervisor, aborting "
550
101
                                  "installation!")
 
102
 
551
103
        self._caps = CapabilitiesParser.parse(self.conn.getCapabilities())
552
104
 
553
 
        self.disknode = None # this needs to be set in the subclass
554
105
 
555
106
    def get_installer(self):
556
107
        return self._installer
557
108
    def set_installer(self, val):
 
109
        # FIXME: Make sure this is valid: it's pretty fundamental to
 
110
        # working operation. Should we even allow it to be changed?
558
111
        self._installer = val
559
112
    installer = property(get_installer, set_installer)
560
113
 
561
 
 
562
 
    def get_type(self):
563
 
        return self._installer.type
564
 
    def set_type(self, val):
565
 
        self._installer.type = val
566
 
    type = property(get_type, set_type)
567
 
 
568
 
 
569
114
    # Domain name of the guest
570
115
    def get_name(self):
571
116
        return self._name
572
117
    def set_name(self, val):
573
 
        if type(val) is not type("string") or len(val) > 50 or len(val) == 0:
574
 
            raise ValueError, _("System name must be a string greater than 0 and no more than 50 characters")
575
 
        if re.match("^[0-9]+$", val):
576
 
            raise ValueError, _("System name must not be only numeric characters")
577
 
        if re.match("^[A-Za-z0-9_.:/+-]+$", val) == None:
578
 
            raise ValueError, _("System name can only contain: alphanumeric "
579
 
                                "'_', '.', ':', '+', or '-' characters")
580
 
        self._name = val
 
118
        _util.validate_name(_("Guest"), val)
 
119
        try:
 
120
            self.conn.lookupByName(val)
 
121
        except:
 
122
            # Name not found
 
123
            self._name = val
 
124
            return
 
125
        raise ValueError(_("Guest name '%s' is already in use.") % val)
581
126
    name = property(get_name, set_name)
582
127
 
583
 
 
584
128
    # Memory allocated to the guest.  Should be given in MB
585
129
    def get_memory(self):
586
130
        return self._memory
587
131
    def set_memory(self, val):
588
132
        if (type(val) is not type(1) or val <= 0):
589
 
            raise ValueError, _("Memory value must be an integer greater than 0")
 
133
            raise ValueError, _("Memory value must be an integer greater "
 
134
                                "than 0")
590
135
        self._memory = val
591
136
        if self._maxmemory is None or self._maxmemory < val:
592
137
            self._maxmemory = val
597
142
        return self._maxmemory
598
143
    def set_maxmemory(self, val):
599
144
        if (type(val) is not type(1) or val <= 0):
600
 
            raise ValueError, _("Max Memory value must be an integer greater than 0")
 
145
            raise ValueError, _("Max Memory value must be an integer greater "
 
146
                                "than 0")
601
147
        self._maxmemory = val
602
148
    maxmemory = property(get_maxmemory, set_maxmemory)
603
149
 
604
 
 
605
150
    # UUID for the guest
606
151
    def get_uuid(self):
607
152
        return self._uuid
610
155
        self._uuid = val
611
156
    uuid = property(get_uuid, set_uuid)
612
157
 
613
 
 
614
158
    # number of vcpus for the guest
615
159
    def get_vcpus(self):
616
160
        return self._vcpus
619
163
        if type(val) is not int or val < 1:
620
164
            raise ValueError, _("Number of vcpus must be a postive integer.")
621
165
        if val > maxvcpus:
622
 
            raise ValueError, \
623
 
                  _("Number of vcpus must be no greater than %d for this vm type.") % maxvcpus
 
166
            raise ValueError, _("Number of vcpus must be no greater than %d "
 
167
                                "for this vm type.") % maxvcpus
624
168
        self._vcpus = val
625
169
    vcpus = property(get_vcpus, set_vcpus)
626
170
 
631
175
        if type(val) is not type("string") or len(val) == 0:
632
176
            raise ValueError, _("cpuset must be string")
633
177
        if re.match("^[0-9,-]*$", val) is None:
634
 
            raise ValueError, _("cpuset can only contain numeric, ',', or '-' characters")
 
178
            raise ValueError, _("cpuset can only contain numeric, ',', or "
 
179
                                "'-' characters")
635
180
 
636
181
        pcpus = _util.get_phy_cpus(self.conn)
637
182
        for c in val.split(','):
640
185
                if int(x) > int(y):
641
186
                    raise ValueError, _("cpuset contains invalid format.")
642
187
                if int(x) >= pcpus or int(y) >= pcpus:
643
 
                    raise ValueError, _("cpuset's pCPU numbers must be less than pCPUs.")
 
188
                    raise ValueError, _("cpuset's pCPU numbers must be less "
 
189
                                        "than pCPUs.")
644
190
            else:
645
191
                if int(c) >= pcpus:
646
 
                    raise ValueError, _("cpuset's pCPU numbers must be less than pCPUs.")
 
192
                    raise ValueError, _("cpuset's pCPU numbers must be less "
 
193
                                        "than pCPUs.")
647
194
        self._cpuset = val
648
195
    cpuset = property(get_cpuset, set_cpuset)
649
196
 
653
200
        self._graphics_dev = val
654
201
    graphics_dev = property(get_graphics_dev, set_graphics_dev)
655
202
 
 
203
    # GAH! - installer.os_type = "hvm" or "xen" (aka xen paravirt)
 
204
    #        guest.os_type     = "Solaris", "Windows", "Linux"
 
205
    # FIXME: We should really rename this property to something else,
 
206
    #        change it throughout the codebase for readability sake, but
 
207
    #        maintain back compat.
656
208
    def get_os_type(self):
657
209
        return self._os_type
658
210
    def set_os_type(self, val):
659
211
        if type(val) is not str:
660
212
            raise ValueError(_("OS type must be a string."))
661
213
        val = val.lower()
 
214
 
662
215
        if self._OS_TYPES.has_key(val):
 
216
            if self._os_type == val:
 
217
                # No change, don't invalidate variant
 
218
                return
 
219
 
 
220
            # Invalidate variant, since it may not apply to the new os type
663
221
            self._os_type = val
664
 
            # Invalidate variant, since it may not apply to the new os type
665
222
            self._os_variant = None
666
223
        else:
667
224
            raise ValueError, _("OS type '%s' does not exist in our "
693
250
            raise ValueError, _("Unknown OS variant '%s'" % val)
694
251
    os_variant = property(get_os_variant, set_os_variant)
695
252
 
 
253
    # Get the current variants 'distro' tag: 'rhel', 'fedora', etc.
 
254
    def get_os_distro(self):
 
255
        return self._lookup_osdict_key("distro")
 
256
    os_distro = property(get_os_distro)
 
257
 
696
258
 
697
259
    # DEPRECATED PROPERTIES
 
260
 
698
261
    # Deprecated: Should set graphics_dev.keymap directly
699
262
    def get_keymap(self):
700
263
        if self._graphics_dev is None:
750
313
        if enabled not in (True, False):
751
314
            raise ValueError, _("Graphics enabled must be True or False")
752
315
 
753
 
        if enabled == True:
754
 
            gdev = VirtualGraphics(type=gtype)
 
316
        if enabled:
 
317
            gdev = VirtualGraphics.VirtualGraphics(type=gtype)
755
318
            if port:
756
319
                gdev.port = port
757
320
            if keymap:
760
323
 
761
324
    graphics = property(get_graphics, set_graphics)
762
325
 
 
326
 
 
327
    # Properties that are mapped through to the Installer
 
328
 
 
329
    # Hypervisor name (qemu, xen, kvm, etc.)
 
330
    def get_type(self):
 
331
        return self._installer.type
 
332
    def set_type(self, val):
 
333
        self._installer.type = val
 
334
    type = property(get_type, set_type)
 
335
 
 
336
    def get_arch(self):
 
337
        return self.installer.arch
 
338
    def set_arch(self, val):
 
339
        self.installer.arch = val
 
340
    arch = property(get_arch, set_arch)
 
341
 
763
342
    # Deprecated: Should be called from the installer directly
764
343
    def get_location(self):
765
344
        return self._installer.location
803
382
    cdrom = property(get_cdrom, set_cdrom)
804
383
    # END DEPRECATED PROPERTIES
805
384
 
806
 
    def _create_devices(self,progresscb):
807
 
        """Ensure that devices are setup"""
808
 
        for disk in self._install_disks:
809
 
            disk.setup(progresscb)
810
 
        for nic in self._install_nics:
811
 
            nic.setup(self.conn)
 
385
 
 
386
    # Private xml building methods
812
387
 
813
388
    def _get_disk_xml(self, install=True):
814
389
        """Return xml for disk devices (Must be implemented in subclass)"""
816
391
 
817
392
    def _get_network_xml(self):
818
393
        """Get the network config in the libvirt XML format"""
819
 
        ret = ""
 
394
        xml = ""
820
395
        for n in self._install_nics:
821
 
            if ret:
822
 
                ret += "\n"
823
 
            ret += n.get_xml_config()
824
 
        return ret
 
396
            xml = _util.xml_append(xml, n.get_xml_config())
 
397
        return xml
825
398
 
826
399
    def _get_graphics_xml(self):
827
400
        """Get the graphics config in the libvirt XML format."""
843
416
        """Get the sound device configuration in libvirt XML format."""
844
417
        xml = ""
845
418
        for sound_dev in self.sound_devs:
846
 
            if xml != "":
847
 
                xml += "\n"
848
 
            xml += sound_dev.get_xml_config()
 
419
            xml = _util.xml_append(xml, sound_dev.get_xml_config())
 
420
        return xml
 
421
 
 
422
    def _get_hostdev_xml(self):
 
423
        xml = ""
 
424
        for hostdev in self.hostdevs:
 
425
            xml = _util.xml_append(xml, hostdev.get_xml_config())
849
426
        return xml
850
427
 
851
428
    def _get_device_xml(self, install=True):
852
 
 
853
429
        xml = ""
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]:
860
 
            if devxml:
861
 
                if xml:
862
 
                    xml += "\n"
863
 
                xml += devxml
 
430
 
 
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())
864
437
        return xml
865
438
 
 
439
    def _get_features_xml(self):
 
440
        """
 
441
        Return features (pae, acpi, apic) xml (currently only releavnt for FV)
 
442
        """
 
443
        return ""
 
444
 
 
445
    def _get_clock_xml(self):
 
446
        """
 
447
        Return <clock/> xml (currently only relevant for FV guests)
 
448
        """
 
449
        return ""
 
450
 
866
451
    def _get_osblob(self, install):
867
452
        """Return os, features, and clock xml (Implemented in subclass)"""
868
 
        raise NotImplementedError
 
453
        xml = ""
 
454
 
 
455
        osxml = self.installer.get_install_xml(self, install)
 
456
        if not osxml:
 
457
            return None
 
458
 
 
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())
 
463
        return xml
 
464
 
 
465
 
869
466
 
870
467
    def get_config_xml(self, install = True, disk_boot = False):
 
468
        """
 
469
        Return the full Guest xml configuration.
 
470
 
 
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'
 
474
                        config.)
 
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
 
480
                          this.)
 
481
        @type disk_boot: C{bool}
 
482
        """
 
483
 
871
484
        if install:
872
485
            action = "destroy"
873
486
        else:
879
492
 
880
493
        osblob = self._get_osblob(osblob_install)
881
494
        if not osblob:
 
495
            # This means there is no 'install' phase, so just return
882
496
            return None
883
497
 
884
498
        if self.cpuset is not None:
916
530
                      wait=True):
917
531
        """Do the startup of the guest installation."""
918
532
        self.validate_parms()
 
533
        self._consolechild = None
919
534
 
920
535
        if meter is None:
921
536
            # BaseMeter does nothing, but saves a lot of null checking
927
542
        finally:
928
543
            self._installer.cleanup()
929
544
 
 
545
    def get_continue_inst(self):
 
546
        val = self._lookup_osdict_key("continue")
 
547
        if not val:
 
548
            val = False
 
549
 
 
550
        if val == True:
 
551
            # If we are doing an 'import' or 'liveCD' install, there is
 
552
            # no true install process, so continue install has no meaning
 
553
            if not self.get_config_xml(install=True):
 
554
                val = False
 
555
        return val
 
556
 
 
557
    def continue_install(self, consolecb, meter, wait=True):
 
558
        cont_xml = self.get_config_xml(disk_boot = True)
 
559
        logging.debug("Continuing guest with:\n%s" % cont_xml)
 
560
        meter.start(size=None, text="Starting domain...")
 
561
 
 
562
        # As of libvirt 0.5.1 we can't 'create' over an defined VM.
 
563
        # So, redefine the existing domain (which should be shutoff at
 
564
        # this point), and start it.
 
565
        finalxml = self.domain.XMLDesc(0)
 
566
 
 
567
        self.domain = self.conn.defineXML(cont_xml)
 
568
        self.domain.create()
 
569
        self.conn.defineXML(finalxml)
 
570
 
 
571
        #self.domain = self.conn.createLinux(install_xml, 0)
 
572
        if self.domain is None:
 
573
            raise RuntimeError, _("Unable to start domain for guest, aborting installation!")
 
574
        meter.end(0)
 
575
 
 
576
        self.connect_console(consolecb, wait)
 
577
 
 
578
        # ensure there's time for the domain to finish destroying if the
 
579
        # install has finished or the guest crashed
 
580
        if consolecb:
 
581
            time.sleep(1)
 
582
 
 
583
        # This should always work, because it'll lookup a config file
 
584
        # for inactive guest, or get the still running install..
 
585
        return self.conn.lookupByName(self.name)
 
586
 
 
587
 
930
588
    def _prepare_install(self, meter):
931
589
        self._install_disks = self.disks[:]
932
590
        self._install_nics = self.nics[:]
937
595
        if self._installer.install_disk is not None:
938
596
            self._install_disks.append(self._installer.install_disk)
939
597
 
 
598
    def _create_devices(self, progresscb):
 
599
        """Ensure that devices are setup"""
 
600
        for disk in self._install_disks:
 
601
            disk.setup(progresscb)
 
602
        for nic in self._install_nics:
 
603
            nic.setup(self.conn)
 
604
        for hostdev in self.hostdevs:
 
605
            hostdev.setup()
 
606
 
940
607
    def _do_install(self, consolecb, meter, removeOld=False, wait=True):
941
608
        vm = None
942
609
        try:
978
645
            if consolecb:
979
646
                logging.debug("Launching console callback")
980
647
                child = consolecb(self.domain)
 
648
                self._consolechild = child
981
649
 
982
650
        boot_xml = self.get_config_xml(install = False)
983
651
        logging.debug("Saving XML boot config:\n%s" % boot_xml)
1014
682
        if consolecb:
1015
683
            logging.debug("Launching console callback")
1016
684
            child = consolecb(self.domain)
 
685
            self._consolechild = child
1017
686
 
1018
687
        if child and wait: # if we connected the console, wait for it to finish
1019
688
            try:
1037
706
            if _util.vm_uuid_collision(self.conn, self.uuid):
1038
707
                raise RuntimeError, _("The UUID you entered is already in "
1039
708
                                      "use by another guest!")
1040
 
        if self.vcpus is None:
1041
 
            self.vcpus = 1
1042
709
        if self.name is None or self.memory is None:
1043
710
            raise RuntimeError, _("Name and memory must be specified for all guests!")
1044
711
 
1078
745
        raise RuntimeError(_("Invalid dictionary entry for device '%s %s'" % \
1079
746
                             (device_key, param)))
1080
747
 
 
748
    def terminate_console(self):
 
749
        if self._consolechild:
 
750
            os.kill(self._consolechild, signal.SIGKILL)
 
751
 
1081
752
def _wait_for_domain(conn, name):
1082
753
    # sleep in .25 second increments until either a) we get running
1083
754
    # domain ID or b) it's been 5 seconds.  this is so that
1084
755
    # we can try to gracefully handle domain restarting failures
1085
756
    dom = None
1086
 
    for ignore in range(1, (5 / .25)): # 5 seconds, .25 second sleeps
 
757
    for ignore in range(1, int(5 / .25)): # 5 seconds, .25 second sleeps
1087
758
        try:
1088
759
            dom = conn.lookupByName(name)
1089
760
            if dom and dom.ID() != -1: