From fd4d864cd7739c9bf106ccf997217b0d13a7dcb9 Mon Sep 17 00:00:00 2001 From: David Lehman Date: Mon, 12 Aug 2013 18:19:26 -0500 Subject: [PATCH 7/9] Add some locking functionality. Uevent handlers are run in a separate thread, so this is a prerequisite for uevent support. As of now, the following (re-entrant) locks are defined: blivet_lock -- general lock for Blivet instance devicetree_lock -- lock for DeviceTree device and action lists Each Device instance has its own lock. Additionally, the Device class has a lock for the id counter. TODO: handle updates like UUIDs from newly-created FS via uevent handlers TODO: don't lock things down more than is necessary while processing actions --- blivet/__init__.py | 140 +++++++++++++++++++++++ blivet/devicefactory.py | 8 ++ blivet/devices.py | 297 ++++++++++++++++++++++++++++++++++++++++++++++-- blivet/devicetree.py | 192 +++++++++++++++++++++++++++---- blivet/partitioning.py | 8 ++ blivet/threads.py | 85 ++++++++++++++ 6 files changed, 697 insertions(+), 33 deletions(-) create mode 100644 blivet/threads.py diff --git a/blivet/__init__.py b/blivet/__init__.py index 15759b3..621fcde 100644 --- a/blivet/__init__.py +++ b/blivet/__init__.py @@ -92,6 +92,11 @@ from size import Size import shelve import contextlib +from threads import blivet_lock +from threads import devicetree_lock +from threads import synchronized +from threads import device_mutex + import gettext _ = lambda x: gettext.ldgettext("blivet", x) @@ -122,6 +127,7 @@ def enable_installer_mode(): flags.installer_mode = True +@synchronized(blivet_lock) def storageInitialize(storage, ksdata, protected): """ Perform installer-specific storage initialization. """ from pyanaconda.flags import flags as anaconda_flags @@ -155,6 +161,7 @@ def storageInitialize(storage, ksdata, protected): if d.name not in ksdata.ignoredisk.ignoredisk] log.debug("onlyuse is now: %s" % (",".join(ksdata.ignoredisk.onlyuse))) +@synchronized(blivet_lock) def turnOnFilesystems(storage, mountOnly=False): """ Perform installer-specific activation of storage configuration. """ if not flags.installer_mode: @@ -189,6 +196,7 @@ def turnOnFilesystems(storage, mountOnly=False): if not mountOnly: writeEscrowPackets(storage) +@synchronized(blivet_lock) def writeEscrowPackets(storage): escrowDevices = filter(lambda d: d.format.type == "luks" and \ d.format.escrow_cert, @@ -249,6 +257,7 @@ class StorageDiscoveryConfig(object): # disklabels depends on this flag. self.clearNonExistent = False + @synchronized(blivet_lock) def update(self, ksdata): self.ignoredDisks = ksdata.ignoredisk.ignoredisk[:] self.exclusiveDisks = ksdata.ignoredisk.onlyuse[:] @@ -331,6 +340,7 @@ class Blivet(object): if notify and self.ueventNotifyCB: self.ueventNotifyCB(action, device) + @synchronized(blivet_lock) def enableUeventMonitoring(self): """ Enable monitoring and handling of block device uevents. """ monitor = pyudev.Monitor.from_netlink(global_udev) @@ -338,12 +348,14 @@ class Blivet(object): self._pyudev_observer = pyudev.MonitorObserver(monitor, self.ueventCB) self._pyudev_observer.start() + @synchronized(blivet_lock) def disableUeventMonitoring(self): """ Disable monitoring and handling of block device uevents. """ if self._pyudev_observer: self._pyudev_observer.stop() self._pyudev_observer = None + @synchronized(blivet_lock) def registerUeventNotifyCB(self, cb): """ Register a uevent notification callback. @@ -361,6 +373,7 @@ class Blivet(object): self.ueventNotifyCB = cb + @synchronized(blivet_lock) def doIt(self): self.devicetree.processActions() if not flags.installer_mode: @@ -424,6 +437,7 @@ class Blivet(object): self.dumpState("final") @property + @synchronized(blivet_lock) def nextID(self): id = self._nextID self._nextID += 1 @@ -435,6 +449,7 @@ class Blivet(object): except Exception as e: log.error("failure tearing down device tree: %s" % e) + @synchronized(blivet_lock) def reset(self, cleanupOnly=False): """ Reset storage configuration to reflect actual system state. @@ -494,6 +509,7 @@ class Blivet(object): self.updateBootLoaderDiskList() @property + @synchronized(devicetree_lock) def unusedDevices(self): used_devices = [] for root in self.roots: @@ -517,6 +533,7 @@ class Blivet(object): return list(_all.difference(used)) @property + @synchronized(devicetree_lock) def devices(self): """ A list of all the devices in the device tree. """ devices = self.devicetree.devices @@ -524,6 +541,7 @@ class Blivet(object): return devices @property + @synchronized(devicetree_lock) def disks(self): """ A list of the disks in the device tree. @@ -544,6 +562,7 @@ class Blivet(object): return disks @property + @synchronized(devicetree_lock) def partitioned(self): """ A list of the partitioned devices in the device tree. @@ -571,6 +590,7 @@ class Blivet(object): return partitioned @property + @synchronized(devicetree_lock) def partitions(self): """ A list of the partitions in the device tree. @@ -583,6 +603,7 @@ class Blivet(object): return partitions @property + @synchronized(devicetree_lock) def vgs(self): """ A list of the LVM Volume Groups in the device tree. @@ -595,6 +616,7 @@ class Blivet(object): return vgs @property + @synchronized(devicetree_lock) def lvs(self): """ A list of the LVM Logical Volumes in the device tree. @@ -607,18 +629,21 @@ class Blivet(object): return lvs @property + @synchronized(devicetree_lock) def thinlvs(self): thin = self.devicetree.getDevicesByType("lvmthinlv") thin.sort(key=lambda d: d.name) return thin @property + @synchronized(devicetree_lock) def thinpools(self): pools = self.devicetree.getDevicesByType("lvmthinpool") pools.sort(key=lambda d: d.name) return pools @property + @synchronized(devicetree_lock) def pvs(self): """ A list of the LVM Physical Volumes in the device tree. @@ -631,6 +656,7 @@ class Blivet(object): pvs.sort(key=lambda d: d.name) return pvs + @synchronized(devicetree_lock) def unusedPVs(self, vg=None): unused = [] for pv in self.pvs: @@ -646,6 +672,7 @@ class Blivet(object): return unused @property + @synchronized(devicetree_lock) def mdarrays(self): """ A list of the MD arrays in the device tree. @@ -658,6 +685,7 @@ class Blivet(object): return arrays @property + @synchronized(devicetree_lock) def mdcontainers(self): """ A list of the MD containers in the device tree. """ arrays = self.devicetree.getDevicesByType("mdcontainer") @@ -665,6 +693,7 @@ class Blivet(object): return arrays @property + @synchronized(devicetree_lock) def mdmembers(self): """ A list of the MD member devices in the device tree. @@ -677,6 +706,7 @@ class Blivet(object): members.sort(key=lambda d: d.name) return members + @synchronized(devicetree_lock) def unusedMDMembers(self, array=None): unused = [] for member in self.mdmembers: @@ -692,11 +722,13 @@ class Blivet(object): return unused @property + @synchronized(devicetree_lock) def btrfsVolumes(self): return sorted(self.devicetree.getDevicesByType("btrfs volume"), key=lambda d: d.name) @property + @synchronized(devicetree_lock) def swaps(self): """ A list of the swap devices in the device tree. @@ -710,6 +742,7 @@ class Blivet(object): return swaps @property + @synchronized(devicetree_lock) def protectedDevices(self): devices = self.devicetree.devices protected = [d for d in devices if d.protected] @@ -717,10 +750,12 @@ class Blivet(object): return protected @property + @synchronized(devicetree_lock) def liveImage(self): """ The OS image used by live installs. """ return None + @synchronized(blivet_lock) def shouldClear(self, device, **kwargs): clearPartType = kwargs.get("clearPartType", self.config.clearPartType) clearPartDisks = kwargs.get("clearPartDisks", @@ -799,6 +834,7 @@ class Blivet(object): return True + @synchronized(devicetree_lock) def recursiveRemove(self, device): log.debug("removing %s" % device.name) devices = self.deviceDeps(device) @@ -822,6 +858,7 @@ class Blivet(object): else: self.destroyDevice(device) + @synchronized(devicetree_lock) def clearPartitions(self): """ Clear partitions and dependent devices from disks. @@ -862,6 +899,7 @@ class Blivet(object): self.updateBootLoaderDiskList() + @synchronized(devicetree_lock) def initializeDisk(self, disk): """ (Re)initialize a disk by creating a disklabel on it. @@ -897,6 +935,7 @@ class Blivet(object): create_action = ActionCreateFormat(disk, format=newLabel) self.devicetree.registerAction(create_action) + @synchronized(devicetree_lock) def removeEmptyExtendedPartitions(self): for disk in self.partitioned: log.debug("checking whether disk %s has an empty extended" % disk.name) @@ -909,6 +948,7 @@ class Blivet(object): extended = self.devicetree.getDeviceByName(extended_name) self.destroyDevice(extended) + @synchronized(devicetree_lock) def getFreeSpace(self, disks=None, clearPartType=None): """ Return a dict with free space info for each disk. @@ -961,9 +1001,11 @@ class Blivet(object): return free @property + @synchronized(devicetree_lock) def names(self): return self.devicetree.names + @synchronized(devicetree_lock) def exceptionDisks(self): """ Return a list of removable devices to save exceptions to. @@ -1004,6 +1046,7 @@ class Blivet(object): return dests + @synchronized(devicetree_lock) def deviceImmutable(self, device, ignoreProtected=False): """ Return any reason the device cannot be modified/removed. @@ -1072,9 +1115,11 @@ class Blivet(object): return False + @synchronized(devicetree_lock) def deviceDeps(self, device): return self.devicetree.getDependentDevices(device) + @synchronized(blivet_lock) def newPartition(self, *args, **kwargs): """ Return a new PartitionDevice instance for configuring. """ if kwargs.has_key("fmt_type"): @@ -1099,6 +1144,7 @@ class Blivet(object): return PartitionDevice(name, *args, **kwargs) + @synchronized(blivet_lock) def newMDArray(self, *args, **kwargs): """ Return a new MDRaidArrayDevice instance for configuring. """ if kwargs.has_key("fmt_type"): @@ -1123,6 +1169,7 @@ class Blivet(object): return MDRaidArrayDevice(name, *args, **kwargs) + @synchronized(blivet_lock) def newVG(self, *args, **kwargs): """ Return a new LVMVolumeGroupDevice instance. """ pvs = kwargs.pop("parents", []) @@ -1149,6 +1196,7 @@ class Blivet(object): return LVMVolumeGroupDevice(name, pvs, *args, **kwargs) + @synchronized(blivet_lock) def newLV(self, *args, **kwargs): """ Return a new LVMLogicalVolumeDevice instance. """ thin_volume = kwargs.pop("thin_volume", False) @@ -1198,6 +1246,7 @@ class Blivet(object): return device_class(name, *args, **kwargs) + @synchronized(blivet_lock) def newBTRFS(self, *args, **kwargs): """ Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice. """ log.debug("newBTRFS: args = %s ; kwargs = %s" % (args, kwargs)) @@ -1246,10 +1295,13 @@ class Blivet(object): device.format = getFormat("btrfs", **fmt_args) return device + @synchronized(blivet_lock) def newBTRFSSubVolume(self, *args, **kwargs): kwargs["subvol"] = True return self.newBTRFS(*args, **kwargs) + @synchronized(blivet_lock) + @device_mutex("device") def createDevice(self, device): """ Schedule creation of a device. @@ -1260,6 +1312,8 @@ class Blivet(object): if device.format.type: self.devicetree.registerAction(ActionCreateFormat(device)) + @synchronized(blivet_lock) + @device_mutex("device") def destroyDevice(self, device): """ Schedule destruction of a device. """ if device.format.exists and device.format.type: @@ -1269,11 +1323,15 @@ class Blivet(object): action = ActionDestroyDevice(device) self.devicetree.registerAction(action) + @synchronized(blivet_lock) + @device_mutex("device") def formatDevice(self, device, format): """ Schedule formatting of a device. """ self.devicetree.registerAction(ActionDestroyFormat(device)) self.devicetree.registerAction(ActionCreateFormat(device, format)) + @synchronized(blivet_lock) + @device_mutex("device") def resetDevice(self, device): """ Cancel all scheduled actions and reset formatting. """ actions = self.devicetree.findActions(device=device) @@ -1283,6 +1341,8 @@ class Blivet(object): # make sure any random overridden attributes are reset device.format = copy.copy(device.originalFormat) + @synchronized(blivet_lock) + @device_mutex("device") def resizeDevice(self, device, new_size): classes = [] if device.resizable: @@ -1365,6 +1425,7 @@ class Blivet(object): return tmp + @synchronized(devicetree_lock) def suggestContainerName(self, hostname=None, prefix=""): """ Return a reasonable, unused device name. """ if not prefix: @@ -1397,6 +1458,7 @@ class Blivet(object): return name + @synchronized(devicetree_lock) def suggestDeviceName(self, parent=None, swap=None, mountpoint=None, prefix=""): """ Return a suitable, unused name for a new logical volume. """ @@ -1441,6 +1503,8 @@ class Blivet(object): return name + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def savePassphrase(self, device): """ Save a device's LUKS passphrase in case of reset. """ passphrase = device.format._LUKS__passphrase @@ -1448,6 +1512,7 @@ class Blivet(object): self.devicetree._DeviceTree__luksDevs[device.format.uuid] = passphrase self.devicetree._DeviceTree__passphrases.append(passphrase) + @synchronized(blivet_lock) def doEncryptionPassphraseRetrofits(self): """ Add the global passphrase to all preexisting LUKS devices. @@ -1469,11 +1534,13 @@ class Blivet(object): log.error("failed to add new passphrase to existing " "device %s" % device.path) + @synchronized(blivet_lock) def setupDiskImages(self): self.devicetree.setDiskImages(self.config.diskImages) self.devicetree.setupDiskImages() @property + @synchronized(devicetree_lock) def fileSystemFreeSpace(self): mountpoints = ["/", "/usr"] free = 0 @@ -1498,6 +1565,7 @@ class Blivet(object): return free + @synchronized(blivet_lock) def sanityCheck(self): """ Run a series of tests to verify the storage configuration. @@ -1682,15 +1750,19 @@ class Blivet(object): return (errors, warnings) + @device_mutex("device") def isProtected(self, device): """ Return True is the device is protected. """ return device.protected + @synchronized(blivet_lock) def checkNoDisks(self): """Check that there are valid disk devices.""" if not self.disks: raise NoDisksError() + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def dumpState(self, suffix): """ Dump the current device list to the storage shelf. """ key = "devices.%d.%s" % (time.time(), suffix) @@ -1698,6 +1770,7 @@ class Blivet(object): shelf[key] = [d.dict for d in self.devices] @property + @synchronized(blivet_lock) def packages(self): pkgs = set() pkgs.update(_platform.packages) @@ -1712,6 +1785,7 @@ class Blivet(object): return list(pkgs) + @synchronized(blivet_lock) def write(self): if not os.path.isdir("%s/etc" % ROOT_PATH): os.mkdir("%s/etc" % ROOT_PATH) @@ -1723,34 +1797,42 @@ class Blivet(object): self.zfcp.write(ROOT_PATH) self.dasd.write(ROOT_PATH) + @synchronized(blivet_lock) def turnOnSwap(self, upgrading=None): self.fsset.turnOnSwap(rootPath=ROOT_PATH, upgrading=upgrading) + @synchronized(blivet_lock) def mountFilesystems(self, raiseErrors=None, readOnly=None, skipRoot=False): self.fsset.mountFilesystems(rootPath=ROOT_PATH, raiseErrors=raiseErrors, readOnly=readOnly, skipRoot=skipRoot) + @synchronized(blivet_lock) def umountFilesystems(self, ignoreErrors=True, swapoff=True): self.fsset.umountFilesystems(ignoreErrors=ignoreErrors, swapoff=swapoff) + @synchronized(blivet_lock) def parseFSTab(self, chroot=None): self.fsset.parseFSTab(chroot=chroot) + @synchronized(blivet_lock) def mkDevRoot(self): self.fsset.mkDevRoot() + @synchronized(blivet_lock) def createSwapFile(self, device, size): self.fsset.createSwapFile(device, size) @property + @synchronized(blivet_lock) def bootloader(self): if self._bootloader is None and flags.installer_mode: self._bootloader = get_bootloader() return self._bootloader + @synchronized(blivet_lock) def updateBootLoaderDiskList(self): if not self.bootloader: return @@ -1759,6 +1841,7 @@ class Blivet(object): boot_disks.sort(cmp=self.compareDisks, key=lambda d: d.name) self.bootloader.set_disk_list(boot_disks) + @synchronized(blivet_lock) def setUpBootLoader(self): """ Propagate ksdata into BootLoader. """ if not self.bootloader or not self.ksdata: @@ -1777,6 +1860,8 @@ class Blivet(object): log.debug("failed to set bootloader stage1 device: %s" % e) @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def bootDisk(self): disk = None if self.ksdata: @@ -1785,6 +1870,8 @@ class Blivet(object): return disk @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def bootDevice(self): dev = None if self.fsset: @@ -1792,10 +1879,12 @@ class Blivet(object): return dev @property + @synchronized(blivet_lock) def bootLoaderDevice(self): return getattr(self.bootloader, "stage1_device", None) @property + @synchronized(blivet_lock) def bootFSTypes(self): """A list of all valid filesystem types for the boot partition.""" fstypes = [] @@ -1804,6 +1893,7 @@ class Blivet(object): return fstypes @property + @synchronized(blivet_lock) def defaultBootFSType(self): """The default filesystem type for the boot partition.""" fstype = None @@ -1812,9 +1902,11 @@ class Blivet(object): return fstype @property + @synchronized(blivet_lock) def defaultFSType(self): return self._defaultFSType + @synchronized(blivet_lock) def setDefaultFSType(self, newtype): """ Set the default fstype for this instance. @@ -1833,10 +1925,14 @@ class Blivet(object): self._defaultFSType = newtype @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def mountpoints(self): return self.fsset.mountpoints @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def rootDevice(self): return self.fsset.rootDevice @@ -1928,6 +2024,9 @@ class Blivet(object): return fstype + @synchronized(blivet_lock) + @synchronized(devicetree_lock) + @device_mutex("device") def factoryDevice(self, device_type, size, **kwargs): """ Schedule creation of a device based on a top-down specification. @@ -1979,6 +2078,8 @@ class Blivet(object): factory.configure() return factory.device + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def copy(self): log.debug("starting Blivet copy") new = copy.deepcopy(self) @@ -2020,6 +2121,8 @@ class Blivet(object): log.debug("finished Blivet copy") return new + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def __deepcopy__(self, memo): """ Create a deep copy of a Blivet instance. @@ -2040,6 +2143,8 @@ class Blivet(object): return new + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def getActiveMounts(self): """ Reflect active mounts in the appropriate devices' formats. """ log.info("collecting information about active mounts") @@ -2091,6 +2196,8 @@ class Blivet(object): device.format._mountpoint = mountpoint # active mountpoint device.format.mountopts = options + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def updateKSData(self): """ Update ksdata to reflect the settings of this Blivet instance. """ if not self.ksdata or not self.mountpoints: @@ -2195,6 +2302,8 @@ class Blivet(object): parent = getattr(self.ksdata, list_attr) parent.dataList().append(data) +@synchronized(blivet_lock) +@synchronized(devicetree_lock) def mountExistingSystem(fsset, rootDevice, allowDirty=None, dirtyCB=None, readOnly=None): @@ -2363,6 +2472,7 @@ class CryptTab(object): def get(self, key, default=None): return self.mappings.get(key, default) +@synchronized(devicetree_lock) def get_containing_device(path, devicetree): """ Return the device that a path resides on. """ if not os.path.exists(path): @@ -2480,6 +2590,8 @@ class FSSet(object): return sorted(self.devicetree.devices, key=lambda d: d.path) @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def mountpoints(self): filesystems = {} for device in self.devices: @@ -2585,6 +2697,8 @@ class FSSet(object): return device + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def parseFSTab(self, chroot=None): """ parse /etc/fstab @@ -2665,6 +2779,8 @@ class FSSet(object): # just write duplicates back out post-install self.preserveLines.append(line) + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def turnOnSwap(self, rootPath="", upgrading=None): """ Activate the system's swap space. """ if not flags.installer_mode: @@ -2694,6 +2810,8 @@ class FSSet(object): else: break + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def mountFilesystems(self, rootPath="", readOnly=None, skipRoot=False, raiseErrors=None): """ Mount the system's filesystems. """ @@ -2755,6 +2873,8 @@ class FSSet(object): self.active = True + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def umountFilesystems(self, ignoreErrors=True, swapoff=True): """ unmount filesystems, except swap if swapoff == False """ devices = self.mountpoints.values() + self.swapDevices @@ -2772,6 +2892,8 @@ class FSSet(object): self.active = False + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def createSwapFile(self, device, size): """ Create and activate a swap file under ROOT_PATH. """ filename = "/SWAP" @@ -2795,6 +2917,8 @@ class FSSet(object): # nasty, nasty self.devicetree._addDevice(dev) + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def mkDevRoot(self): root = self.rootDevice dev = "%s/%s" % (ROOT_PATH, root.path) @@ -2803,6 +2927,8 @@ class FSSet(object): os.mknod("%s/dev/root" % (ROOT_PATH,), stat.S_IFBLK | 0600, rdev) @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def swapDevices(self): swaps = [] for device in self.devices: @@ -2811,6 +2937,8 @@ class FSSet(object): return swaps @property + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def rootDevice(self): for path in ["/", ROOT_PATH]: for device in self.devices: @@ -2822,6 +2950,8 @@ class FSSet(object): if mountpoint == path: return device + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def write(self): """ write out all config files based on the set of filesystems """ # /etc/fstab @@ -2850,6 +2980,8 @@ class FSSet(object): else: log.info("not writing out mpath configuration") + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def crypttab(self): # if we are upgrading, do we want to update crypttab? # gut reaction says no, but plymouth needs the names to be very @@ -2875,6 +3007,8 @@ class FSSet(object): return self.cryptTab.crypttab() + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def mdadmConf(self): """ Return the contents of mdadm.conf. """ arrays = self.devicetree.getDevicesByType("mdarray") @@ -2901,6 +3035,8 @@ class FSSet(object): return conf + @synchronized(blivet_lock) + @synchronized(devicetree_lock) def fstab (self): format = "%-23s %-23s %-7s %-15s %d %d\n" fstab = """ @@ -2992,6 +3128,8 @@ def getReleaseString(): return (relArch, relName, relVer) +@synchronized(blivet_lock) +@synchronized(devicetree_lock) def findExistingInstallations(devicetree): if not os.path.exists(ROOT_PATH): util.makedirs(ROOT_PATH) @@ -3067,6 +3205,8 @@ class Root(object): def device(self): return self.mounts.get("/") +@synchronized(blivet_lock) +@synchronized(devicetree_lock) def parseFSTab(devicetree, chroot=None): """ parse /etc/fstab and return a tuple of a mount dict and swap list """ if not chroot or not os.path.isdir(chroot): diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index 627819f..5567817 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -34,6 +34,8 @@ from devicelibs.lvm import LVM_PE_SIZE from .partitioning import SameSizeSet from .partitioning import TotalSizeSet from .partitioning import doPartitioning +from threads import blivet_lock +from threads import synchronized import gettext _ = lambda x: gettext.ldgettext("blivet", x) @@ -52,6 +54,7 @@ DEVICE_TYPE_BTRFS = 3 DEVICE_TYPE_DISK = 4 DEVICE_TYPE_LVM_THINP = 5 +@synchronized(blivet_lock) def get_device_type(device): device_types = {"partition": DEVICE_TYPE_PARTITION, "lvmlv": DEVICE_TYPE_LVM, @@ -71,6 +74,7 @@ def get_device_type(device): return device_type +@synchronized(blivet_lock) def get_raid_level(device): # TODO: move this into StorageDevice use_dev = device @@ -90,6 +94,7 @@ def get_raid_level(device): return raid_level +@synchronized(blivet_lock) def get_device_factory(blivet, device_type, size, **kwargs): """ Return a suitable DeviceFactory instance for device_type. """ disks = kwargs.pop("disks", []) @@ -374,6 +379,7 @@ class DeviceFactory(object): return [] # FIXME: This is nuts. Move specifics into the appropriate classes. + @synchronized(blivet_lock) def get_container(self, device=None, name=None, allow_existing=False): """ Return the best choice of container for this factory. @@ -686,6 +692,7 @@ class DeviceFactory(object): self.child_factory = factory factory.parent_factory = self + @synchronized(blivet_lock) def configure(self): """ Configure the factory's device(s). @@ -878,6 +885,7 @@ class PartitionSetFactory(PartitionFactory): def devices(self): return self._devices + @synchronized(blivet_lock) def configure(self): """ Configure the factory's device set. diff --git a/blivet/devices.py b/blivet/devices.py index b1557b4..f47178a 100644 --- a/blivet/devices.py +++ b/blivet/devices.py @@ -119,6 +119,10 @@ from storage_log import log_method_call from udev import * from formats import get_device_format_class, getFormat, DeviceFormat +from threading import RLock +from threads import exclusive +from threads import NamedRLock + import gettext _ = lambda x: gettext.ldgettext("blivet", x) P_ = lambda x, y, z: gettext.ldngettext("blivet", x, y, z) @@ -206,6 +210,7 @@ class Device(object): # This is a counter for generating unique ids for Devices. _id = 0 + _id_lock = RLock() _type = "device" _packages = [] @@ -232,12 +237,16 @@ class Device(object): self.kids = 0 # Set this instance's id and increment the counter. - self.id = Device._id - Device._id += 1 + with Device._id_lock: + self.id = Device._id + Device._id += 1 + + self.lock = NamedRLock(name=self._name) for parent in self.parents: parent.addChild() + @exclusive def __deepcopy__(self, memo): """ Create a deep copy of a Device instance. @@ -247,7 +256,7 @@ class Device(object): new = self.__class__.__new__(self.__class__) memo[id(self)] = new dont_copy_attrs = ('_raidSet', 'node') - shallow_copy_attrs = ('_partedDevice', '_partedPartition') + shallow_copy_attrs = ('_partedDevice', '_partedPartition', "lock") for (attr, value) in self.__dict__.items(): if attr in dont_copy_attrs: setattr(new, attr, value) @@ -258,6 +267,7 @@ class Device(object): return new + @exclusive def __repr__(self): s = ("%(type)s instance (%(id)s) --\n" " name = %(name)s status = %(status)s" @@ -269,51 +279,62 @@ class Device(object): "parents": pprint.pformat([str(p) for p in self.parents])}) return s + @exclusive def __str__(self): s = "%s %s (%d)" % (self.type, self.name, self.id) return s @property + @exclusive def dict(self): d = {"type": self.type, "name": self.name, "parents": [p.name for p in self.parents]} return d + @exclusive def removeChild(self): log_method_call(self, name=self.name, kids=self.kids) self.kids -= 1 + @exclusive def addChild(self): log_method_call(self, name=self.name, kids=self.kids) self.kids += 1 + @exclusive def setup(self): """ Open, or set up, a device. """ raise NotImplementedError("setup method not defined for Device") + @exclusive def teardown(self, recursive=None): """ Close, or tear down, a device. """ raise NotImplementedError("teardown method not defined for Device") + @exclusive def create(self): """ Create the device. """ raise NotImplementedError("create method not defined for Device") + @exclusive def destroy(self): """ Destroy the device. """ raise NotImplementedError("destroy method not defined for Device") + @exclusive def setupParents(self, orig=False): """ Run setup method of all parent devices. """ log_method_call(self, name=self.name, orig=orig, kids=self.kids) for parent in self.parents: parent.setup(orig=orig) + @exclusive def teardownParents(self, recursive=None): """ Run teardown method of all parent devices. """ for parent in self.parents: parent.teardown(recursive=recursive) + @exclusive def dependsOn(self, dep): """ Return True if this device depends on dep. """ # XXX does a device depend on itself? @@ -326,10 +347,12 @@ class Device(object): return False + @exclusive def dracutSetupArgs(self): return set() @property + @exclusive def status(self): """ This device's status. @@ -340,26 +363,31 @@ class Device(object): return False @property + @exclusive def name(self): """ This device's name. """ return self._name @property + @exclusive def isleaf(self): """ True if this device has no children. """ return self.kids == 0 @property + @exclusive def typeDescription(self): """ String describing the device type. """ return self._type @property + @exclusive def type(self): """ Device type. """ return self._type @property + @exclusive def ancestors(self): l = set([self]) for p in [d for d in self.parents if d not in l]: @@ -367,6 +395,7 @@ class Device(object): return list(l) @property + @exclusive def packages(self): """ List of packages required to manage devices of this type. @@ -381,6 +410,7 @@ class Device(object): return packages @property + @exclusive def services(self): """ List of services required to manage devices of this type. @@ -519,6 +549,7 @@ class StorageDevice(Device): return s @property + @exclusive def packages(self): """ List of packages required to manage devices of this type. @@ -536,6 +567,7 @@ class StorageDevice(Device): return packages @property + @exclusive def services(self): """ List of services required to manage devices of this type. @@ -553,6 +585,7 @@ class StorageDevice(Device): return services @property + @exclusive def disks(self): """ A list of all disks this device depends on, including itself. """ _disks = [] @@ -567,6 +600,7 @@ class StorageDevice(Device): return _disks @property + @exclusive def encrypted(self): """ True if this device, or any it requires, is encrypted. """ crypted = False @@ -581,6 +615,7 @@ class StorageDevice(Device): return crypted @property + @exclusive def partedDevice(self): if self.exists and self.status and not self._partedDevice: log.debug("looking up parted Device: %s" % self.path) @@ -596,9 +631,11 @@ class StorageDevice(Device): return self._partedDevice + @exclusive def _getTargetSize(self): return self._targetSize + @exclusive def _setTargetSize(self, newsize): self._targetSize = newsize @@ -606,6 +643,7 @@ class StorageDevice(Device): lambda s, v: s._setTargetSize(v), doc="Target size of this device") + @exclusive def __repr__(self): s = Device.__repr__(self) s += (" uuid = %(uuid)s size = %(size)s\n" @@ -624,6 +662,7 @@ class StorageDevice(Device): return s @property + @exclusive def dict(self): d = super(StorageDevice, self).dict d.update({"uuid": self.uuid, "size": self.size, @@ -634,10 +673,12 @@ class StorageDevice(Device): return d @property + @exclusive def path(self): """ Device node representing this device. """ return "%s/%s" % (self._devDir, self.name) + @exclusive def updateSysfsPath(self): """ Update this device's sysfs path. """ log_method_call(self, self.name, status=self.status) @@ -651,11 +692,13 @@ class StorageDevice(Device): log.debug("%s sysfsPath set to %s" % (self.name, self.sysfsPath)) @property + @exclusive def formatArgs(self): """ Device-specific arguments to format creation program. """ return [] @property + @exclusive def resizable(self): """ Can this type of device be resized? """ return (self._resizable and self.exists and @@ -679,12 +722,14 @@ class StorageDevice(Device): log.warning("failed to notify kernel of change: %s" % e) @property + @exclusive def fstabSpec(self): spec = self.path if self.format and self.format.uuid: spec = "UUID=%s" % self.format.uuid return spec + @exclusive def resize(self): """ Resize the device. @@ -713,6 +758,7 @@ class StorageDevice(Device): """ Perform device-specific setup operations. """ pass + @exclusive def setup(self, orig=False): """ Open, or set up, a device. """ log_method_call(self, self.name, orig=orig, status=self.status, @@ -755,6 +801,7 @@ class StorageDevice(Device): """ Perform device-specific teardown operations. """ pass + @exclusive def teardown(self, recursive=None): """ Close, or tear down, a device. """ log_method_call(self, self.name, status=self.status, @@ -784,6 +831,7 @@ class StorageDevice(Device): """ Perform device-specific create operations. """ pass + @exclusive def create(self): """ Create the device. """ log_method_call(self, self.name, status=self.status) @@ -819,6 +867,7 @@ class StorageDevice(Device): """ Perform device-specific destruction operations. """ pass + @exclusive def destroy(self): """ Destroy the device. """ log_method_call(self, self.name, status=self.status) @@ -830,6 +879,7 @@ class StorageDevice(Device): """ Perform post-destruction operations. """ self.exists = False + @exclusive def setupParents(self, orig=False): """ Run setup method of all parent devices. """ log_method_call(self, name=self.name, orig=orig, kids=self.kids) @@ -844,6 +894,7 @@ class StorageDevice(Device): if _format.type and _format.exists: _format.setup() + @exclusive def _getSize(self): """ Get the device's size in MB, accounting for pending changes. """ if self.exists and not self.mediaPresent: @@ -858,6 +909,7 @@ class StorageDevice(Device): return size + @exclusive def _setSize(self, newsize): """ Set the device's size to a new value. """ if newsize > self.maxSize: @@ -870,6 +922,7 @@ class StorageDevice(Device): doc="The device's size in MB, accounting for pending changes") @property + @exclusive def currentSize(self): """ The device's actual size. """ size = 0 @@ -880,6 +933,7 @@ class StorageDevice(Device): return size @property + @exclusive def minSize(self): """ The minimum size this device can be. """ if self.format.minSize: @@ -888,6 +942,7 @@ class StorageDevice(Device): return self.size @property + @exclusive def maxSize(self): """ The maximum size this device can be. """ if self.format.maxSize > self.currentSize: @@ -896,6 +951,7 @@ class StorageDevice(Device): return self.format.maxSize @property + @exclusive def status(self): """ This device's status. @@ -907,6 +963,7 @@ class StorageDevice(Device): return False return os.access(self.path, os.W_OK) + @exclusive def _setFormat(self, format): """ Set the Device's format. """ if not format: @@ -920,6 +977,7 @@ class StorageDevice(Device): self._format = format self._format.device = self.path + @exclusive def _getFormat(self): return self._format @@ -927,11 +985,13 @@ class StorageDevice(Device): lambda d,f: d._setFormat(f), doc="The device's formatting.") + @exclusive def preCommitFixup(self, *args, **kwargs): """ Do any necessary pre-commit fixups.""" pass @property + @exclusive def removable(self): devpath = os.path.normpath(self.sysfsPath) remfile = os.path.normpath("%s/removable" % devpath) @@ -940,32 +1000,39 @@ class StorageDevice(Device): open(remfile).readline().strip() == "1") @property + @exclusive def isDisk(self): return self._isDisk @property + @exclusive def partitionable(self): return self._partitionable @property + @exclusive def partitioned(self): return self.format.type == "disklabel" and self.partitionable @property + @exclusive def serial(self): return self._serial @property + @exclusive def model(self): if not self._model: self._model = getattr(self.partedDevice, "model", "") return self._model @property + @exclusive def vendor(self): return self._vendor @property + @exclusive def growable(self): """ True if this device or it's component devices are growable. """ grow = getattr(self, "req_grow", False) @@ -976,6 +1043,7 @@ class StorageDevice(Device): break return grow + @exclusive def checkSize(self): """ Check to make sure the size of the device is allowed by the format used. @@ -991,6 +1059,7 @@ class StorageDevice(Device): return -1 return 0 + @exclusive def populateKSData(self, data): # the common pieces are basically the formatting self.format.populateKSData(data) @@ -1044,6 +1113,7 @@ class DiskDevice(StorageDevice): serial=serial, model=model, vendor=vendor, bus=bus) + @exclusive def __repr__(self): s = StorageDevice.__repr__(self) s += (" removable = %(removable)s partedDevice = %(partedDevice)r" % @@ -1051,6 +1121,7 @@ class DiskDevice(StorageDevice): return s @property + @exclusive def mediaPresent(self): if flags.testing: return True @@ -1064,10 +1135,12 @@ class DiskDevice(StorageDevice): return self.partedDevice.getSize() != 0 @property + @exclusive def description(self): return self.model @property + @exclusive def size(self): """ The disk's size in MB """ return super(DiskDevice, self).size @@ -1217,6 +1290,7 @@ class PartitionDevice(StorageDevice): self.req_start_sector = start self.req_end_sector = end + @exclusive def __repr__(self): s = StorageDevice.__repr__(self) s += (" grow = %(grow)s max size = %(maxsize)s bootable = %(bootable)s\n" @@ -1241,6 +1315,7 @@ class PartitionDevice(StorageDevice): return s @property + @exclusive def dict(self): d = super(PartitionDevice, self).dict d.update({"type": self.partType}) @@ -1256,6 +1331,7 @@ class PartitionDevice(StorageDevice): "flags": self.partedPartition.getFlagsAsString()}) return d + @exclusive def _setTargetSize(self, newsize): if newsize != self.currentSize: # change this partition's geometry in-memory so that other @@ -1270,6 +1346,7 @@ class PartitionDevice(StorageDevice): start=geometry.start, end=geometry.end) @property + @exclusive def path(self): if not self.parents: devDir = StorageDevice._devDir @@ -1279,6 +1356,7 @@ class PartitionDevice(StorageDevice): return "%s/%s" % (devDir, self.name) @property + @exclusive def partType(self): """ Get the partition's type (as parted constant). """ try: @@ -1292,26 +1370,31 @@ class PartitionDevice(StorageDevice): return ptype @property + @exclusive def isExtended(self): return (self.partType is not None and self.partType & parted.PARTITION_EXTENDED) @property + @exclusive def isLogical(self): return (self.partType is not None and self.partType & parted.PARTITION_LOGICAL) @property + @exclusive def isPrimary(self): return (self.partType is not None and self.partType == parted.PARTITION_NORMAL) @property + @exclusive def isProtected(self): return (self.partType is not None and self.partType & parted.PARTITION_PROTECTED) @property + @exclusive def fstabSpec(self): spec = self.path if self.disk and self.disk.type == 'dasd': @@ -1320,9 +1403,11 @@ class PartitionDevice(StorageDevice): spec = "UUID=%s" % self.format.uuid return spec + @exclusive def _getPartedPartition(self): return self._partedPartition + @exclusive def _setPartedPartition(self, partition): """ Set this PartitionDevice's parted Partition instance. """ log_method_call(self, self.name) @@ -1340,6 +1425,7 @@ class PartitionDevice(StorageDevice): partedPartition = property(lambda d: d._getPartedPartition(), lambda d,p: d._setPartedPartition(p)) + @exclusive def preCommitFixup(self, *args, **kwargs): """ Re-get self.partedPartition from the original disklabel. """ log_method_call(self, self.name) @@ -1365,15 +1451,18 @@ class PartitionDevice(StorageDevice): self.partedPartition = _partition + @exclusive def _getWeight(self): return self.req_base_weight + @exclusive def _setWeight(self, weight): self.req_base_weight = weight weight = property(lambda d: d._getWeight(), lambda d,w: d._setWeight(w)) + @exclusive def updateName(self): if self.partedPartition is None: self._name = self.req_name @@ -1381,6 +1470,7 @@ class PartitionDevice(StorageDevice): self._name = \ devicePathToName(self.partedPartition.getDeviceNodeName()) + @exclusive def dependsOn(self, dep): """ Return True if this device depends on dep. """ if isinstance(dep, PartitionDevice) and dep.isExtended and \ @@ -1390,6 +1480,7 @@ class PartitionDevice(StorageDevice): return Device.dependsOn(self, dep) @property + @exclusive def isleaf(self): """ True if this device has no children. """ no_kids = super(PartitionDevice, self).isleaf @@ -1398,11 +1489,7 @@ class PartitionDevice(StorageDevice): self.disk.format.logicalPartitions)) return (no_kids and not extended_has_logical) - def _setFormat(self, format): - """ Set the Device's format. """ - log_method_call(self, self.name) - StorageDevice._setFormat(self, format) - + @exclusive def _setBootable(self, bootable): """ Set the bootable flag for this partition. """ if self.partedPartition: @@ -1420,17 +1507,20 @@ class PartitionDevice(StorageDevice): else: self.req_bootable = bootable + @exclusive def _getBootable(self): return self._bootable or self.req_bootable bootable = property(_getBootable, _setBootable) + @exclusive def flagAvailable(self, flag): if not self.partedPartition: return return self.partedPartition.isFlagAvailable(flag) + @exclusive def getFlag(self, flag): log_method_call(self, path=self.path, flag=flag) if not self.partedPartition or not self.flagAvailable(flag): @@ -1438,6 +1528,7 @@ class PartitionDevice(StorageDevice): return self.partedPartition.getFlag(flag) + @exclusive def setFlag(self, flag): log_method_call(self, path=self.path, flag=flag) if not self.partedPartition or not self.flagAvailable(flag): @@ -1445,6 +1536,7 @@ class PartitionDevice(StorageDevice): self.partedPartition.setFlag(flag) + @exclusive def unsetFlag(self, flag): log_method_call(self, path=self.path, flag=flag) if not self.partedPartition or not self.flagAvailable(flag): @@ -1453,6 +1545,7 @@ class PartitionDevice(StorageDevice): self.partedPartition.unsetFlag(flag) @property + @exclusive def isMagic(self): if not self.disk: return False @@ -1461,6 +1554,7 @@ class PartitionDevice(StorageDevice): magic = self.disk.format.magicPartitionNumber return (number == magic) + @exclusive def probe(self): """ Probe for any missing information about this device. @@ -1565,6 +1659,7 @@ class PartitionDevice(StorageDevice): return (constraint, newGeometry) + @exclusive def resize(self): """ Resize the device. @@ -1616,6 +1711,7 @@ class PartitionDevice(StorageDevice): self.disk.format.removePartition(part) self.disk.format.commit() + @exclusive def deactivate(self): """ This is never called. For instructional purposes only. @@ -1631,6 +1727,7 @@ class PartitionDevice(StorageDevice): raise DeviceTeardownError("failed to tear down device-mapper partition %s: %s" % (self.name, e)) udev_settle() + @exclusive def _getSize(self): """ Get the device's size. """ size = self._size @@ -1639,6 +1736,7 @@ class PartitionDevice(StorageDevice): size = self.partedPartition.getSize() return size + @exclusive def _setSize(self, newsize): """ Set the device's size (for resize, not creation). @@ -1668,6 +1766,7 @@ class PartitionDevice(StorageDevice): new_length = (newsize * (1024 * 1024)) / physicalSectorSize geometry.length = new_length + @exclusive def _getDisk(self): """ The disk that contains this partition.""" try: @@ -1676,6 +1775,7 @@ class PartitionDevice(StorageDevice): disk = None return disk + @exclusive def _setDisk(self, disk): """Change the parent. @@ -1696,6 +1796,7 @@ class PartitionDevice(StorageDevice): disk = property(lambda p: p._getDisk(), lambda p,d: p._setDisk(d)) @property + @exclusive def maxSize(self): """ The maximum size this partition can be. """ # XXX Only allow growth up to the amount of free space following this @@ -1715,6 +1816,7 @@ class PartitionDevice(StorageDevice): return min(self.format.maxSize, maxPartSize) @property + @exclusive def currentSize(self): """ The device's actual size. """ if self.exists: @@ -1723,11 +1825,13 @@ class PartitionDevice(StorageDevice): return 0 @property + @exclusive def resizable(self): """ Can this type of device be resized? """ return super(PartitionDevice, self).resizable and \ self.disk.type != 'dasd' + @exclusive def checkSize(self): """ Check to make sure the size of the device is allowed by the format used. @@ -1747,6 +1851,7 @@ class PartitionDevice(StorageDevice): return -1 return 0 + @exclusive def populateKSData(self, data): super(PartitionDevice, self).populateKSData(data) data.resize = (self.exists and self.targetSize and @@ -1796,6 +1901,7 @@ class DMDevice(StorageDevice): self.target = target self.dmUuid = dmUuid + @exclusive def __repr__(self): s = StorageDevice.__repr__(self) s += (" target = %(target)s dmUuid = %(dmUuid)s" % @@ -1803,22 +1909,26 @@ class DMDevice(StorageDevice): return s @property + @exclusive def dict(self): d = super(DMDevice, self).dict d.update({"target": self.target, "dmUuid": self.dmUuid}) return d @property + @exclusive def fstabSpec(self): """ Return the device specifier for use in /etc/fstab. """ return self.path @property + @exclusive def mapName(self): """ This device's device-mapper map name """ return self.name @property + @exclusive def status(self): _status = False for map in block.dm.maps(): @@ -1831,6 +1941,7 @@ class DMDevice(StorageDevice): #def getTargetType(self): # return dm.getDmTarget(name=self.name) + @exclusive def getDMNode(self): """ Return the dm-X (eg: dm-0) device node for this device. """ log_method_call(self, self.name, status=self.status) @@ -1839,6 +1950,7 @@ class DMDevice(StorageDevice): return dm.dm_node_from_name(self.name) + @exclusive def setupPartitions(self): log_method_call(self, name=self.name, kids=self.kids) rc = util.run_program(["kpartx", "-a", "-s", self.path]) @@ -1846,6 +1958,7 @@ class DMDevice(StorageDevice): raise DMError("partition activation failed for '%s'" % self.name) udev_settle() + @exclusive def teardownPartitions(self): log_method_call(self, name=self.name, kids=self.kids) rc = util.run_program(["kpartx", "-d", "-s", self.path]) @@ -1853,6 +1966,7 @@ class DMDevice(StorageDevice): raise DMError("partition deactivation failed for '%s'" % self.name) udev_settle() + @exclusive def _setName(self, name): """ Set the device's map name. """ log_method_call(self, self.name, status=self.status) @@ -1866,6 +1980,7 @@ class DMDevice(StorageDevice): lambda d,n: d._setName(n)) @property + @exclusive def slave(self): """ This device's backing device. """ return self.parents[0] @@ -1919,9 +2034,11 @@ class DMLinearDevice(DMDevice): dm.dm_remove(self.name) udev_settle() + @exclusive def deactivate(self, recursive=False): StorageDevice.teardown(self, recursive=recursive) + @exclusive def teardown(self, recursive=None): """ Close, or tear down, a device. """ log_method_call(self, self.name, status=self.status, @@ -1932,6 +2049,7 @@ class DMLinearDevice(DMDevice): log.debug("not tearing down dm-linear device %s" % self.name) @property + @exclusive def description(self): return self.model @@ -1987,6 +2105,7 @@ class LUKSDevice(DMCryptDevice): uuid=None, exists=exists) @property + @exclusive def size(self): if not self.exists or not self.partedDevice: size = float(self.slave.size) - crypto.LUKS_METADATA_SIZE @@ -2006,9 +2125,11 @@ class LUKSDevice(DMCryptDevice): StorageDevice._postTeardown(self, recursive=recursive) + @exclusive def dracutSetupArgs(self): return set(["rd.luks.uuid=luks-%s" % self.slave.format.uuid]) + @exclusive def populateKSData(self, data): self.slave.populateKSData(data) data.encrypted = True @@ -2095,6 +2216,7 @@ class LVMVolumeGroupDevice(DMDevice): # >0 is fixed self.size_policy = self.size + @exclusive def __repr__(self): s = DMDevice.__repr__(self) s += (" free = %(free)s PE Size = %(peSize)s PE Count = %(peCount)s\n" @@ -2116,6 +2238,7 @@ class LVMVolumeGroupDevice(DMDevice): return s @property + @exclusive def dict(self): d = super(LVMVolumeGroupDevice, self).dict d.update({"free": self.free, "peSize": self.peSize, @@ -2133,16 +2256,19 @@ class LVMVolumeGroupDevice(DMDevice): return d @property + @exclusive def mapName(self): """ This device's device-mapper map name """ # Thank you lvm for this lovely hack. return self.name.replace("-","--") @property + @exclusive def path(self): """ Device node representing this device. """ return "%s/%s" % (self._devDir, self.mapName) + @exclusive def updateSysfsPath(self): """ Update this device's sysfs path. """ log_method_call(self, self.name, status=self.status) @@ -2152,6 +2278,7 @@ class LVMVolumeGroupDevice(DMDevice): self.sysfsPath = '' @property + @exclusive def status(self): """ The device's status (True means active). """ if not self.exists: @@ -2173,6 +2300,7 @@ class LVMVolumeGroupDevice(DMDevice): return True + @exclusive def _addDevice(self, device): """ Add a new physical volume device to the volume group. @@ -2207,6 +2335,7 @@ class LVMVolumeGroupDevice(DMDevice): if self.complete and flags.installer_mode: self.setup() + @exclusive def _removeDevice(self, device): """ Remove a physical volume from the volume group. @@ -2261,6 +2390,7 @@ class LVMVolumeGroupDevice(DMDevice): lvm.vgdeactivate(self.name) lvm.vgremove(self.name) + @exclusive def reduce(self, pv_list): """ Remove the listed PVs from the VG. """ log_method_call(self, self.name, status=self.status) @@ -2270,6 +2400,7 @@ class LVMVolumeGroupDevice(DMDevice): lvm.vgreduce(self.name, pv_list) # XXX do we need to notify the kernel? + @exclusive def _addLogVol(self, lv): """ Add an LV to this VG. """ if lv in self._lvs: @@ -2286,6 +2417,7 @@ class LVMVolumeGroupDevice(DMDevice): log.debug("Adding %s/%dMB to %s" % (lv.name, lv.size, self.name)) self._lvs.append(lv) + @exclusive def _removeLogVol(self, lv): """ Remove an LV from this VG. """ if lv not in self.lvs: @@ -2293,6 +2425,7 @@ class LVMVolumeGroupDevice(DMDevice): self._lvs.remove(lv) + @exclusive def _addPV(self, pv): """ Add a PV to this VG. """ if pv in self.pvs: @@ -2308,9 +2441,11 @@ class LVMVolumeGroupDevice(DMDevice): # and update our pv count self.pvCount = len(self.parents) + @exclusive def addMember(self, member): self._addPV(member) + @exclusive def _removePV(self, pv): """ Remove an PV from this VG. """ if not pv in self.pvs: @@ -2326,6 +2461,7 @@ class LVMVolumeGroupDevice(DMDevice): # and update our pv count self.pvCount = len(self.parents) + @exclusive def removeMember(self, member): self._removePV(member) @@ -2336,6 +2472,7 @@ class LVMVolumeGroupDevice(DMDevice): # -- liblvm may contain support for in-memory devices @property + @exclusive def isModified(self): """ Return True if the VG has changes queued that LVM is unaware of. """ modified = True @@ -2345,6 +2482,7 @@ class LVMVolumeGroupDevice(DMDevice): return modified @property + @exclusive def snapshotSpace(self): """ Total space used by snapshots in this volume group. """ used = 0 @@ -2357,6 +2495,7 @@ class LVMVolumeGroupDevice(DMDevice): return used @property + @exclusive def reservedSpace(self): """ Reserved space in this VG, in MB """ reserved = 0 @@ -2368,6 +2507,7 @@ class LVMVolumeGroupDevice(DMDevice): return self.align(reserved, roundup=True) @property + @exclusive def size(self): """ The size of this VG """ # TODO: just ask lvm if isModified returns False @@ -2380,6 +2520,7 @@ class LVMVolumeGroupDevice(DMDevice): return size @property + @exclusive def extents(self): """ Number of extents in this VG """ # TODO: just ask lvm if isModified returns False @@ -2387,6 +2528,7 @@ class LVMVolumeGroupDevice(DMDevice): return self.size / self.peSize @property + @exclusive def freeSpace(self): """ The amount of free space in this VG (in MB). """ # TODO: just ask lvm if isModified returns False @@ -2400,11 +2542,13 @@ class LVMVolumeGroupDevice(DMDevice): return free @property + @exclusive def freeExtents(self): """ The number of free extents in this VG. """ # TODO: just ask lvm if isModified returns False return self.freeSpace / self.peSize + @exclusive def align(self, size, roundup=None): """ Align a size to a multiple of physical extent size. """ size = util.numeric_type(size) @@ -2420,24 +2564,29 @@ class LVMVolumeGroupDevice(DMDevice): return long((round(size / pesize) * pesize) / 1024) @property + @exclusive def pvs(self): """ A list of this VG's PVs """ return self.parents[:] # we don't want folks changing our list @property + @exclusive def lvs(self): """ A list of this VG's LVs """ return self._lvs[:] # we don't want folks changing our list @property + @exclusive def thinpools(self): return [l for l in self._lvs if isinstance(l, LVMThinPoolDevice)] @property + @exclusive def thinlvs(self): return [l for l in self._lvs if isinstance(l, LVMThinLogicalVolumeDevice)] @property + @exclusive def complete(self): """Check if the vg has all its pvs in the system Return True if complete. @@ -2448,6 +2597,7 @@ class LVMVolumeGroupDevice(DMDevice): return len(self.pvs) == self.pvCount or not self.exists + @exclusive def populateKSData(self, data): super(LVMVolumeGroupDevice, self).populateKSData(data) data.vgname = self.name @@ -2550,6 +2700,7 @@ class LVMLogicalVolumeDevice(DMDevice): # here we go with the circular references self.parents[0]._addLogVol(self) + @exclusive def __repr__(self): s = DMDevice.__repr__(self) s += (" VG device = %(vgdev)r\n" @@ -2563,6 +2714,7 @@ class LVMLogicalVolumeDevice(DMDevice): return s @property + @exclusive def dict(self): d = super(LVMLogicalVolumeDevice, self).dict if self.exists: @@ -2575,9 +2727,11 @@ class LVMLogicalVolumeDevice(DMDevice): return d @property + @exclusive def mirrored(self): return self.copies > 1 + @exclusive def _setSize(self, size): size = self.vg.align(util.numeric_type(size)) log.debug("trying to set lv %s size to %dMB" % (self.name, size)) @@ -2591,32 +2745,38 @@ class LVMLogicalVolumeDevice(DMDevice): size = property(StorageDevice._getSize, _setSize) @property + @exclusive def maxSize(self): """ The maximum size this lv can be. """ return min(self.format.maxSize, self.size + self.vg.freeSpace) @property + @exclusive def vgSpaceUsed(self): """ Space occupied by this LV, not including snapshots. """ return (self.vg.align(self.size, roundup=True) * self.copies + self.logSize + self.metaDataSize) @property + @exclusive def vg(self): """ This Logical Volume's Volume Group. """ return self.parents[0] @property + @exclusive def mapName(self): """ This device's device-mapper map name """ # Thank you lvm for this lovely hack. return "%s-%s" % (self.vg.mapName, self._name.replace("-","--")) @property + @exclusive def path(self): """ Device node representing this device. """ return "%s/%s" % (self._devDir, self.mapName) + @exclusive def getDMNode(self): """ Return the dm-X (eg: dm-0) device node for this device. """ log_method_call(self, self.name, status=self.status) @@ -2626,20 +2786,24 @@ class LVMLogicalVolumeDevice(DMDevice): return dm.dm_node_from_name(self.mapName) @property + @exclusive def name(self): """ This device's name. """ return "%s-%s" % (self.vg.name, self._name) @property + @exclusive def lvname(self): """ The LV's name (not including VG name). """ return self._name @property + @exclusive def complete(self): """ Test if vg exits and if it has all pvs. """ return self.vg.complete + @exclusive def setupParents(self, orig=False): # parent is a vg, which has no formatting (or device for that matter) Device.setupParents(self, orig=orig) @@ -2688,6 +2852,7 @@ class LVMLogicalVolumeDevice(DMDevice): log_method_call(self, self.name, status=self.status) lvm.lvremove(self.vg.name, self._name) + @exclusive def _getSinglePV(self): validpvs = filter(lambda x: float(x.size) >= self.size, self.vg.pvs) @@ -2696,6 +2861,7 @@ class LVMLogicalVolumeDevice(DMDevice): return [validpvs[0].path] + @exclusive def resize(self): log_method_call(self, self.name, status=self.status) self._preDestroy() @@ -2711,11 +2877,13 @@ class LVMLogicalVolumeDevice(DMDevice): udev_settle() lvm.lvresize(self.vg.name, self._name, self.size) + @exclusive def dracutSetupArgs(self): # Note no mapName usage here, this is a lvm cmdline name, which # is different (ofcourse) return set(["rd.lvm.lv=%s/%s" % (self.vg.name, self._name)]) + @exclusive def checkSize(self): """ Check to make sure the size of the device is allowed by the format used. @@ -2735,6 +2903,7 @@ class LVMLogicalVolumeDevice(DMDevice): return -1 return 0 + @exclusive def populateKSData(self, data): super(LVMLogicalVolumeDevice, self).populateKSData(data) data.vgname = self.vg.name @@ -2806,6 +2975,7 @@ class LVMThinPoolDevice(LVMLogicalVolumeDevice): self.chunkSize = chunksize or 0 self._lvs = [] + @exclusive def _addLogVol(self, lv): """ Add an LV to this pool. """ if lv in self._lvs: @@ -2816,6 +2986,7 @@ class LVMThinPoolDevice(LVMLogicalVolumeDevice): log.debug("Adding %s/%dMB to %s" % (lv.name, lv.size, self.name)) self._lvs.append(lv) + @exclusive def _removeLogVol(self, lv): """ Remove an LV from this pool. """ if lv not in self._lvs: @@ -2825,21 +2996,25 @@ class LVMThinPoolDevice(LVMLogicalVolumeDevice): self.vg._removeLogVol(lv) @property + @exclusive def lvs(self): """ A list of this pool's LVs """ return self._lvs[:] # we don't want folks changing our list @property + @exclusive def vgSpaceUsed(self): space = super(LVMThinPoolDevice, self).vgSpaceUsed space += lvm.get_pool_padding(space, pesize=self.vg.peSize) return space @property + @exclusive def usedSpace(self): return sum(l.poolSpaceUsed for l in self.lvs) @property + @exclusive def freeSpace(self): return self.size - self.usedSpace @@ -2851,9 +3026,11 @@ class LVMThinPoolDevice(LVMLogicalVolumeDevice): metadatasize=self.metaDataSize, chunksize=self.chunkSize) + @exclusive def dracutSetupArgs(self): return set() + @exclusive def populateKSData(self, data): super(LVMThinPoolDevice, self).populateKSData(data) data.thin_pool = True @@ -2866,14 +3043,17 @@ class LVMThinLogicalVolumeDevice(LVMLogicalVolumeDevice): _resizable = True @property + @exclusive def pool(self): return self.parents[0] @property + @exclusive def vg(self): return self.pool.vg @property + @exclusive def poolSpaceUsed(self): """ The total space used within the thin pool by this volume. @@ -2884,9 +3064,11 @@ class LVMThinLogicalVolumeDevice(LVMLogicalVolumeDevice): return self.vg.align(self.size, roundup=True) @property + @exclusive def vgSpaceUsed(self): return 0 # the pool's size is already accounted for in the vg + @exclusive def _setSize(self, size): size = self.vg.align(util.numeric_type(size)) self._size = size @@ -2900,6 +3082,7 @@ class LVMThinLogicalVolumeDevice(LVMLogicalVolumeDevice): lvm.thinlvcreate(self.vg.name, self.pool.lvname, self.lvname, self.size) + @exclusive def populateKSData(self, data): super(LVMThinLogicalVolumeDevice, self).populateKSData(data) data.thin_volume = True @@ -2983,6 +3166,7 @@ class MDRaidArrayDevice(StorageDevice): % (self.path, self.uuid)) @property + @exclusive def level(self): """ Return the raid level @@ -2992,6 +3176,7 @@ class MDRaidArrayDevice(StorageDevice): return self._level @level.setter + @exclusive def level(self, value): """ Set the RAID level and enforce restrictions based on it. @@ -3007,6 +3192,7 @@ class MDRaidArrayDevice(StorageDevice): self.createBitmap = self._level != 0 @property + @exclusive def rawArraySize(self): """ Calculate the raw array size without taking into account space reserved for metadata or chunkSize alignment. @@ -3036,11 +3222,13 @@ class MDRaidArrayDevice(StorageDevice): return size @property + @exclusive def superBlockSize(self): return mdraid.get_raid_superblock_size(self.rawArraySize, version=self.metadataVersion) @property + @exclusive def smallestMember(self): try: smallest = sorted(self.devices, key=lambda d: d.size)[0] @@ -3049,6 +3237,7 @@ class MDRaidArrayDevice(StorageDevice): return smallest @property + @exclusive def size(self): if not self.devices: return 0 @@ -3086,6 +3275,7 @@ class MDRaidArrayDevice(StorageDevice): return size @property + @exclusive def description(self): if self.level == mdraid.RAID0: levelstr = "stripe" @@ -3101,6 +3291,7 @@ class MDRaidArrayDevice(StorageDevice): else: return "MDRAID set (%s)" % levelstr + @exclusive def __repr__(self): s = StorageDevice.__repr__(self) s += (" level = %(level)s spares = %(spares)s\n" @@ -3114,6 +3305,7 @@ class MDRaidArrayDevice(StorageDevice): return s @property + @exclusive def dict(self): d = super(MDRaidArrayDevice, self).dict d.update({"level": self.level, @@ -3123,6 +3315,7 @@ class MDRaidArrayDevice(StorageDevice): return d @property + @exclusive def mdadmConfEntry(self): """ This array's mdadm.conf entry. """ if self.level is None or self.memberDevices is None or not self.uuid: @@ -3137,6 +3330,7 @@ class MDRaidArrayDevice(StorageDevice): return fmt % (self.path, self.level, self.memberDevices, self.uuid) @property + @exclusive def totalDevices(self): """ Total number of devices in the array, including spares. """ count = len(self.parents) @@ -3144,9 +3338,11 @@ class MDRaidArrayDevice(StorageDevice): count = self._totalDevices return count + @exclusive def _getMemberDevices(self): return self._memberDevices + @exclusive def _setMemberDevices(self, number): if not isinstance(number, int): raise ValueError("memberDevices is an integer") @@ -3158,6 +3354,7 @@ class MDRaidArrayDevice(StorageDevice): memberDevices = property(_getMemberDevices, _setMemberDevices, doc="number of member devices") + @exclusive def _getSpares(self): spares = 0 if self.memberDevices is not None: @@ -3169,6 +3366,7 @@ class MDRaidArrayDevice(StorageDevice): self._totalDevices = self.memberDevices return spares + @exclusive def _setSpares(self, spares): # FIXME: this is too simple to be right if self.totalDevices > spares: @@ -3176,6 +3374,7 @@ class MDRaidArrayDevice(StorageDevice): spares = property(_getSpares, _setSpares) + @exclusive def _addDevice(self, device): """ Add a new member device to the array. @@ -3223,6 +3422,7 @@ class MDRaidArrayDevice(StorageDevice): # information about it self._size = self.currentSize + @exclusive def _removeDevice(self, device): """ Remove a component device from the array. @@ -3239,6 +3439,7 @@ class MDRaidArrayDevice(StorageDevice): self.devices.remove(device) device.removeChild() + @exclusive def addMember(self, member): if member in self.parents: raise ValueError("member is already part of this array") @@ -3251,11 +3452,13 @@ class MDRaidArrayDevice(StorageDevice): member.addChild() self.memberDevices += 1 + @exclusive def removeMember(self, member): self._removeDevice(member) self.memberDevices -= 1 @property + @exclusive def status(self): """ This device's status. @@ -3282,6 +3485,7 @@ class MDRaidArrayDevice(StorageDevice): return status @property + @exclusive def degraded(self): """ Return True if the array is running in degraded mode. """ rc = False @@ -3294,6 +3498,7 @@ class MDRaidArrayDevice(StorageDevice): return rc @property + @exclusive def complete(self): if self.type == "mdbiosraidarray": members = len(self.parents[0].parents) @@ -3303,6 +3508,7 @@ class MDRaidArrayDevice(StorageDevice): return (self.memberDevices <= members) or not self.exists @property + @exclusive def devices(self): """ Return a list of this array's member device instances. """ return self.parents @@ -3331,6 +3537,7 @@ class MDRaidArrayDevice(StorageDevice): # give valid results self.sysfsPath = '' + @exclusive def teardown(self, recursive=None): """ Close, or tear down, a device. """ log_method_call(self, self.name, status=self.status, @@ -3353,6 +3560,7 @@ class MDRaidArrayDevice(StorageDevice): self._postTeardown(recursive=recursive) + @exclusive def preCommitFixup(self, *args, **kwargs): """ Determine create parameters for this set """ mountpoints = kwargs.pop("mountpoints") @@ -3398,6 +3606,7 @@ class MDRaidArrayDevice(StorageDevice): bitmap=self.createBitmap) @property + @exclusive def formatArgs(self): formatArgs = [] if self.format.type == "ext2": @@ -3412,6 +3621,7 @@ class MDRaidArrayDevice(StorageDevice): 'stride=%d' % (self.memberDevices * 16)] @property + @exclusive def mediaPresent(self): # Containers should not get any format handling done # (the device node does not allow read / write calls) @@ -3426,20 +3636,25 @@ class MDRaidArrayDevice(StorageDevice): return self.partedDevice is not None @property + @exclusive def model(self): return self.description @property + @exclusive def partitionable(self): return self.type == "mdbiosraidarray" @property + @exclusive def isDisk(self): return self.type == "mdbiosraidarray" + @exclusive def dracutSetupArgs(self): return set(["rd.md.uuid=%s" % self.uuid]) + @exclusive def populateKSData(self, data): if self.isDisk: return @@ -3488,9 +3703,11 @@ class DMRaidArrayDevice(DMDevice): self._raidSet = raidSet @property + @exclusive def raidSet(self): return self._raidSet + @exclusive def _addDevice(self, device): """ Add a new member device to the array. @@ -3513,20 +3730,24 @@ class DMRaidArrayDevice(DMDevice): device.addChild() @property + @exclusive def members(self): return self.parents @property + @exclusive def devices(self): """ Return a list of this array's member device instances. """ return self.parents + @exclusive def deactivate(self): """ Deactivate the raid set. """ log_method_call(self, self.name, status=self.status) # This call already checks if the set is not active. self._raidSet.deactivate() + @exclusive def activate(self): """ Activate the raid set. """ log_method_call(self, self.name, status=self.status) @@ -3540,6 +3761,7 @@ class DMRaidArrayDevice(DMDevice): controllable=self.controllable) self.activate() + @exclusive def teardown(self, recursive=None): """ Close, or tear down, a device. """ log_method_call(self, self.name, status=self.status, @@ -3550,13 +3772,16 @@ class DMRaidArrayDevice(DMDevice): log.debug("not tearing down dmraid device %s" % self.name) @property + @exclusive def description(self): return "BIOS RAID set (%s)" % self._raidSet.rs.set_type @property + @exclusive def model(self): return self.description + @exclusive def dracutSetupArgs(self): return set(["rd.dm.uuid=%s" % self.name]) @@ -3598,6 +3823,7 @@ class MultipathDevice(DMDevice): } @property + @exclusive def wwid(self): identity = self.identity ret = [] @@ -3607,21 +3833,25 @@ class MultipathDevice(DMDevice): return ":".join(ret) @property + @exclusive def model(self): if not self.parents: return "" return self.parents[0].model @property + @exclusive def vendor(self): if not self.parents: return "" return self.parents[0].vendor @property + @exclusive def description(self): return "WWID %s" % (self.wwid,) + @exclusive def addParent(self, parent): """ Add a parent device to the mpath. """ log_method_call(self, self.name, status=self.status) @@ -3632,6 +3862,7 @@ class MultipathDevice(DMDevice): else: self.parents.append(parent) + @exclusive def deactivate(self): """ This is never called, included just for documentation. @@ -3691,16 +3922,19 @@ class NoDevice(StorageDevice): StorageDevice.__init__(self, name, format=format, exists=True) @property + @exclusive def path(self): """ Device node representing this device. """ # the name may have a '.%d' suffix to make it unique return self.name.split(".")[0] + @exclusive def setup(self, orig=False): """ Open, or set up, a device. """ log_method_call(self, self.name, orig=orig, status=self.status, controllable=self.controllable) + @exclusive def teardown(self, recursive=False): """ Close, or tear down, a device. """ log_method_call(self, self.name, status=self.status, @@ -3708,10 +3942,12 @@ class NoDevice(StorageDevice): # just make sure the format is unmounted self._preTeardown(recursive=recursive) + @exclusive def create(self): """ Create the device. """ log_method_call(self, self.name, status=self.status) + @exclusive def destroy(self): """ Destroy the device. """ log_method_call(self, self.name, status=self.status) @@ -3748,10 +3984,12 @@ class FileDevice(StorageDevice): exists=exists, parents=parents) @property + @exclusive def fstabSpec(self): return self.name @property + @exclusive def path(self): root = "" try: @@ -3858,6 +4096,7 @@ class LoopDevice(StorageDevice): StorageDevice.__init__(self, name, format=format, size=size, exists=True, parents=parents) + @exclusive def updateName(self): """ Update this device's name. """ if not self.slave.status: @@ -3875,12 +4114,14 @@ class LoopDevice(StorageDevice): return self.name @property + @exclusive def status(self): return (self.slave.status and self.name.startswith("loop") and loop.get_loop_name(self.slave.path) == self.name) @property + @exclusive def size(self): return self.slave.size @@ -3907,6 +4148,7 @@ class LoopDevice(StorageDevice): self.sysfsPath = '' @property + @exclusive def slave(self): return self.parents[0] @@ -3943,6 +4185,7 @@ class iScsiDiskDevice(DiskDevice, NetworkStorageDevice): self.node.iface, self.nic)) + @exclusive def dracutSetupArgs(self): if self.ibft: return set(["iscsi_firmware"]) @@ -3989,6 +4232,7 @@ class FcoeDiskDevice(DiskDevice, NetworkStorageDevice): log.debug("created new fcoe disk %s (%s) @ %s" % (device, self.identifier, self.nic)) + @exclusive def dracutSetupArgs(self): dcb = True @@ -4021,6 +4265,7 @@ class OpticalDevice(StorageDevice): vendor=vendor, model=model) @property + @exclusive def mediaPresent(self): """ Return a boolean indicating whether or not the device contains media. @@ -4041,6 +4286,7 @@ class OpticalDevice(StorageDevice): os.close(fd) return True + @exclusive def eject(self): """ Eject the drawer. """ log_method_call(self, self.name, status=self.status) @@ -4066,6 +4312,7 @@ class ZFCPDiskDevice(DiskDevice): self.fcp_lun = kwargs.pop("fcp_lun") DiskDevice.__init__(self, device, **kwargs) + @exclusive def __repr__(self): s = DiskDevice.__repr__(self) s += (" hba_id = %(hba_id)s wwpn = %(wwpn)s fcp_lun = %(fcp_lun)s" % @@ -4075,12 +4322,14 @@ class ZFCPDiskDevice(DiskDevice): return s @property + @exclusive def description(self): return "FCP device %(device)s with WWPN %(wwpn)s and LUN %(lun)s" \ % {'device': self.hba_id, 'wwpn': self.wwpn, 'lun': self.fcp_lun} + @exclusive def dracutSetupArgs(self): return set(["rd.zfcp=%s,%s,%s" % (self.hba_id, self.wwpn, self.fcp_lun,)]) @@ -4094,12 +4343,15 @@ class DASDDevice(DiskDevice): DiskDevice.__init__(self, device, **kwargs) @property + @exclusive def description(self): return "DASD device %s" % self.busid + @exclusive def getOpts(self): return ["%s=%s" % (k, v) for k, v in self.opts.items() if v == '1'] + @exclusive def dracutSetupArgs(self): conf = "/etc/dasd.conf" line = None @@ -4159,25 +4411,30 @@ class NFSDevice(StorageDevice, NetworkStorageDevice): NetworkStorageDevice.__init__(self, device.split(":")[0]) @property + @exclusive def path(self): """ Device node representing this device. """ return self.name + @exclusive def setup(self, orig=False): """ Open, or set up, a device. """ log_method_call(self, self.name, orig=orig, status=self.status, controllable=self.controllable) + @exclusive def teardown(self, recursive=None): """ Close, or tear down, a device. """ log_method_call(self, self.name, status=self.status, controllable=self.controllable) + @exclusive def create(self): """ Create the device. """ log_method_call(self, self.name, status=self.status) self._preCreate() + @exclusive def destroy(self): """ Destroy the device. """ log_method_call(self, self.name, status=self.status) @@ -4196,6 +4453,7 @@ class BTRFSDevice(StorageDevice): self.req_size = kwargs.pop("size", None) super(BTRFSDevice, self).__init__(*args, **kwargs) + @exclusive def updateSysfsPath(self): """ Update this device's sysfs path. """ log_method_call(self, self.name, status=self.status) @@ -4213,27 +4471,33 @@ class BTRFSDevice(StorageDevice): super(BTRFSDevice, self)._preDestroy() self.setupParents(orig=True) + @exclusive def _getSize(self): size = sum([d.size for d in self.parents]) return size + @exclusive def _setSize(self, size): raise RuntimeError("cannot directly set size of btrfs volume") @property + @exclusive def currentSize(self): # at some point we'll want to make this a bit more realistic, but the # btrfs tools don't provide much of this type of information return self.size @property + @exclusive def status(self): return not any([not d.status for d in self.parents]) @property + @exclusive def _temp_dir_prefix(self): return "btrfs-tmp.%s" % self.id + @exclusive def _do_temp_mount(self, orig=False): if self.format.status or not self.exists: return @@ -4246,6 +4510,7 @@ class BTRFSDevice(StorageDevice): fmt.mount(mountpoint=tmpdir) + @exclusive def _undo_temp_mount(self): if getattr(self.format, "_mountpoint", None): fmt = self.format @@ -4261,6 +4526,7 @@ class BTRFSDevice(StorageDevice): os.rmdir(mountpoint) @property + @exclusive def path(self): return self.parents[0].path @@ -4297,6 +4563,7 @@ class BTRFSVolumeDevice(BTRFSDevice): mountopts="subvolid=0") self.originalFormat = copy.copy(self.format) + @exclusive def _setFormat(self, format): """ Set the Device's format. """ super(BTRFSVolumeDevice, self)._setFormat(format) @@ -4305,6 +4572,7 @@ class BTRFSVolumeDevice(BTRFSDevice): if label: self._name = label + @exclusive def _getSize(self): size = sum([d.size for d in self.parents]) if self.dataLevel in ("raid1", "raid10"): @@ -4312,6 +4580,7 @@ class BTRFSVolumeDevice(BTRFSDevice): return size + @exclusive def _addDevice(self, device): """ Add a new device to this volume. @@ -4337,6 +4606,7 @@ class BTRFSVolumeDevice(BTRFSDevice): self.parents.append(device) device.addChild() + @exclusive def _removeDevice(self, device): """ Remove a device from the volume. @@ -4353,6 +4623,7 @@ class BTRFSVolumeDevice(BTRFSDevice): device.removeChild() + @exclusive def addMember(self, member): if member in self.parents: raise ValueError("member is already part of this volume") @@ -4364,6 +4635,7 @@ class BTRFSVolumeDevice(BTRFSDevice): self.parents.append(member) member.addChild() + @exclusive def removeMember(self, member): if member not in self.parents: raise ValueError("member is not part of this volume") @@ -4374,12 +4646,14 @@ class BTRFSVolumeDevice(BTRFSDevice): self.parents.remove(member) member.removeChild() + @exclusive def _addSubVolume(self, vol): if vol.name in [v.name for v in self.subvolumes]: raise ValueError("subvolume %s already exists" % vol.name) self.subvolumes.append(vol) + @exclusive def _removeSubVolume(self, name): if name not in [v.name for v in self.subvolumes]: raise ValueError("cannot remove non-existent subvolume %s" % name) @@ -4387,6 +4661,7 @@ class BTRFSVolumeDevice(BTRFSDevice): names = [v.name for v in self.subvolumes] self.subvolumes.pop(names.index(name)) + @exclusive def listSubVolumes(self): subvols = [] if flags.installer_mode: @@ -4407,6 +4682,7 @@ class BTRFSVolumeDevice(BTRFSDevice): return subvols + @exclusive def createSubVolumes(self): self._do_temp_mount() for name, subvol in self.subvolumes: @@ -4415,6 +4691,7 @@ class BTRFSVolumeDevice(BTRFSDevice): subvol.create(mountpoint=self._temp_dir_prefix) self._undo_temp_mount() + @exclusive def removeSubVolume(self, name): raise NotImplementedError() @@ -4431,6 +4708,7 @@ class BTRFSVolumeDevice(BTRFSDevice): device.setup(orig=True) DeviceFormat(device=device.path, exists=True).destroy() + @exclusive def populateKSData(self, data): super(BTRFSVolumeDevice, self).populateKSData(data) data.dataLevel = self.dataLevel @@ -4449,9 +4727,11 @@ class BTRFSSubVolumeDevice(BTRFSDevice): self.volume._addSubVolume(self) @property + @exclusive def volume(self): return self.parents[0] + @exclusive def setupParents(self, orig=False): """ Run setup method of all parent devices. """ log_method_call(self, name=self.name, orig=orig, kids=self.kids) @@ -4476,6 +4756,7 @@ class BTRFSSubVolumeDevice(BTRFSDevice): btrfs.delete_subvolume(mountpoint, self.name) self.volume._undo_temp_mount() + @exclusive def populateKSData(self, data): super(BTRFSSubVolumeDevice, self).populateKSData(data) data.subvol = True diff --git a/blivet/devicetree.py b/blivet/devicetree.py index 119f299..ebeb910 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -48,6 +48,10 @@ from storage_log import log_method_call, log_method_return import parted import _ped +from threads import devicetree_lock +from threads import synchronized +from threads import device_mutex + import gettext _ = lambda x: gettext.ldgettext("blivet", x) @@ -79,6 +83,7 @@ class DeviceTree(object): iscsi=None, dasd=None): self.reset(conf, passphrase, luksDict, iscsi, dasd) + @synchronized(devicetree_lock) def reset(self, conf=None, passphrase=None, luksDict=None, iscsi=None, dasd=None): # internal data members @@ -137,6 +142,7 @@ class DeviceTree(object): self.ignoredDisks.append(disk) devicelibs.lvm.lvm_cc_addFilterRejectRegexp(disk) + @synchronized(devicetree_lock) def pruneActions(self): """ Remove redundant/obsolete actions from the action list. """ for action in reversed(self._actions[:]): @@ -150,6 +156,7 @@ class DeviceTree(object): % (obsolete.id, action.id)) self._actions.remove(obsolete) + @synchronized(devicetree_lock) def sortActions(self): """ Sort actions based on dependencies. """ if not self._actions: @@ -185,8 +192,73 @@ class DeviceTree(object): actions.append(self._actions[idx]) self._actions = actions + def _lockDevices(self, devices): + """ Acquire the mutex for every device in a list. """ + locked = [] + + # First, go through and grab as many locks as possible without blocking. + for idx in reversed(range(len(devices))): + log.debug(" trying non-blocking acquire: %s" % devices[idx]) + if devices[idx].lock.acquire(blocking=False): + locked.append(devices.pop(idx)) + log.debug(" got it") + + for idx in reversed(range(len(devices))): + log.debug(" trying blocking acquire: %s" % devices[idx]) + devices[idx].lock.acquire() + locked.append(devices.pop(idx)) + + return locked + + """ + We won't be able to process uevents during processActions if we're + going to hold the devicetree lock for the duration. What do we actually + need to lock down while this method runs? + + - action list + - at least while we make a copy of it + - device list + - at least devices whose actions are still queued + + - always lock the parent device's format while executing + + - partition actions require locking the disklabel + - lv actions require locking the vg + + - format actions require locking the device + + What would users expect to be able to do while actions run? + + - schedule actions on devices that do not have queued actions + - device list, action list + + Q: Do we need to allow users read-only access to objects while they are + locked? + A: Yes, probably -- especially if we're locking down the entire stack + while we process an action. + + Q: What changes do we need to prevent in ancestors while we process an + action? + A: Nothing external to blivet. Probably also a limited (?) set of + changes to the ancestors StorageDevice instances. + + """ def processActions(self, dryRun=None): """ Execute all registered actions. """ + # XXX Should this locking all use context managers? + # - We don't really handle exceptions at all in here, so meh? + # - OTOH it would be nice to start doing better now. + + devicetree_lock.acquire() + + # Acquire the lock for every device that an action will operate on. + # XXX Do I need to lock the whole stack for each device? + # - Here, or just while executing each action? + locked = self._lockDevices(list(set([a.device for a in self._actions]))) + actions_by_device = dict((d.id, []) for d in locked) + for action in self._actions: + actions_by_device[action.device.id].append(action.id) + log.info("resetting parted disks...") for device in self.devices: if device.partitioned: @@ -230,30 +302,65 @@ class DeviceTree(object): for action in self._actions: log.debug("action: %s" % action) - for action in self._actions[:]: + actions = self._actions[:] + devicetree_lock.release() + + # now, unlock any devices that are no longer associated with any actions + # due to pruning + action_devices = set(a.device for a in self._actions) + for idx in reversed(range(len(locked))): + ldev = locked[idx] + if ldev not in action_devices: + ldev.lock.release() + locked.remove(ldev) + + for action in actions[:]: log.info("executing action: %s" % action) if not dryRun: + # lock down the whole device stack for this action + action_locked = self._lockDevices(action.device.ancestors) try: - action.execute() - except DiskLabelCommitError: - # it's likely that a previous format destroy action - # triggered setup of an lvm or md device. - self.teardownAll() - action.execute() - - udev_settle() - for device in self._devices: - # make sure we catch any renumbering parted does - if device.exists and isinstance(device, PartitionDevice): - device.updateName() - device.format.device = device.path - - self._completed_actions.append(self._actions.pop(0)) + try: + action.execute() + except DiskLabelCommitError: + # it's likely that a previous format destroy action + # triggered setup of an lvm or md device. + self.teardownAll() + action.execute() + + udev_settle() + for device in self._devices: + # make sure we catch any renumbering parted does + if device.exists and isinstance(device, PartitionDevice): + device.updateName() + device.format.device = device.path + + self._completed_actions.append(actions.pop(0)) + except Exception: + for ldev in locked: + ldev.lock.release() + finally: + for ldev in action_locked: + ldev.lock.release() + + # If this is the last action operating on this device we can + # release the lock on the device. This will allow the uevents + # generated by the action to get received (by the devicetree) + # relatively quickly. + actions_by_device[action.device.id].remove(action.id) + if not actions_by_device[action.device.id]: + action.device.lock.release() + locked.remove(action.device) # removal of partitions makes use of originalFormat, so it has to stay # up to date in case of multiple passes through this method for disk in (d for d in self.devices if d.partitioned): - disk.originalFormat = copy.deepcopy(disk.format) + with disk.lock: + disk.originalFormat = copy.deepcopy(disk.format) + + # release locks on any devices whose lock we still hold (should be none) + for ldev in locked: + ldev.lock.release() def _checkForMountPoint(self, device): if not device: @@ -384,6 +491,7 @@ class DeviceTree(object): return removed + @synchronized(devicetree_lock) def _addDevice(self, newdev): """ Add a device to the tree. @@ -416,6 +524,8 @@ class DeviceTree(object): newdev.name, newdev.id)) + @synchronized(devicetree_lock) + @device_mutex("dev") def _removeDevice(self, dev, force=None, moddisk=True): """ Remove a device from the tree. @@ -438,15 +548,16 @@ class DeviceTree(object): raise ValueError("Cannot remove extended partition %s. " "Logical partitions present." % dev.name) - dev.disk.format.removePartition(dev.partedPartition) + with dev.disk.lock: + dev.disk.format.removePartition(dev.partedPartition) - # adjust all other PartitionDevice instances belonging to the - # same disk so the device name matches the potentially altered - # name of the parted.Partition - for device in self._devices: - if isinstance(device, PartitionDevice) and \ - device.disk == dev.disk: - device.updateName() + # adjust all other PartitionDevice instances belonging to the + # same disk so the device name matches the potentially altered + # name of the parted.Partition + for device in self._devices: + if isinstance(device, PartitionDevice) and \ + device.disk == dev.disk: + device.updateName() elif hasattr(dev, "pool"): dev.pool._removeLogVol(dev) elif hasattr(dev, "vg"): @@ -466,6 +577,7 @@ class DeviceTree(object): # Do we care about garbage collection? At all? parent.removeChild() + @synchronized(devicetree_lock) def registerAction(self, action): """ Register an action to be performed at a later time. @@ -491,6 +603,7 @@ class DeviceTree(object): log.info("registered action: %s" % action) self._actions.append(action) + @synchronized(devicetree_lock) def cancelAction(self, action): """ Cancel a registered action. @@ -512,6 +625,7 @@ class DeviceTree(object): self._actions.remove(action) + @synchronized(devicetree_lock) def findActions(self, device=None, type=None, object=None, path=None, devid=None): """ Find all actions that match all specified parameters. @@ -553,6 +667,7 @@ class DeviceTree(object): return actions + @device_mutex("dep") def getDependentDevices(self, dep): """ Return a list of devices that depend on dep. @@ -1104,6 +1219,7 @@ class DeviceTree(object): self._addDevice(device) return device + @synchronized(devicetree_lock) def addUdevDevice(self, info): name = udev_device_get_name(info) log_method_call(self, name=name, info=pprint.pformat(dict(info))) @@ -1731,6 +1847,7 @@ class DeviceTree(object): exists=True) self._addDevice(subvol) + @device_mutex("device") def handleUdevDeviceFormat(self, info, device): log_method_call(self, name=getattr(device, "name", None)) name = udev_device_get_name(info) @@ -1843,6 +1960,7 @@ class DeviceTree(object): device.originalFormat = copy.copy(device.format) + @device_mutex("device") def updateDeviceFormat(self, device): log.info("updating format of device: %s" % device) try: @@ -1856,6 +1974,7 @@ class DeviceTree(object): if device.format.type: log.info("got format: %s" % device.format) + @synchronized(devicetree_lock) def _handleInconsistencies(self): for vg in [d for d in self.devices if d.type == "lvmvg"]: if vg.complete: @@ -1867,6 +1986,7 @@ class DeviceTree(object): for pv in vg.pvs: devicelibs.lvm.lvm_cc_addFilterRejectRegexp(pv.name) + @synchronized(devicetree_lock) def cancelDeviceActions(self, device, full=True): """ Cancel all actions the depend on or operate on a given device. @@ -1888,6 +2008,7 @@ class DeviceTree(object): finally: self._actions.remove(action) + @synchronized(devicetree_lock) def hide(self, device): if device in self._hidden: return @@ -1925,6 +2046,7 @@ class DeviceTree(object): if isinstance(device, DASDDevice): self.dasd.removeDASD(device) + @synchronized(devicetree_lock) def unhide(self, device): # the hidden list should be in leaves-first order for hidden in reversed(self._hidden): @@ -1941,6 +2063,7 @@ class DeviceTree(object): if isinstance(device, DASDDevice): self.dasd.addDASD(device) + @synchronized(devicetree_lock) def setupDiskImages(self): """ Set up devices to represent the disk image files. """ for (name, path) in self.diskImages.items(): @@ -2033,6 +2156,7 @@ class DeviceTree(object): finally: self.restoreConfigs() + @synchronized(devicetree_lock) def _populate(self): log.info("DeviceTree.populate: ignoredDisks is %s ; exclusiveDisks is %s" % (self.ignoredDisks, self.exclusiveDisks)) @@ -2094,6 +2218,7 @@ class DeviceTree(object): self._applyDiskFilters() + @synchronized(devicetree_lock) def _applyDiskFilters(self): def _is_ignored(disk): return ((self.ignoredDisks and disk.name in self.ignoredDisks) or @@ -2120,6 +2245,7 @@ class DeviceTree(object): self.hide(disk) + @synchronized(devicetree_lock) def teardownAll(self): """ Run teardown methods on all devices. """ if not flags.installer_mode: @@ -2142,6 +2268,7 @@ class DeviceTree(object): except DeviceError as (msg, name): log.error("setup of %s failed: %s" % (device.name, msg)) + @synchronized(devicetree_lock) def getDeviceBySysfsPath(self, path, incomplete=False, hidden=False): if not path: return None @@ -2162,6 +2289,7 @@ class DeviceTree(object): log_method_return(self, found) return found + @synchronized(devicetree_lock) def getDeviceByUuid(self, uuid, incomplete=False, hidden=False): if not uuid: return None @@ -2185,6 +2313,7 @@ class DeviceTree(object): log_method_return(self, found) return found + @synchronized(devicetree_lock) def getDevicesBySerial(self, serial, incomplete=False, hidden=False): devices = self._devices[:] if hidden: @@ -2204,6 +2333,7 @@ class DeviceTree(object): log_method_return(self, retval) return retval + @synchronized(devicetree_lock) def getDeviceByLabel(self, label, incomplete=False, hidden=False): if not label: return None @@ -2228,6 +2358,7 @@ class DeviceTree(object): log_method_return(self, found) return found + @synchronized(devicetree_lock) def getDeviceByName(self, name, incomplete=False, hidden=False): log_method_call(self, name=name) if not name: @@ -2254,6 +2385,7 @@ class DeviceTree(object): log_method_return(self, str(found)) return found + @synchronized(devicetree_lock) def getDeviceByPath(self, path, preferLeaves=True, incomplete=False, hidden=False): log_method_call(self, path=path) if not path: @@ -2291,19 +2423,23 @@ class DeviceTree(object): log_method_return(self, str(found)) return found + @synchronized(devicetree_lock) def getDevicesByType(self, device_type): # TODO: expand this to catch device format types return [d for d in self._devices if d.type == device_type] + @synchronized(devicetree_lock) def getDevicesByInstance(self, device_class): return [d for d in self._devices if isinstance(d, device_class)] + @synchronized(devicetree_lock) def getDeviceByID(self, id_num): for device in self._devices: if device.id == id_num: return device @property + @synchronized(devicetree_lock) def devices(self): """ List of device instances """ devices = [] @@ -2320,6 +2456,7 @@ class DeviceTree(object): return devices @property + @synchronized(devicetree_lock) def filesystems(self): """ List of filesystems. """ #""" Dict with mountpoint keys and filesystem values. """ @@ -2331,6 +2468,7 @@ class DeviceTree(object): return filesystems @property + @synchronized(devicetree_lock) def uuids(self): """ Dict with uuid keys and Device values. """ uuids = {} @@ -2354,6 +2492,7 @@ class DeviceTree(object): return uuids @property + @synchronized(devicetree_lock) def labels(self): """ Dict with label keys and Device values. @@ -2369,15 +2508,18 @@ class DeviceTree(object): return labels @property + @synchronized(devicetree_lock) def leaves(self): """ List of all devices upon which no other devices exist. """ leaves = [d for d in self._devices if d.isleaf] return leaves + @synchronized(devicetree_lock) def getChildren(self, device): """ Return a list of a device's children. """ return [c for c in self._devices if device in c.parents] + @synchronized(devicetree_lock) def resolveDevice(self, devspec, blkidTab=None, cryptTab=None, options=None): # find device in the tree device = None diff --git a/blivet/partitioning.py b/blivet/partitioning.py index abaf1b3..03888ab 100644 --- a/blivet/partitioning.py +++ b/blivet/partitioning.py @@ -34,6 +34,9 @@ from devices import PartitionDevice, LUKSDevice, devicePathToName from formats import getFormat from devicelibs.lvm import get_pool_padding +from threads import blivet_lock +from threads import synchronized + import gettext _ = lambda x: gettext.ldgettext("blivet", x) @@ -284,6 +287,7 @@ def _scheduleVolumes(storage, devs): # schedule the device for creation storage.createDevice(dev) +@synchronized(blivet_lock) def doAutoPartition(storage, data): log.debug("doAutoPart: %s" % storage.doAutoPart) log.debug("encryptedAutoPart: %s" % storage.encryptedAutoPart) @@ -731,6 +735,7 @@ def updateExtendedPartitions(storage, disks): # moment to simplify things storage.devicetree._addDevice(device) +@synchronized(blivet_lock) def doPartitioning(storage): """ Allocate and grow partitions. @@ -819,6 +824,7 @@ def doPartitioning(storage): % {"format": part.format.name, "minSize": part.format.minSize, "maxSize": part.format.maxSize}) +@synchronized(blivet_lock) def allocatePartitions(storage, disks, partitions, freespace): """ Allocate partitions based on requested features. @@ -1770,6 +1776,7 @@ def manageSizeSets(size_sets, chunks): if reclaimed[chunk] and not chunk.done: chunk.growRequests() +@synchronized(blivet_lock) def growPartitions(disks, partitions, free, size_sets=None): """ Grow all growable partition requests. @@ -1976,6 +1983,7 @@ def lvCompare(lv1, lv2): return ret +@synchronized(blivet_lock) def growLVM(storage): """ Grow LVs according to the sizes of the PVs. diff --git a/blivet/threads.py b/blivet/threads.py new file mode 100644 index 0000000..30b7c50 --- /dev/null +++ b/blivet/threads.py @@ -0,0 +1,85 @@ +from functools import wraps +from threading import RLock +from threading import _RLock +from threading import currentThread + +import logging +log = logging.getLogger("blivet") + +class NamedRLock(_RLock): + def __init__(self, *args, **kwargs): + self.name = kwargs.pop("name", "") + super(NamedRLock, self).__init__(*args, **kwargs) + + def __repr__(self): + s = super(NamedRLock, self).__repr__() + return "%s name=%s>" % (s[:-1], self.name) + + def __enter__(self, *args, **kwargs): + super(NamedRLock, self).acquire(*args, **kwargs) + log.debug("acquired %s", self) + + def __exit__(self, *args, **kwargs): + super(NamedRLock, self).release() + log.debug("released %s", self) + + +# for Blivet instance attributes +blivet_lock = NamedRLock(name="blivet") + +# for device list, action list, hidden list, possibly device filters +devicetree_lock = NamedRLock(name="devicetree") + +def synchronized(lock): + """ Decorator function to acquire a lock before calling a callable. """ + def synchronize(_callable): + @wraps(_callable) + def synchronized_callable(*args, **kwargs): + log.debug("thread %s acquiring lock %s before calling %s)" + % (currentThread(), lock, _callable)) + with lock: + return _callable(*args, **kwargs) + + return synchronized_callable + + return synchronize + +def exclusive(m): + """ Run a bound method after aqcuiring the instance's lock. """ + @wraps(m) + def run_m(*args, **kwargs): + log.debug("thread %s acquiring lock %s before calling %s", + currentThread().name, args[0].lock, m.func_code.co_name) + with args[0].lock: + return m(*args, **kwargs) + + return run_m + +# It would be neat if this could take a variable list of argument names, but +# that would require the use of contextlib.nested (deprecated) or ExitStack +# (python-3.1+). +def arg_lock(arg_name): + """ Aqcuire a lock on a named arg before running a callable. """ + def with_lock(f): + @wraps(f) + def run_with_lock(*args, **kwargs): + arg = None + if arg_name in kwargs: + arg = kwargs[arg_name] + if arg is None: + if arg_name in f.func_code.co_varnames: + arg_idx = f.func_code.co_varnames.index(arg_name) + if len(args) > arg_idx: + arg = args[arg_idx] + + log.debug("thread %s acquiring lock %s before calling %s", + currentThread().name, getattr(arg, "lock", None), + f.func_code.co_name) + with getattr(arg, "lock", RLock()): + return f(*args, **kwargs) + + return run_with_lock + + return with_lock + +device_mutex = arg_lock -- 1.8.1.4