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

« back to all changes in this revision

Viewing changes to virtinst/VirtualDisk.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:
19
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
20
# MA 02110-1301 USA.
21
21
 
22
 
import os, stat, statvfs
 
22
import os, statvfs
23
23
import subprocess
24
 
import libxml2
25
24
import logging
26
25
import libvirt
27
26
 
87
86
    driver_types = [DRIVER_TAP_RAW, DRIVER_TAP_QCOW,
88
87
        DRIVER_TAP_VMDK, DRIVER_TAP_VDISK]
89
88
 
 
89
    CACHE_MODE_NONE = "none"
 
90
    CACHE_MODE_WRITETHROUGH = "writethrough"
 
91
    CACHE_MODE_WRITEBACK = "writeback"
 
92
    cache_types = [CACHE_MODE_NONE, CACHE_MODE_WRITETHROUGH,
 
93
        CACHE_MODE_WRITEBACK]
 
94
 
90
95
    DEVICE_DISK = "disk"
91
96
    DEVICE_CDROM = "cdrom"
92
97
    DEVICE_FLOPPY = "floppy"
99
104
    def __init__(self, path=None, size=None, transient=False, type=None,
100
105
                 device=DEVICE_DISK, driverName=None, driverType=None,
101
106
                 readOnly=False, sparse=True, conn=None, volObject=None,
102
 
                 volInstall=None, volName=None, bus=None):
 
107
                 volInstall=None, volName=None, bus=None, shareable=False,
 
108
                 driverCache=None):
103
109
        """
104
110
        @param path: filesystem path to the disk image.
105
111
        @type path: C{str}
130
136
        @type volName: C{tuple} of (C{str}, C{str})
131
137
        @param bus: Emulated bus type (ide, scsi, virtio, ...)
132
138
        @type bus: C{str}
 
139
        @param shareable: If disk can be shared among VMs
 
140
        @type shareable: C{bool}
 
141
        @param driverCache: Disk cache mode (none, writethrough, writeback)
 
142
        @type driverCache: member of cache_types
133
143
        """
134
144
 
135
145
        VirtualDevice.__init__(self, conn=conn)
143
153
        self._vol_object = None
144
154
        self._vol_install = None
145
155
        self._bus = None
 
156
        self._shareable = None
 
157
        self._driver_cache = None
146
158
 
147
159
        # XXX: No property methods for these
148
160
        self.transient = transient
159
171
        self._set_vol_object(volObject, validate=False)
160
172
        self._set_vol_install(volInstall, validate=False)
161
173
        self._set_bus(bus, validate=False)
 
174
        self._set_shareable(shareable, validate=False)
 
175
        self._set_driver_cache(driverCache, validate=False)
162
176
 
163
177
        if volName:
164
178
            self.__lookup_vol_name(volName)
262
276
        self.__validate_wrapper("_bus", val, validate)
263
277
    bus = property(_get_bus, _set_bus)
264
278
 
 
279
    def _get_shareable(self):
 
280
        return self._shareable
 
281
    def _set_shareable(self, val, validate=True):
 
282
        self._check_bool(val, "shareable")
 
283
        self.__validate_wrapper("_shareable", val, validate)
 
284
    shareable = property(_get_shareable, _set_shareable)
 
285
 
 
286
    def _get_driver_cache(self):
 
287
        return self._driver_cache
 
288
    def _set_driver_cache(self, val, validate=True):
 
289
        if val is not None:
 
290
            self._check_str(val, "cache")
 
291
            if val not in self.cache_types:
 
292
                raise ValueError, _("Unknown cache mode '%s'" % val)
 
293
        self.__validate_wrapper("_driver_cache", val, validate)
 
294
    driver_cache = property(_get_driver_cache, _set_driver_cache)
 
295
 
265
296
    # Validation assistance methods
266
297
 
267
298
    # Initializes attribute if it hasn't been done, then validates args.
279
310
                setattr(self, varname, orig)
280
311
                raise
281
312
 
 
313
    def __set_size(self, creating_storage):
 
314
        """
 
315
        Fill in 'size' attribute for existing storage.
 
316
        """
 
317
 
 
318
        if creating_storage:
 
319
            return
 
320
 
 
321
        if self.__storage_specified() and self.vol_object:
 
322
            newsize = _util.get_xml_path(self.vol_object.XMLDesc(0),
 
323
                                         "/volume/capacity")
 
324
            try:
 
325
                newsize = float(newsize) / 1024.0 / 1024.0 / 1024.0
 
326
            except:
 
327
                newsize = 0
 
328
        elif self.path is None:
 
329
            newsize = 0
 
330
        else:
 
331
            ignore, newsize = _util.stat_disk(self.path)
 
332
            newsize = newsize / 1024.0 / 1024.0 / 1024.0
 
333
 
 
334
        if newsize != self.size:
 
335
            logging.debug("Setting size for existing storage to '%s'" %
 
336
                          newsize)
 
337
            self._set_size(newsize, validate=False)
 
338
 
282
339
    def __set_dev_type(self):
