~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: 2008-11-19 09:19:29 UTC
  • mto: (1.4.1 sid)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20081119091929-vwksujnqzo1utdln
Tags: upstream-0.400.0
Import upstream version 0.400.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
# MA 02110-1301 USA.
21
21
 
22
22
import os, os.path
 
23
import statvfs
23
24
import stat, sys, time
24
25
import re
25
26
import libxml2
27
28
import util
28
29
import libvirt
29
30
import __builtin__
 
31
import CapabilitiesParser
 
32
from VirtualDisk import VirtualDisk
30
33
from virtinst import _virtinst as _
31
34
 
32
35
import logging
33
36
 
34
 
class VirtualDisk:
35
 
    DRIVER_FILE = "file"
36
 
    DRIVER_PHY = "phy"
37
 
    DRIVER_TAP = "tap"
38
 
 
39
 
    DRIVER_TAP_RAW = "aio"
40
 
    DRIVER_TAP_QCOW = "qcow"
41
 
    DRIVER_TAP_VMDK = "vmdk"
42
 
 
43
 
    DEVICE_DISK = "disk"
44
 
    DEVICE_CDROM = "cdrom"
45
 
    DEVICE_FLOPPY = "floppy"
46
 
 
47
 
    TYPE_FILE = "file"
48
 
    TYPE_BLOCK = "block"
49
 
 
50
 
    def __init__(self, path = None, size = None, transient=False, type=None, device=DEVICE_DISK, driverName=None, driverType=None, readOnly=False, sparse=True):
51
 
        """@path is the path to the disk image.
52
 
           @size is the size of the disk image in gigabytes."""
53
 
        self.size = size
54
 
        self.sparse = sparse
55
 
        self.transient = transient
56
 
        self.path = path
57
 
        self._type = type
58
 
        self._readOnly = readOnly
59
 
        self._device = device
60
 
        self._driverName = driverName
61
 
        self._driverType = driverType
62
 
        self.target = None
63
 
       
64
 
        if self.path is not None:
65
 
            # Check that the basics are valid
66
 
            if __builtin__.type(self.path) is not __builtin__.type("string"):
67
 
                raise ValueError, _("The %s path must be a string or None.") % self._device
68
 
            self.path = os.path.abspath(self.path)
69
 
            if os.path.isdir(self.path):
70
 
                raise ValueError, _("The %s path must be a file or a device, not a directory") % self._device
71
 
            if not os.path.exists(os.path.dirname(self.path)):
72
 
                raise ValueError, _("The specified path's root directory must exist.")
73
 
 
74
 
            if (self._device == self.DEVICE_FLOPPY or \
75
 
                self._device == self.DEVICE_CDROM) and \
76
 
               not os.path.exists(self.path):
77
 
                raise ValueError, _("The %s path must exist.") % self._device
78
 
 
79
 
            # If no disk type specified, attempt to determine from path
80
 
            if self._type is None:
81
 
                if not os.path.exists(self.path):
82
 
                    logging.debug(\
83
 
                        "Disk path not found: Assuming file disk type.");
84
 
                    self._type = VirtualDisk.TYPE_FILE
85
 
                else:
86
 
                    if stat.S_ISBLK(os.stat(self.path)[stat.ST_MODE]):
87
 
                        logging.debug(\
88
 
                            "Path is block file: Assuming Block disk type.");
89
 
                        self._type = VirtualDisk.TYPE_BLOCK
90
 
                    else:
91
 
                        self._type = VirtualDisk.TYPE_FILE
92
 
        
93
 
            if self._type == VirtualDisk.TYPE_FILE:
94
 
                if self.size is None and not os.path.exists(self.path):
95
 
                    raise ValueError, \
96
 
                        _("A size must be provided for non-existent disks")
97
 
                if os.path.exists(self.path) and \
98
 
                   stat.S_ISBLK(os.stat(self.path)[stat.ST_MODE]):
99
 
                    raise ValueError, _("The specified path is a block device, not a regular file.")
100
 
                if self.size is not None and \
101
 
                   (__builtin__.type(self.size) is not __builtin__.type(1) and __builtin__.type(self.size) is not __builtin__.type(1.0)):
102
 
                    raise ValueError, _("Disk size must be an int or a float.")
103
 
                if self.size < 0 and self.size is not None:
104
 
                    raise ValueError, _("Disk size must not be less than 0.")
105
 
            elif self._type == VirtualDisk.TYPE_BLOCK:
106
 
                if not os.path.exists(self.path):
107
 
                    raise ValueError, \
108
 
                          _("The specified block device does not exist.")
109
 
                if not stat.S_ISBLK(os.stat(self.path)[stat.ST_MODE]):
110
 
                    raise ValueError, \
111
 
                          _("The specified path is not a block device.")
112
 
 
113
 
        else:
114
 
            # Only floppy or cdrom can be created w/o media
115
 
            if device != self.DEVICE_FLOPPY and \
116
 
               device != self.DEVICE_CDROM:
117
 
                raise ValueError, _("Disk type '%s' requires a path") % device
