From d321d712ccee6fbdca893ef6468432da69a554d0 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Wed, 4 Feb 2015 17:21:21 -0600
Subject: [PATCH 26/26] Use udev to determine when device actions are
 manifested.

TODO:
- add handling for pseudo-devices
- add similar support to formats
---
 blivet/devices/md.py        |  6 +++--
 blivet/devices/partition.py |  4 ++--
 blivet/devices/storage.py   | 50 ++++++++++++++++++++++++++++++++------
 blivet/devicetree.py        | 58 ++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 106 insertions(+), 12 deletions(-)

diff --git a/blivet/devices/md.py b/blivet/devices/md.py
index d7f5167..b15c4f8 100644
--- a/blivet/devices/md.py
+++ b/blivet/devices/md.py
@@ -466,8 +466,10 @@ class MDRaidArrayDevice(ContainerDevice, RaidDevice):
 
     def _postCreate(self):
         # this is critical since our status method requires a valid sysfs path
-        self.exists = True  # this is needed to run updateSysfsPath
-        self.updateSysfsPath()
+        if not flags.uevents:
+            self.exists = True  # this is needed to run updateSysfsPath
+            self.updateSysfsPath()
+
         StorageDevice._postCreate(self)
 
         # update our uuid attribute with the new array's UUID
diff --git a/blivet/devices/partition.py b/blivet/devices/partition.py
index dc8d98c..089e9c8 100644
--- a/blivet/devices/partition.py
+++ b/blivet/devices/partition.py
@@ -566,6 +566,8 @@ class PartitionDevice(StorageDevice):
             raise
 
     def _postCreate(self):
+        StorageDevice._postCreate(self)
+
         if self.isExtended:
             partition = self.disk.format.extendedPartition
         else:
@@ -580,8 +582,6 @@ class PartitionDevice(StorageDevice):
             # explictly destroyed by a destroyformat action gets wiped
             DeviceFormat(device=self.path, exists=True).destroy()
 