283
340
        """
284
341
        Detect disk 'type' () from passed storage parameters
301
358
                # All others should be using TYPE_BLOCK (hopefully)
302
359
                dtype = self.TYPE_BLOCK
303
360
        elif self.path:
304
 
            if stat.S_ISBLK(os.stat(self.path)[stat.ST_MODE]):
305
 
                dtype = self.TYPE_BLOCK
 
361
            if _util.stat_disk(self.path)[0]:
 
362
                dtype = self.TYPE_FILE
306
363
            else:
307
 
                dtype = self.TYPE_FILE
 
364
                dtype = self.TYPE_BLOCK
308
365
            if _util.is_vdisk(self.path):
309
366
                self._driverName = self.DRIVER_TAP
310
367
                self._driverType = self.DRIVER_TAP_VDISK
396
453
                               capacity=cap, allocation=alloc, pool=pool)
397
454
                self._set_vol_install(vol, validate=False)
398
455
            elif self._is_remote():
399
 
                raise ValueError(_("'%s' is not managed on remote "
400
 
                                   "host: %s" % (self.path, verr)))
 
456
 
 
457
                if not verr:
 
458
                    # Since there is no error, no pool was ever found
 
459
                    err = (_("Cannot use storage '%(path)s': '%(rootdir)s' is "
 
460
                             "not managed on the remote host.") %
 
461
                             { 'path' : self.path,
 
462
                               'rootdir' : os.path.dirname(self.path)})
 
463
                else:
 
464
                    err = (_("Cannot use storage %(path)s: %(err)s") %
 
465
                           { 'path' : self.path, 'err' : verr })
 
466
 
 
467
                raise ValueError(err)
401
468
        else:
402
469
            self._set_vol_object(vol, validate=False)
403
470
 
412
479
        if self.vol_object:
413
480
            newpath = self.vol_object.path()
414
481
        elif self.vol_install:
415
 
            newpath = _util.get_xml_path(self.vol_install.pool.XMLDesc(0),
416
 
                                         "/pool/target/path") + \
417
 
                      self.vol_install.name
 
482
            newpath = (_util.get_xml_path(self.vol_install.pool.XMLDesc(0),
 
483
                                         "/pool/target/path") + "/" +
 
484
                       self.vol_install.name)
418
485
 
419
486
        if newpath and newpath != self.path:
420
 
            logging.debug("Overwriting 'path' from passed volume object.")
 
487
            logging.debug("Overwriting 'path' with value from StorageVolume"
 
488
                          " object.")
421
489
            self._set_path(newpath, validate=False)
422
490
 
423
491
        if self.vol_install:
424
492
            newsize = self.vol_install.capacity/1024.0/1024.0/1024.0
425
493
            if self.size != newsize:
426
494
                logging.debug("Overwriting 'size' with value from "
427
 
                              "StorageVolume")
 
495
                              "StorageVolume object")
428
496
                self._set_size(newsize, validate=False)
429
497
 
430
498
        # Remove this piece when storage volume creation is async
475
543
        create_media = not ((managed_storage and self.vol_object) or \
476
544
                            (self.path and os.path.exists(self.path)))
477
545
 
 
546
        self.__set_size(create_media)
 
547
 
478
548
        if self._is_remote() and not managed_storage:
479
549
            raise ValueError, _("Must specify libvirt managed storage if on "
480
550
                                "a remote connection")
504
574
            self.set_type(self.TYPE_FILE, validate=False)
505
575
 
506
576
            # Path doesn't exist: make sure we have write access to dir
 
577
            if not os.access(os.path.dirname(self.path), os.R_OK):
 
578
                raise ValueError("No read access to directory '%s'" %
 
579
                                 os.path.dirname(self.path))
 
580
            if self.size is None:
 
581
                raise ValueError, _("size is required for non-existent disk "
 
582
                                    "'%s'" % self.path)
507
583
            if not os.access(os.path.dirname(self.path), os.W_OK):
508
584
                raise ValueError, _("No write access to directory '%s'") % \
509
585
                                    os.path.dirname(self.path)
510
 
            if self.size is None:
511
 
                raise ValueError, _("size is required for non-existent disk "
512
 
                                    "'%s'" % self.path)
513
586
        else:
514
587
            self.__set_dev_type()
515
588
 
603
676
        if path:
604
677
            path = _util.xml_escape(path)
605
678
 
606
 
        ret = "    <disk type='%(type)s' device='%(device)s'>\n" % { "type": self.type, "device": self.device }
607
 
        if not(self.driver_name is None):
608
 
            if self.driver_type is None:
609
 
                ret += "      <driver name='%(name)s'/>\n" % { "name": self.driver_name }
610
 
            else:
611
 
                ret += "      <driver name='%(name)s' type='%(type)s'/>\n" % { "name": self.driver_name, "type": self.driver_type }
 
679
        ret = "    <disk type='%s' device='%s'>\n" % (self.type, self.device)
 
680
 
 
681
        dname = self.driver_name
 
682
        if not dname and self.driver_cache:
 
683
            self.driver_name = "qemu"
 
684
 
 
685
        if not self.driver_name is None:
 
686
            dtypexml = ""
 
687
            if not self.driver_type is None:
 
688
                dtypexml = " type='%s'" % self.driver_type
 
689
 
 
690
            dcachexml = ""
 
691
            if not self.driver_cache is None:
 
692
                dcachexml = " cache='%s'" % self.driver_cache
 
693
 
 
694
            ret += "      <driver name='%s'%s%s/>\n" % (self.driver_name,
 
695
                                                      dtypexml, dcachexml)
 
696
 
612
697
        if path is not None:
613
 
            ret += "      <source %(typeattr)s='%(disk)s'/>\n" % { "typeattr": typeattr, "disk": path }
 
698
            ret += "      <source %s='%s'/>\n" % (typeattr, path)
614
699
 
615
700
        bus_xml = ""
616
701
        if self.bus is not None:
617
702
            bus_xml = " bus='%s'" % self.bus
618
 
        ret += "      <target dev='%s'" % disknode + \
619
 
                      "%s" % bus_xml + \
620
 
                      "/>\n"
 
703
        ret += "      <target dev='%s'%s/>\n" % (disknode, bus_xml)
621
704
 
622
705
        ro = self.read_only
623
706
 
624
707
        if self.device == self.DEVICE_CDROM:
625
708
            ro = True
 
709
        if self.shareable:
 
710
            ret += "      <shareable/>\n"
626
711
        if ro:
627
712
            ret += "      <readonly/>\n"
628
713
        ret += "    </disk>"
666
751
                        ((need / (1024*1024)), (avail / (1024*1024)))
667
752
        return (ret, msg)
668
753
 
669
 
    def is_conflict_disk(self, conn):
 
754
    def is_conflict_disk(self, conn, return_names=False):
670
755
        """