118
 
 
119
 
    def get_type(self):
120
 
        return self._type
121
 
    type = property(get_type)
122
 
 
123
 
    def get_transient(self):
124
 
        return self._transient
125
 
    transient = property(get_transient)
126
 
 
127
 
    def get_device(self):
128
 
        return self._device
129
 
    device = property(get_device)
130
 
 
131
 
    def get_driver_name(self):
132
 
        return self._driverName
133
 
    driver_name = property(get_driver_name)
134
 
 
135
 
    def get_driver_type(self):
136
 
        return self._driverType
137
 
    driver_type = property(get_driver_type)
138
 
 
139
 
    def get_read_only(self):
140
 
        return self._readOnly
141
 
    read_only = property(get_read_only)
142
 
 
143
 
    def setup(self, progresscb):
144
 
        if self._type == VirtualDisk.TYPE_FILE and self.path is not None \
145
 
           and not os.path.exists(self.path):
146
 
            size_bytes = long(self.size * 1024L * 1024L * 1024L)
147
 
            progresscb.start(filename=self.path,size=long(size_bytes), \
148
 
                             text=_("Creating storage file..."))
149
 
            fd = None
150
 
            try: 
151
 
                try:
152
 
                    fd = os.open(self.path, os.O_WRONLY | os.O_CREAT)
153
 
                    if self.sparse:
154
 
                        os.lseek(fd, size_bytes, 0)
155
 
                        os.write(fd, '\x00')
156
 
                        progresscb.update(self.size)
157
 
                    else:
158
 
                        buf = '\x00' * 1024 * 1024 # 1 meg of nulls
159
 
                        for i in range(0, long(self.size * 1024L)):
160
 
                            os.write(fd, buf)
161
 
                            progresscb.update(long(i * 1024L * 1024L))
162
 
                except OSError, detail:
163
 
                    raise RuntimeError, "Error creating diskimage " + self.path + ": " + detail.strerror
164
 
            finally:
165
 
                if fd is not None:
166
 
                    os.close(fd)
167
 
                progresscb.end(size_bytes)
168
 
        # FIXME: set selinux context?
169
 
 
170
 
    def get_xml_config(self, disknode):
171
 
        typeattr = 'file'
172
 
        if self.type == VirtualDisk.TYPE_BLOCK:
173
 
            typeattr = 'dev'
174
 
 
175
 
        ret = "    <disk type='%(type)s' device='%(device)s'>\n" % { "type": self.type, "device": self.device }
176
 
        if not(self.driver_name is None):
177
 
            if self.driver_type is None:
178
 
                ret += "      <driver name='%(name)s'/>\n" % { "name": self.driver_name }
179
 
            else:
180
 
                ret += "      <driver name='%(name)s' type='%(type)s'/>\n" % { "name": self.driver_name, "type": self.driver_type }
181
 
        if self.path is not None:
182
 
            path = util.xml_escape(self.path)
183
 
            ret += "      <source %(typeattr)s='%(disk)s'/>\n" % { "typeattr": typeattr, "disk": path }
184
 
        if self.target is not None:
185
 
            disknode = self.target
186
 
        ret += "      <target dev='%(disknode)s'/>\n" % { "disknode": disknode }
187
 
        if self.read_only:
188
 
            ret += "      <readonly/>\n"
189
 
        ret += "    </disk>\n"
190
 
        return ret
191
 
 
192
 
    def is_conflict_disk(self, conn):
193
 
        vms = []
194
 
        # get working domain's name
195
 
        ids = conn.listDomainsID();
196
 
        for id in ids:
197
 
            try:
198
 
                vm = conn.lookupByID(id)
199
 
                vms.append(vm)
200
 
            except libvirt.libvirtError:
201
 
                # guest probably in process of dieing
202
 
                logging.warn("Failed to lookup domain id %d" % id)
203
 
        # get defined domain
204
 
        names = conn.listDefinedDomains()
205
 
        for name in names:
206
 
            try:
207
 
                vm = conn.lookupByName(name)
208
 
                vms.append(vm)
209
 
            except libvirt.libvirtError:
210
 
                # guest probably in process of dieing
211
 
                logging.warn("Failed to lookup domain name %s" % name)
212
 
 
213
 
        count = 0
214
 
        for vm in vms:
215
 
            doc = None
216
 
            try:
217
 
                doc = libxml2.parseDoc(vm.XMLDesc(0))
218
 
            except:
219
 
                continue
220
 
            ctx = doc.xpathNewContext()
221
 
            try:
222
 
                try:
223
 
                    count += ctx.xpathEval("count(/domain/devices/disk/source[@dev='%s'])" % self.path)
224
 
                    count += ctx.xpathEval("count(/domain/devices/disk/source[@file='%s'])" % self.path)
225
 
                except:
226
 
                    continue
227
 
            finally:
228
 
                if ctx is not None:
229
 
                    ctx.xpathFreeContext()
230
 
                if doc is not None:
231
 
                    doc.freeDoc()
232
 
        if count > 0:
233
 
            return True