-        StorageDevice._postCreate(self)
-
     def _computeResize(self, partition, newsize=None):
         """ Return a new constraint and end-aligned geometry for new size.
 
diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py
index 2a49196..ed3109f 100644
--- a/blivet/devices/storage.py
+++ b/blivet/devices/storage.py
@@ -23,6 +23,8 @@
 import os
 import copy
 import pyudev
+import time
+from threading import Condition
 
 from .. import errors
 from .. import util
@@ -31,6 +33,7 @@ from ..storage_log import log_method_call
 from .. import udev
 from ..formats import getFormat
 from ..size import Size
+from ..threads import blivet_lock
 
 import logging
 log = logging.getLogger("blivet")
@@ -128,6 +131,18 @@ class StorageDevice(Device):
 
         self.deviceLinks = []
 
+        """ Indicates we have initiated creation of this device. """
+        self.created = False
+        self.destroyed = False
+
+        """ Indicates we have initiated activation of this device. """
+        self.started = False
+        self.stopped = False
+
+        """ Condition used to synchronize access to :attr:`exists` """
+        #self.cv = Condition(blivet_lock, verbose=True)
+        self.cv = Condition(blivet_lock)
+
     def __str__(self):
         exist = "existing"
         if not self.exists:
@@ -368,6 +383,8 @@ class StorageDevice(Device):
             return False
 
         self.setupParents(orig=orig)
+        if flags.uevents:
+            self.started = True
         return True
 
     def _setup(self, orig=False):
@@ -386,7 +403,11 @@ class StorageDevice(Device):
 
     def _postSetup(self):
         """ Perform post-setup operations. """
-        udev.settle()
+        if flags.uevents:
+            self.cv.wait()
+        else:
+            udev.settle()
+
         # we always probe since the device may not be set up when we want
         # information about it
         self._size = self.currentSize
@@ -409,7 +430,7 @@ class StorageDevice(Device):
             self.originalFormat.teardown()
         if self.format.exists:
             self.format.teardown()
-        udev.settle()
+        udev.settle() # this can go once we are able to properly detect umount
         return True
 
     def _teardown(self, recursive=None):
@@ -441,6 +462,9 @@ class StorageDevice(Device):
 
         self.setupParents()
 
+        if flags.uevents:
+            self.created = True
+
     def _create(self):
         """ Perform device-specific create operations. """
         pass
@@ -454,10 +478,17 @@ class StorageDevice(Device):
 
     def _postCreate(self):
         """ Perform post-create operations. """
-        self.exists = True
-        self.setup()
-        self.updateSysfsPath()
-        udev.settle()
+        if flags.uevents:
+            # XXX what about devices that don't get started automatically when
+            #     they are created
+            self.cv.wait()
+        else:
+            self.exists = True
+
+        self.setup() # this seems redundant
+        if not flags.uevents:
+            self.updateSysfsPath()
+            udev.settle()
 
         # make sure that targetSize is updated to reflect the actual size
         if self.resizable:
@@ -477,6 +508,8 @@ class StorageDevice(Device):
             raise errors.DeviceError("Cannot destroy non-leaf device", self.name)
 
         self.teardown()
+        if flags.uevents:
+            self.destroyed = True
 
     def _destroy(self):
         """ Perform device-specific destruction operations. """
@@ -491,7 +524,10 @@ class StorageDevice(Device):
 
     def _postDestroy(self):
         """ Perform post-destruction operations. """
-        self.exists = False
+        if flags.uevents:
+            self.cv.wait()
+        else:
+            self.exists = False
 
     def setupParents(self, orig=False):
         """ Run setup method of all parent devices. """
diff --git a/blivet/devicetree.py b/blivet/devicetree.py
index 9650621..74fee68 100644
--- a/blivet/devicetree.py
+++ b/blivet/devicetree.py
@@ -380,7 +380,6 @@ class DeviceTree(object):
 
                     action.execute(callbacks)
 
-                udev.settle()
                 for device in self._devices:
                     # make sure we catch any renumbering parted does
                     if device.exists and isinstance(device, PartitionDevice):
@@ -420,6 +419,54 @@ class DeviceTree(object):
         else:
             log.info("unknown event: %s", event)
 
+    def _notifyDevice(self, action, info):
+        """ Notify device condition variables as appropriate.
+
+            :param str action: an action string, eg: 'add', 'remove'
+            :param :class:`pyudev.Device` info: udev data for the device
+            :returns: True if any condition variable was notified
+            :rtype: bool
+
+            Methods :meth:`~.devices.StorageDevice.create`,
+            :meth:`~.devices.StorageDevice.destroy`, and
+            :meth:`~.devices.StorageDevice.setup` all use a condition variable
+            to synchronize the finalization/confirmation of their respective
+            operations. Since each device has only one condition variable, flags
+            are used to indicate which, if any, of these methods is waiting.
+        """
+        ret = False
+        if action in ("add", "change"):
+            # We can't do this lookup by sysfs path since the StorageDevice
+            # might have just been created, in which case it might not have a
+            # meaningful sysfs path (for dm and md they aren't predictable).
+            name = udev.device_get_name(info)
+            device = self.getDeviceByName(name)
+            if device:
+                if not device.exists and device.created:
+                    # The StorageDevice will be waiting for this to finalize
+                    # creation of the device.
+                    device.exists = True
+                    device.sysfsPath = udev.device_get_sysfs_path(info)
+                    device.cv.notify()
+                    ret = True
+                    # update size, uuid, &c
+                elif device.exists and device.started:
+                    # unset this so a) the StorageDevice knows it's ready and
+                    #               b) it's ready for future activations
+                    device.started = False
+                    device.cv.notify()
+                    ret = True
+        elif action == "remove" and self._actions:
+            current_action = self._actions[0]
+            if current_action.isDestroy and current_action.isDevice and \
+               current_action.device.exists and current_action.device.destroyed:
+                current_action.device.exists = False
+                current_action.device.sysfsPath = ""
+                current_action.device.cv.notify()
+                ret = True
+
+        return ret
+
     def deviceAddedCB(self, info, force=False):
         """ Handle an "add" uevent on a block device.
 
@@ -444,6 +491,9 @@ class DeviceTree(object):
             log.debug("new device not initialized -- not processing it")
             return
 
+        if self._notifyDevice("add", new_info):
+            return
+
         self.addUdevDevice(new_info)
         device = self.getDeviceBySysfsPath(udev.device_get_sysfs_path(new_info),
                                            hidden=True)
@@ -462,6 +512,9 @@ class DeviceTree(object):
             log.warning("failed to look up changed info")
             return
 
+        # ignore return value since we'll want to update size, &c either way
+        self._notifyDevice("change", new_info)
+
         # Would a lookup by UUID be more robust here?
         device = self.getDeviceBySysfsPath(sysfs_path, hidden=True)
         if (not device and
@@ -664,6 +717,9 @@ class DeviceTree(object):
         if info.subsystem != "block":
             return
 
+        if self._notifyDevice("remove", info):
+            return
+
         removed = None
         for device in (d for d in self._devices if d.exists):
             if device.sysfsPath == udev.device_get_sysfs_path(info):
-- 
1.9.3