671
756
        check if specified storage is in use by any other VMs on passed
672
757
        connection.
673
758
 
674
759
        @param conn: connection to check for collisions on
675
760
        @type conn: libvirt.virConnect
 
761
        @param return_names: Whether or not to return a list of VM names using
 
762
                             the same storage (default = False)
 
763
        @type return_names: C{bool}
676
764
 
677
 
        @return: True if a collision, False otherwise
 
765
        @return: True if a collision, False otherwise (list of names if
 
766
                 return_names passed)
678
767
        @rtype: C{bool}
679
768
        """
680
 
        vms = []
681
 
        # get working domain's name
682
 
        ids = conn.listDomainsID()
683
 
        for i in ids:
684
 
            try:
685
 
                vm = conn.lookupByID(i)
686
 
                vms.append(vm)
687
 
            except libvirt.libvirtError:
688
 
                # guest probably in process of dieing
689
 
                logging.warn("Failed to lookup domain id %d" % i)
690
 
        # get defined domain
691
 
        names = conn.listDefinedDomains()
692
 
        for name in names:
693
 
            try:
694
 
                vm = conn.lookupByName(name)
695
 
                vms.append(vm)
696
 
            except libvirt.libvirtError:
697
 
                # guest probably in process of dieing
698
 
                logging.warn("Failed to lookup domain name %s" % name)
 
769
        active, inactive = _util.fetch_all_guests(conn)
 
770
        vms = active + inactive
699
771
 
700
772
        if self.vol_object:
701
773
            path = self.vol_object.path()
705
777
        if not path:
706
778
            return False
707
779
 
 
780
        def count_cb(ctx):
 
781
            c = 0
 
782
 
 
783
            c += ctx.xpathEval("count(/domain/devices/disk/source[@dev='%s'])" % path)
 
784
            c += ctx.xpathEval("count(/domain/devices/disk/source[@file='%s'])" % path)
 
785
            return c
 
786
 
708
787
        count = 0
 
788
        names = []
709
789
        for vm in vms:
710
 
            doc = None
711
 
            try:
712
 
                doc = libxml2.parseDoc(vm.XMLDesc(0))
713
 
            except:
714
 
                continue
715
 
            ctx = doc.xpathNewContext()
716
 
            try:
717
 
                try:
718
 
                    count += ctx.xpathEval("count(/domain/devices/disk/source[@dev='%s'])" % path)
719
 
                    count += ctx.xpathEval("count(/domain/devices/disk/source[@file='%s'])" % path)
720
 
                except:
721
 
                    continue
722
 
            finally:
723
 
                if ctx is not None:
724
 
                    ctx.xpathFreeContext()
725
 
                if doc is not None:
726
 
                    doc.freeDoc()
 
790
            xml = vm.XMLDesc(0)
 
791
            tmpcount = _util.get_xml_path(xml, func = count_cb)
 
792
            if tmpcount:
 
793
                count += tmpcount
 
794
                names.append(vm.name())
 
795
 
 
796
        ret = False
727
797
        if count > 0:
728
 
            return True
729
 
        else:
730
 
            return False
 
798
            ret = True
 
799
        if return_names:
 
800
            ret = names
 
801
 
 
802
        return ret
731
803
 
732
804
    def _get_target_type(self):
733
805
        """