234
 
        else:
235
 
            return False
236
 
 
237
 
    def __repr__(self):
238
 
        return "%s:%s" %(self.type, self.path)
239
 
 
240
 
# Back compat class to avoid ABI break
241
 
class XenDisk(VirtualDisk):
242
 
    pass
243
 
 
244
 
class VirtualNetworkInterface:
245
 
    def __init__(self, macaddr = None, type="bridge", bridge = None, network=None):
 
37
class VirtualNetworkInterface(object):
 
38
 
 
39
    TYPE_BRIDGE  = "bridge"
 
40
    TYPE_VIRTUAL = "network"
 
41
    TYPE_USER    = "user"
 
42
 
 
43
    def __init__(self, macaddr=None, type=TYPE_BRIDGE, bridge=None,
 
44
                 network=None, model=None):
246
45
 
247
46
        if macaddr is not None and \
248
47
           __builtin__.type(macaddr) is not __builtin__.type("string"):
257
56
        self.type = type
258
57
        self.bridge = bridge
259
58
        self.network = network
260
 
        if self.type == "network":
 
59
        self.model = model
 
60
        if self.type == self.TYPE_VIRTUAL:
261
61
            if network is None:
262
62
                raise ValueError, _("A network name was not provided")
263
 
            if bridge != None:
264
 
                raise ValueError, _("Bridge name is not required for %s") % ("type=network",)
265
 
        elif self.type == "bridge":
266
 
            if network != None:
267
 
                raise ValueError, _("Network name is not required for %s") % ("type=bridge",)
268
 
        elif self.type == "user":
269
 
            if network != None:
270
 
                raise ValueError, _("Network name is not required for %s") % ("type=bridge",)
271
 
            if bridge != None:
272
 
                raise ValueError, _("Bridge name is not required for %s") % ("type=network",)
 
63
        elif self.type == self.TYPE_BRIDGE:
 
64
            pass
 
65
        elif self.type == self.TYPE_USER:
 
66
            pass
273
67
        else:
274
68
            raise ValueError, _("Unknown network type %s") % (type,)
275
69
 
276
70
    def is_conflict_net(self, conn):
277
71
        """is_conflict_net: determines if mac conflicts with others in system
278
72
 
279
 
           returns a two element tuple: 
 
73
           returns a two element tuple:
280
74
               first element is True if fatal collision occured
281
75
               second element is a string description of the collision.
282
76
           Non fatal collisions (mac addr collides with inactive guest) will
283
77
           return (False, "description of collision")"""
 
78
        if self.macaddr is None:
 
79
            return (False, None)
284
80
        # get Running Domains
285
81
        ids = conn.listDomainsID();
286
82
        vms = []
306
102
        hostdevs = util.get_host_network_devices()
307
103
 
308
104
        if self.countMACaddr(vms) > 0:
309
 
            return (True, _("The MAC address you entered is already in use by another virtual machine!"))
 
105
            return (True, _("The MAC address you entered is already in use by another active virtual machine."))
310
106
        for (dummy, dummy, dummy, dummy, host_macaddr) in hostdevs:
311
107
            if self.macaddr.upper() == host_macaddr.upper():
312
 
                return (True, _("The MAC address you entered conflicts with the physical NIC."))
 
108
                return (True, _("The MAC address you entered conflicts with a device on the physical host."))
313
109
        if self.countMACaddr(inactive_vm) > 0:
314
 
            return (False, _("The MAC address you entered is already in use by another inactive virtual machine!"))
 
110
            return (False, _("The MAC address you entered is already in use by another inactive virtual machine."))
315
111
        return (False, None)
316
112
 
317
113
    def setup(self, conn):
318
 
 
319
 
        # check conflict MAC address
320
114
        if self.macaddr is None:
321
115
            while 1:
322
 
                self.macaddr = util.randomMAC()
 
116
                self.macaddr = util.randomMAC(type=conn.getType().lower())
323
117
                if self.is_conflict_net(conn)[1] is not None:
324
118
                    continue
325
119
                else:
327
121
        else:
328
122
            ret, msg = self.is_conflict_net(conn)
329
123
            if msg is not None:
330
 
                # Error message found
331
124
                if ret is False:
332
 
                    # Not fatal
333
125
                    logging.warning(msg)
334
126
                else:
335
127
                    raise RuntimeError(msg)
338
130
            self.bridge = util.default_bridge()
339
131
 
340
132
    def get_xml_config(self):
341
 
        if self.type == "bridge":
342
 
            return ("    <interface type='bridge'>\n" + \
343
 
                    "      <source bridge='%(bridge)s'/>\n" + \
344
 
                    "      <mac address='%(mac)s'/>\n" + \
345
 
                    "    </interface>\n") % \
346
 
                    { "bridge": self.bridge, "mac": self.macaddr }
347
 
        elif self.type == "network":
348
 
            return ("    <interface type='network'>\n" + \
349
 
                    "      <source network='%(network)s'/>\n" + \
350
 
                    "      <mac address='%(mac)s'/>\n" + \
351
 
                    "    </interface>\n") % \
352
 
                    { "network": self.network, "mac": self.macaddr }
353
 
        elif self.type == "user":
354
 
            return ("    <interface type='user'>\n" + \
355
 
                    "      <mac address='%(mac)s'/>\n" + \
356
 
                    "    </interface>\n") % \
357
 
                    { "mac": self.macaddr }
 
133
        src_xml = ""
 
134
        model_xml = ""
 
135
        if self.type == self.TYPE_BRIDGE:
 
136
            src_xml =   "      <source bridge='%s'/>\n" % self.bridge
 
137
        elif self.type == self.TYPE_VIRTUAL:
 
138
            src_xml =   "      <source network='%s'/>\n" % self.network
 
139
 
 
140
        if self.model:
 
141
            model_xml = "      <model type='%s'/>\n" % self.model
 
142
 
 
143
        return "    <interface type='%s'>\n" % self.type + \
 
144
               src_xml + \
 
145
               "      <mac address='%s'/>\n" % self.macaddr + \
 
146
               model_xml + \
 
147
               "    </interface>"
358
148
 
359
149
    def countMACaddr(self, vms):
360
150
        count = 0
366
156
                continue
367
157
            ctx = doc.xpathNewContext()
368
158
            try:
369
 
                try:
370
 
                    count += ctx.xpathEval("count(/domain/devices/interface/mac[@address='%s'])"
371
 
                                           % self.macaddr.upper())
372
 
                    count += ctx.xpathEval("count(/domain/devices/interface/mac[@address='%s'])"
373
 
                                           % self.macaddr.lower())
374
 
                except:
375
 
                    continue
 
159
                for mac in ctx.xpathEval("/domain/devices/interface/mac"):
 
160
                    macaddr = mac.xpathEval("attribute::address")[0].content
 
161
                    if util.compareMAC(self.macaddr, macaddr) == 0:
 
162
                        count += 1
376
163
            finally:
377
164
                if ctx is not None:
378
165
                    ctx.xpathFreeContext()
380
167
                    doc.freeDoc()
381
168
        return count
382
169
 
 
170
class VirtualAudio(object):
 
171
 
 
172
    MODELS = [ "es1370", "sb16", "pcspk" ]
 
173
 
 
174
    def __init__(self, model):
 
175
        self.model = model
 
176
 
 
177
    def get_model(self):
 
178
        return self._model
 
179
    def set_model(self, new_model):
 
180
        if type(new_model) != str:
 
181
            raise ValueError, _("'model' must be a string, "
 
182
                                " was '%s'." % type(new_model))
 
183
        if not self.MODELS.count(new_model):
 
184
            raise ValueError, _("Unsupported sound model '%s'" % new_model)
 
185
        self._model = new_model
 
186
    model = property(get_model, set_model)
 
187
 
 
188
    def get_xml_config(self):
 
189
        return "    <sound model='%s'/>" % self.model
 
190
 
383
191
# Back compat class to avoid ABI break
384
192
class XenNetworkInterface(VirtualNetworkInterface):
385
193
    pass
386
194
 
387
 
class VirtualGraphics:
388
 
    def __init__(self, *args):
389
 
        self.name = ""
390
 
 
391
 
    def get_xml_config(self):
392
 
        return ""
393
 
 
394
 
# Back compat class to avoid ABI break
395
 
class XenGraphics(VirtualGraphics):
396
 
    pass
397
 
 
398
 
class VNCVirtualGraphics(XenGraphics):
399
 
    def __init__(self, *args):
400
 
        self.name = "vnc"
401
 
        if len(args) >= 1 and not args[0] is None:
402
 
            if args[0] < 5900 or args[0] > 65535:
403
 
                raise ValueError, _("Invalid value for vnc port, port number must be in between 5900 and 65535")
404
 
            self.port = args[0]
405
 
        else:
406
 
            self.port = -1
407
 
        if len(args) >= 2 and args[1]:
408
 
            self.keymap = args[1]
409
 
        else:
410
 
            self.keymap = None
411
 
 
412
 
    def get_xml_config(self):
413
 
        if self.keymap == None:
414
 
            keymapstr = ""
415
 
        else:
416
 
            keymapstr = "keymap='"+self.keymap+"' "
417
 
        return "    <graphics type='vnc' port='%(port)d' %(keymapstr)s/>" % {"port":self.port, "keymapstr":keymapstr}
418
 
 
419
 
# Back compat class to avoid ABI break
420
 
class XenVNCGraphics(VNCVirtualGraphics):
421
 
    pass
422
 
 
423
 
class SDLVirtualGraphics(XenGraphics):
424
 
    def __init__(self, *args):
425
 
        self.name = "sdl"
426
 
 
427
 
    def get_xml_config(self):
428
 
        return "    <graphics type='sdl'/>"
429
 
 
430
 
# Back compat class to avoid ABI break
431
 
class XenSDLGraphics(SDLVirtualGraphics):
432
 
    pass
 
195
class VirtualGraphics(object):
 
196
 
 
197
    TYPE_SDL = "sdl"
 
198
    TYPE_VNC = "vnc"
 
199
 
 
200
    def __init__(self, type=TYPE_VNC, port=-1, listen=None, passwd=None,
 
201
                 keymap=None):
 
202
 
 
203
        if type != self.TYPE_VNC and type != self.TYPE_SDL:
 
204
            raise ValueError(_("Unknown graphics type"))
 
205
        self._type   = type
 
206
        self.set_port(port)
 
207
        self.set_keymap(keymap)
 
208
        self.set_listen(listen)
 
209
        self.set_passwd(passwd)
 
210
 
 
211
    def get_type(self):
 
212
        return self._type
 
213
    type = property(get_type)
 
214
 
 
215
    def get_keymap(self):
 
216
        return self._keymap
 
217
    def set_keymap(self, val):
 
218
        if not val:
 
219
            val = util.default_keymap()
 
220
        if not val or type(val) != type("string"):
 
221
            raise ValueError, _("Keymap must be a string")
 
222
        if len(val) > 16:
 
223
            raise ValueError, _("Keymap must be less than 16 characters")
 
224
        if re.match("^[a-zA-Z0-9_-]*$", val) == None:
 
225
            raise ValueError, _("Keymap can only contain alphanumeric, '_', or '-' characters")
 
226
        self._keymap = val
 
227
    keymap = property(get_keymap, set_keymap)
 
228
 
 
229
    def get_port(self):
 
230
        return self._port
 
231
    def set_port(self, val):
 
232
        if val is None:
 
233
            val = -1
 
234
        elif type(val) is not int \
 
235
             or (val != -1 and (val < 5900 or val > 65535)):
 
236
            raise ValueError, _("VNC port must be a number between 5900 and 65535, or -1 for auto allocation")
 
237
        self._port = val
 
238
    port = property(get_port, set_port)
 
239
 
 
240
    def get_listen(self):
 
241
        return self._listen
 
242
    def set_listen(self, val):
 
243
        self._listen = val
 
244
    listen = property(get_listen, set_listen)
 
245
 
 
246
    def get_passwd(self):
 
247
        return self._passwd
 
248
    def set_passwd(self, val):
 
249
        self._passwd = val
 
250
    passwd = property(get_passwd, set_passwd)
 
251
 
 
252
    def get_xml_config(self):
 
253
        if self._type == self.TYPE_SDL:
 
254
            return "    <graphics type='sdl'/>"
 
255
        keymapxml = ""
 
256
        listenxml = ""
 
257
        passwdxml = ""
 
258
        if self.keymap:
 
259
            keymapxml = " keymap='%s'" % self._keymap
 
260
        if self.listen:
 
261
            listenxml = " listen='%s'" % self._listen
 
262
        if self.passwd:
 
263
            passwdxml = " passwd='%s'" % self._passwd
 
264
        xml = "    <graphics type='vnc' " + \
 
265
                   "port='%(port)d'" % { "port" : self._port } + \
 
266
                   "%(keymapxml)s"   % { "keymapxml" : keymapxml } + \
 
267
                   "%(listenxml)s"   % { "listenxml" : listenxml } + \
 
268
                   "%(passwdxml)s"   % { "passwdxml" : passwdxml } + \
 
269
                   "/>"
 
270
        return xml
433
271
 
434
272
class Installer(object):
435
 
    def __init__(self, type = "xen", location = None, boot = None, extraargs = None, os_type = None):
 
273
    def __init__(self, type = "xen", location = None, boot = None,
 
274
                 extraargs = None, os_type = None, conn = None):
436
275
        self._location = None
437
276
        self._extraargs = None
438
277
        self._boot = None
439
278
        self._cdrom = False
440
279
        self._os_type = os_type
 
280
        self._conn = conn
441
281
        self._install_disk = None   # VirtualDisk that contains install media
442
282
 
443
283
        if type is None:
463
303
        return self._install_disk
464
304
    install_disk = property(get_install_disk)
465
305
 
 
306
    def get_conn(self):
 
307
        return self._conn
 
308
    conn = property(get_conn)
 
309
 
466
310
    def get_type(self):
467
311
        return self._type
468
312
    def set_type(self, val):
477
321
 
478
322
    def get_scratchdir(self):
479
323
        if self.type == "xen":
480
 
            return "/var/lib/xen"
481
 
        return "/var/tmp"
 
324
            if os.path.exists("/var/lib/xen"):
 
325
                return "/var/lib/xen"
 
326
        return "/var/lib/libvirt/boot"
482
327
    scratchdir = property(get_scratchdir)
483
328
 
484
329
    def get_cdrom(self):
485
330
        return self._cdrom
486
331
    def set_cdrom(self, enable):
487
 
        if __builtin__.type(enable) is not __builtin__.type(True):
 
332
        if enable not in [True, False]:
488
333
            raise ValueError, _("Guest.cdrom must be a boolean type")
489
334
        self._cdrom = enable
490
335
    cdrom = property(get_cdrom, set_cdrom)
533
378
        self._maxmemory = None
534
379
        self._vcpus = None
535
380
        self._cpuset = None
536
 
        self._graphics = { "enabled": False }
537
 
        self._keymap = None
538
 
        
 
381
        self._graphics_dev = None
 
382
 
539
383
        # Public device lists unaltered by install process
540
384
        self.disks = []
541
385
        self.nics = []
 
386
        self.sound_devs = []
542
387
 
543
388
        # Device lists to use/alter during install process
544
389
        self._install_disks = []
547
392
        self.domain = None
548
393
        self.conn = connection
549
394
        if self.conn == None:
 
395
            logging.debug("No conn passed to Guest, opening URI '%s'" % \
 
396
                          hypervisorURI)
550
397
            self.conn = libvirt.open(hypervisorURI)
551
398
        if self.conn == None:
552
 
            raise RuntimeError, _("Unable to connect to hypervisor, aborting installation!")
 
399
            raise RuntimeError, _("Unable to connect to hypervisor, aborting "
 
400
                                  "installation!")
 
401
        self._caps = CapabilitiesParser.parse(self.conn.getCapabilities())
553
402
 
554
403
        self.disknode = None # this needs to be set in the subclass
555
404
 
575
424
            raise ValueError, _("System name must be a string greater than 0 and no more than 50 characters")
576
425
        if re.match("^[0-9]+$", val):
577
426
            raise ValueError, _("System name must not be only numeric characters")
578
 
        if re.match("^[a-zA-Z0-9._-]+$", val) == None:
579
 
            raise ValueError, _("System name can only contain alphanumeric, '_', '.', or '-' characters")
 
427
        if re.match("^[A-Za-z0-9_.:/+-]+$", val) == None:
 
428
            raise ValueError, _("System name can only contain: alphanumeric "
 
429
                                "'_', '.', ':', '+', or '-' characters")
580
430
        self._name = val
581
431
    name = property(get_name, set_name)
582
432
 
628
478
        return self._vcpus
629
479
    def set_vcpus(self, val):
630
480
        maxvcpus = util.get_max_vcpus(self.conn, self.type)
631
 
        if val < 1 or val > maxvcpus:
 
481
        if type(val) is not int or val < 1:
 
482
            raise ValueError, _("Number of vcpus must be a postive integer.")
 
483
        if val > maxvcpus:
632
484
            raise ValueError, \
633
 
                  _("Number of vcpus must be in the range of 1-%d") % (maxvcpus,)
 
485
                  _("Number of vcpus must be no greater than %d for this vm type.") % maxvcpus
634
486
        self._vcpus = val
635
487
    vcpus = property(get_vcpus, set_vcpus)
636
488
 
657
509
        self._cpuset = val
658
510
    cpuset = property(get_cpuset, set_cpuset)
659
511
 
660
 
    # graphics setup
 
512
    def get_graphics_dev(self):
 
513
        return self._graphics_dev
 
514
    def set_graphics_dev(self, val):
 
515
        self._graphics_dev = val
 
516
    graphics_dev = property(get_graphics_dev, set_graphics_dev)
 
517
 
 
518
    # DEPRECATED PROPERTIES
 
519
    # Deprecated: Should set graphics_dev.keymap directly
 
520
    def get_keymap(self):
 
521
        if self._graphics_dev is None:
 
522
            return None
 
523
        return self._graphics_dev.keymap
 
524
    def set_keymap(self, val):
 
525
        if self._graphics_dev is not None:
 
526
            self._graphics_dev.keymap = val
 
527
    keymap = property(get_keymap, set_keymap)
 
528
 
 
529
    # Deprecated: Should set guest.graphics_dev = VirtualGraphics(...)
661
530
    def get_graphics(self):
662
 
        return self._graphics
 
531
        if self._graphics_dev is None:
 
532
            return { "enabled " : False }
 
533
        return { "enabled" : True, "type" : self._graphics_dev, \
 
534
                 "keymap"  : self._graphics_dev.keymap}
663
535
    def set_graphics(self, val):
664
 
        def validate_keymap(keymap):
665
 
            if not keymap:
666
 
                return keymap
667
 
            if type(keymap) != type("string"):
668
 
                raise ValueError, _("Keymap must be a string")
669
 
            if len(keymap) > 16:
670
 
                raise ValueError, _("Keymap must be less than 16 characters")
671
 
            if re.match("^[a-zA-Z0-9_-]*$", keymap) == None:
672
 
                raise ValueError, _("Keymap can only contain alphanumeric, '_', or '-' characters")
673
 
            return keymap
674
536
 
675
 
        opts = None
676
 
        t = None
 
537
        # val can be:
 
538
        #   a dictionary with keys:  enabled, type, port, keymap
 
539
        #   a tuple of the form   : (enabled, type, port, keymap)
 
540
        #                            last 2 optional
 
541
        #                         : "vnc", "sdl", or false
 
542
        port = None
 
543
        gtype = None
 
544
        enabled = False
 
545
        keymap = None
 
546
        gdev = None
677
547
        if type(val) == dict:
678
548
            if not val.has_key("enabled"):
679
549
                raise ValueError, _("Must specify whether graphics are enabled")
680
 
            self._graphics["enabled"] = val["enabled"]
 
550
            enabled = val["enabled"]
681
551
            if val.has_key("type"):
682
 
                t = val["type"]
 
552
                gtype = val["type"]
683
553
                if val.has_key("opts"):
684
 
                    opts = val["opts"]
 
554
                    port = val["opts"]
685
555
        elif type(val) == tuple:
686
 
            if len(val) >= 1: self._graphics["enabled"] = val[0]
687
 
            if len(val) >= 2: t = val[1]
688
 
            if len(val) >= 3: opts = val[2]
689
 
            if len(val) >= 4: self._graphics["keymap"] = validate_keymap(val[3])
 
556
            if len(val) >= 1: enabled = val[0]
 
557
            if len(val) >= 2: gtype = val[1]
 
558
            if len(val) >= 3: port = val[2]
 
559
            if len(val) >= 4: keymap = val[3]
690
560
        else:
691
561
            if val in ("vnc", "sdl"):
692
 
                t = val
693
 
                self._graphics["enabled"] = True
 
562
                gtype = val
 
563
                enabled = True
694
564
            else:
695
 
                self._graphics["enabled"] = val
 
565
                enabled = val
696
566
 
697
 
        if self._graphics["enabled"] not in (True, False):
 
567
        if enabled not in (True, False):
698
568
            raise ValueError, _("Graphics enabled must be True or False")
699
569
 
700
 
        if self._graphics["enabled"] == True:
701
 
            if t == "vnc":
702
 
                if self.graphics.has_key("keymap"):
703
 
                    gt = VNCVirtualGraphics(opts, self._graphics["keymap"])
704
 
                else:
705
 
                    gt = VNCVirtualGraphics(opts)
706
 
            elif t == "sdl":
707
 
                gt = SDLVirtualGraphics(opts)
708
 
            else:
709
 
                raise ValueError, _("Unknown graphics type")
710
 
            self._graphics["type"] = gt
 
570
        if enabled == True:
 
571
            gdev = VirtualGraphics(type=gtype)
 
572
            if port:
 
573
                gdev.port = port
 
574
            if keymap:
 
575
                gdev.keymap = keymap
 
576
        self._graphics_dev = gdev
711
577
 
712
578
    graphics = property(get_graphics, set_graphics)
713
579
 
 
580
    # Deprecated: Should be called from the installer directly
 
581
    def get_location(self):
 
582
        return self._installer.location
 
583
    def set_location(self, val):
 
584
        self._installer.location = val
 
585
    location = property(get_location, set_location)
714
586
 
715
 
    # Legacy, deprecated properties
 
587
    # Deprecated: Should be called from the installer directly
716
588
    def get_scratchdir(self):
717
589
        return self._installer.scratchdir
718
590
    scratchdir = property(get_scratchdir)
719
591
 
 
592
    # Deprecated: Should be called from the installer directly
720
593
    def get_boot(self):
721
594
        return self._installer.boot
722
595
    def set_boot(self, val):
723
596
        self._installer.boot = val
724
597
    boot = property(get_boot, set_boot)
725
598
 
726
 
    def get_location(self):
727
 
        return self._installer.location
728
 
    def set_location(self, val):
729
 
        self._installer.location = val
730
 
    location = property(get_location, set_location)
731
 
 
 
599
    # Deprecated: Should be called from the installer directly
732
600
    def get_extraargs(self):
733
601
        return self._installer.extraargs
734
602
    def set_extraargs(self, val):
735
603
        self._installer.extraargs = val
736
604
    extraargs = property(get_extraargs, set_extraargs)
737
605
 
 
606
    # Deprecated: Should set the installer values directly
738
607
    def get_cdrom(self):
739
608
        return self._installer.location
740
609
    def set_cdrom(self, val):
749
618
            self._installer.location = val
750
619
        self._installer.cdrom = True
751
620
    cdrom = property(get_cdrom, set_cdrom)
 
621
    # END DEPRECATED PROPERTIES
752
622
 
753
623
 
754
624
    def _create_devices(self,progresscb):
761
631
    def _get_network_xml(self, install = True):
762
632
        """Get the network config in the libvirt XML format"""
763
633
        ret = ""
764
 
        for n in self.nics:
 
634
        for n in self._install_nics:
 
635
            if ret:
 
636
                ret += "\n"
765
637
            ret += n.get_xml_config()
766
638
        return ret
767
639
 
768
640
    def _get_graphics_xml(self, install = True):
769
641
        """Get the graphics config in the libvirt XML format."""
770
 
        ret = ""
771
 
        if self.graphics["enabled"] == False:
772
 
            return ret
773
 
        gt = self.graphics["type"]
774
 
        return gt.get_xml_config()
 
642
        if self._graphics_dev is None:
 
643
            return ""
 
644
        return self._graphics_dev.get_xml_config()
775
645
 
776
646
    def _get_input_xml(self, install = True):
777
647
        """Get the input device config in libvirt XML format."""
778
648
        (type,bus) = self.get_input_device()
779
649
        return "    <input type='%s' bus='%s'/>" % (type, bus)
780
650
 
 
651
    def _get_sound_xml(self):
 
652
        """Get the sound device configuration in libvirt XML format."""
 
653
        xml = ""
 
654
        for sound_dev in self.sound_devs:
 
655
            if xml != "":
 
656
                xml += "\n"
 
657
            xml += sound_dev.get_xml_config()
 
658
        return xml
 
659
 
781
660
    def _get_device_xml(self, install = True):
782
 
        return """%(disks)s
783
 
%(networks)s
784
 
%(input)s
785
 
%(graphics)s""" % { "disks": self._get_disk_xml(install), \
786
 
        "networks": self._get_network_xml(install), \
787
 
        "input": self._get_input_xml(install), \
788
 
        "graphics": self._get_graphics_xml(install) }
 
661
 
 
662
        xml = ""
 
663
        diskxml     = self._get_disk_xml(install)
 
664
        netxml      = self._get_network_xml(install)
 
665
        inputxml    = self._get_input_xml(install)
 
666
        graphicsxml = self._get_graphics_xml(install)
 
667
        soundxml    = self._get_sound_xml()
 
668
        for devxml in [diskxml, netxml, inputxml, graphicsxml, soundxml]:
 
669
            if devxml:
 
670
                if xml:
 
671
                    xml += "\n"
 
672
                xml += devxml
 
673
        return xml
 
674
 
789
675
 
790
676
    def get_config_xml(self, install = True, disk_boot = False):
791
677
        if install:
832
718
        "action": action }
833
719
 
834
720
 
835
 
    def start_install(self, consolecb = None, meter = None):
 
721
    def start_install(self, consolecb=None, meter=None, removeOld=False,
 
722
                      wait=True):
836
723
        """Do the startup of the guest installation."""
837
724
        self.validate_parms()
838
725
 
842
729
 
843
730
        self._prepare_install(meter)
844
731
        try:
845
 
            return self._do_install(consolecb, meter)
 
732
            return self._do_install(consolecb, meter, removeOld, wait)
846
733
        finally:
847
734
            self._installer.cleanup()
848
735
 
849
736
    def _prepare_install(self, meter):
850
737
        self._install_disks = self.disks[:]
851
738
        self._install_nics = self.nics[:]
 
739
        self._set_defaults()
852
740
 
853
 
    def _do_install(self, consolecb, meter):
 
741
    def _do_install(self, consolecb, meter, removeOld=False, wait=True):
 
742
        vm = None
854
743
        try:
855
 
            if self.conn.lookupByName(self.name) is not None:
 
744
            vm = self.conn.lookupByName(self.name)
 
745
        except libvirt.libvirtError:
 
746
           pass
 
747
 
 
748
        if vm is not None:
 
749
            if removeOld :
 
750
                try:
 
751
                    if vm.ID() != -1:
 
752
                        logging.info("Destroying image %s" %(self.name))
 
753
                        vm.destroy()
 
754
                    logging.info("Removing old definition for image %s" %(self.name))
 
755
                    vm.undefine()
 
756
                except libvirt.libvirtError, e:
 
757
                    raise RuntimeError, _("Could not remove old vm '%s': %s") %(self.name, str(e))
 
758
            else:
856
759
                raise RuntimeError, _("Domain named %s already exists!") %(self.name,)
857
 
        except libvirt.libvirtError:
858
 
            pass
859
760
 
860
761
        child = None
861
762
        self._create_devices(meter)
895
796
        logging.debug("Saving XML boot config '%s'" % ( boot_xml ))
896
797
        self.conn.defineXML(boot_xml)
897
798
 
898
 
        if child: # if we connected the console, wait for it to finish
 
799
        if child and wait: # if we connected the console, wait for it to finish
899
800
            try:
900
801
                (pid, status) = os.waitpid(child, 0)
901
802
            except OSError, (errno, msg):
912
813
    def post_install_check(self):
913
814
        return self.installer.post_install_check(self)
914
815
 
915
 
    def connect_console(self, consolecb):
 
816
    def connect_console(self, consolecb, wait=True):
916
817
        logging.debug("Restarted guest, looking to see if it is running")
917
818
        # sleep in .25 second increments until either a) we get running
918
819
        # domain ID or b) it's been 5 seconds.  this is so that
940
841
            logging.debug("Launching console callback")
941
842
            child = consolecb(self.domain)
942
843
 
943
 
        if child: # if we connected the console, wait for it to finish
 
844
        if child and wait: # if we connected the console, wait for it to finish
944
845
            try:
945
846
                (pid, status) = os.waitpid(child, 0)
946
847
            except OSError, (errno, msg):
949
850
    def validate_parms(self):
950
851
        if self.domain is not None:
951
852
            raise RuntimeError, _("Domain has already been started!")
952
 
        self._set_defaults()
953
853
 
954
854
    def _set_defaults(self):
955
855
        if self.uuid is None: