From 67e75c2cd8bd7f00d3a6461f4a0026b10993f565 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Tue, 21 Apr 2015 12:05:15 -0500
Subject: [PATCH 35/51] Handle events expected as a result of blivet-initiated
 operations.

---
 blivet/handler.py | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 240 insertions(+), 8 deletions(-)

diff --git a/blivet/handler.py b/blivet/handler.py
index e0fd6b4..8746b63 100644
--- a/blivet/handler.py
+++ b/blivet/handler.py
@@ -94,6 +94,16 @@ class EventHandler(object):
             log.debug("ignoring add event for %s", sysfs_path)
             return
 
+        # If _notifyDevice returns True, this uevent is related to processing
+        # of an action. It may or may not be in the tree.
+        if self._notifyDevice("add", info):
+            # This will update size, uuid, &c for new devices.
+            return self.deviceChangedCB(info, expected=True)
+
+        if self.devicetree.actions.processing:
+            log.debug("ignoring unexpected event during action processing")
+            return
+
         device = self.devicetree.getDeviceByName(udev.device_get_name(info))
         if device and device.exists:
             log.info("%s is already in the tree", udev.device_get_name(info))
@@ -251,7 +261,7 @@ class EventHandler(object):
         # MD TODO: raid level, spares
         # BTRFS TODO: check for changes to subvol list IFF the volume is mounted
 
-    def deviceChangedCB(self, info):
+    def deviceChangedCB(self, info, expected=False):
         """ Handle a "changed" uevent on a block device. """
         sysfs_path = udev.device_get_sysfs_path(info)
         log.debug("device changed: %s", sysfs_path)
@@ -279,13 +289,21 @@ class EventHandler(object):
                 # won't be able to determine its size.
                 device.sysfsPath = sysfs_path
 
-        if (not device and
+        if (not expected and not device and
             ((udev.device_is_md(info) and "MD_UUID" in info) or
              (udev.device_is_dm(info) and "DM_NAME" in info))):
             # md and dm devices aren't really added until you get a change
             # event
             return self.deviceAddedCB(info, force=True)
 
+        if not expected:
+            # See if this event was triggered a blivet action.
+            expected = self._notifyDevice("change", info)
+
+        if not expected and self.devicetree.actions.processing:
+            log.debug("ignoring unexpected event during action processing")
+            return
+
         if not device:
             # We're not concerned with updating devices that have been removed
             # from the tree.
@@ -293,8 +311,9 @@ class EventHandler(object):
             return
 
         if not device.exists:
-            log.error("ignoring change uevent on non-existent device %s",
-                      device.name)
+            if not expected:
+                log.error("ignoring change uevent on non-existent device %s",
+                          device.name)
             return
 
         ##
@@ -314,8 +333,9 @@ class EventHandler(object):
         # XXX resize of inactive lvs is handled in updateLVs (via change event
         #     handler for pv(s))
         current_size = device.readCurrentSize()
-        if device.currentSize != current_size:
-            self.devicetree.cancelDiskActions(device.disks)
+        if expected or device.currentSize != current_size:
+            if not expected:
+                self.devicetree.cancelDiskActions(device.disks)
             device.updateSize(newsize=current_size)
 
         if not device.format.exists:
@@ -323,8 +343,9 @@ class EventHandler(object):
             # A policy must be decided upon for external events that conflict
             # with scheduled actions.
             #
-            log.warning("ignoring change uevent on device with "
-                        "non-existent format")
+            if not expected:
+                log.warning("ignoring change uevent on device with "
+                            "non-existent format")
             return
 
         log.debug("changed: %s", pprint.pformat(dict(info)))
@@ -355,6 +376,30 @@ class EventHandler(object):
             if hasattr(device.format, "label"):
                 device.format.label = label
 
+            if expected:
+                device.format.uuid = uuid
+
+                if hasattr(device.format, "containerUUID"):
+                    if device.format.type == "lvmpv":
+                        self.devicetree.dropLVMCache()
+                    device.format.containerUUID = self._getContainerUUID(info,
+                                                                         device)
+                    try:
+                        container = self.devicetree.getChildren(device)[0]
+                    except IndexError:
+                        pass
+                    else:
+                        # notify the container in case it's waiting on the UUID
+                        # in _postCreate
+                        container.modifySync.notify()
+
+                # notify the event sync now that we're done updating UUID in
+                # case it is waiting on us to set it
+                device.format.eventSync.notify()
+
+        if expected:
+            return
+
         if reformatted:
             log.info("%s was reformatted from outside of blivet", device.name)
             self.devicetree.cancelDiskActions(device.disks)
@@ -386,6 +431,13 @@ class EventHandler(object):
         if info.subsystem != "block":
             return
 
+        if self._notifyDevice("remove", info):
+            return
+
+        if self.devicetree.actions.processing:
+            log.debug("ignoring unexpected event during action processing")
+            return
+
         # XXX Don't forget about disks actually going offline for some reason.
 
         device = self.devicetree.getDeviceByName(udev.device_get_name(info))
@@ -397,3 +449,183 @@ class EventHandler(object):
                 if hasattr(fmt, "_mountpoint"):
                     fmt._mountpoint = None
                     fmt._mounted_read_only = False
+
+    def _notifyDevice(self, action, info):
+        """ Notify 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 synchronization
+            manager (:class:`~.threads.StorageEventSynchronizer`) to
+            synchronize the finalization/confirmation of their respective
+            operations. Flags within the manager are used to indicate which, if
+            any, of these methods is under way.
+        """
+        ret = False
+        name = udev.device_get_name(info)
+        sysfs_path = udev.device_get_sysfs_path(info)
+        log_method_call(self, name=name, action=action, sysfs_path=sysfs_path)
+
+        actions = self.devicetree.findActions()
+
+        # 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).
+        device = self.devicetree.getDeviceByName(name)
+        if device is None and name.startswith("md"):
+            # XXX HACK md devices have no symbolic name by the time they are
+            #          removed, so we have to look it up by sysfs path
+            # XXX do the same for teardown during action processing?
+            for _device in self.devicetree.devices:
+                if _device.sysfsPath and \
+                   os.path.basename(_device.sysfsPath) == name:
+                    device = _device
+                    break
+
+        if action in ("add", "change") and device and \
+           device.modifySync.creating and not device.delegateModifyEvents and \
+           device.modifySync.validate(info):
+            log.debug("* create %s", device.name)
+            ## device create without event delegation (eg: partition)
+            event_sync = device.modifySync
+            # Only wait briefly since there is a possibility the device already
+            # notified the cv from _postCreate.
+            event_sync.wait(1) # wait until the device says it's ready
+            event_sync.notify() # notify the device that we received the event
+            event_sync.wait() # wait for the device to postprocess the event
+            ret = True
+        elif action in ("add", "change"):
+            # could be any number of operations on a device that may no longer
+            # be in the tree
+            # device setup could be add or change event
+            # all other operations will be change event
+            event_sync = None
+            name = getattr(device, "name", None)
+            if device and device.controlSync.starting and \
+               not device.delegateControlEvents:
+                ## device setup
+                log.debug("* setup %s", device.name)
+                # update sysfsPath since the device will not have access to it
+                # until later in the change handler
+                device.sysfsPath = sysfs_path
+                event_sync = device.controlSync
+            elif device and device.controlSync.stopping and \
+                 device.name.startswith("loop"):
+                ## loop device teardown
+                # XXX HACK You don't get a remove event when you deactivate a
+                #          loop device.
+                log.debug("* teardown %s", device.name)
+                event_sync = device.controlSync
+            elif action == "change" and device and device.modifySync.resizing:
+                ## device resize
+                log.debug("* resize %s", device.name)
+                event_sync = device.modifySync
+            elif action == "change" and device and \
+                 device.controlSync.changing and \
+                 device.controlSync.validate(info):
+                ## device change (eg: event on pv for vg or lv creation)
+                log.debug("* change %s", device.name)
+                event_sync = device.controlSync
+                # There's an extra notify/wait pair in device create/destroy
+                # that the other branches in this block will not do.
+                # Only wait briefly since it's possible the device has already
+                # notified the CV from _postCreate/_postDestroy.
+                event_sync.wait(1)
+            elif action == "change" and device and \
+                 device.format.eventSync.active and \
+                 device.format.eventSync.validate(info):
+                ## any change to a format
+                log.debug("* change %s format", device.name)
+                event_sync = device.format.eventSync
+            elif actions:
+                # see if this action is on a device or format that is scheduled
+                # for removal
+                devices = (a.device for a in actions if a.isDevice and
+                                                        a.isDestroy)
+                for device in devices:
+                    # hopefully all device names are intact after removal
+                    if device.name == name:
+                        break
+                else:
+                    device = None
+
+                if action == "change" and not device:
+                    # see if this is a format op on a format scheduled for
+                    # destruction
+                    _actions = (a for a in actions if a.isDestroy and
+                                                      a.isFormat)
+                    for action in _actions:
+                        if action.device.name == name and \
+                           action.format.eventSync.active and \
+                           action.format.eventSync.validate(info):
+                            ## any change to a format scheduled for destruction
+                            ## on a device not scheduled for destruction
+                            log.debug("*** change %s format", action.device.name)
+                            event_sync = action.format.eventSync
+                            name = action.device.name
+                            break
+
+                # If we haven't come up with an event_sync yet, try the same
+                # checks on the device being operated on by the current action,
+                # if any.
+                # XXX None of the sync manager's flags should be set if an
+                #     action is not being executed.
+                if device and not event_sync and actions:
+                    current_action = actions[0]
+                    if current_action.device.controlSync.starting and \
+                       not current_action.device.delegateControlEvents:
+                        ## setup of device scheduled for destruction
+                        log.debug("** setup %s", current_action.device.name)
+                        event_sync = current_action.device.controlSync
+                    elif action == "change" and \
+                         current_action.device.modifySync.destroying and \
+                         current_action.device.modifySync.validate(info):
+                        # change to pv when removing lv or vg
+                        log.debug("** destroy %s", current_action.device.name)
+                        event_sync = current_action.device.modifySync
+                    elif action == "change" and \
+                         current_action.format.eventSync.active and \
+                         current_action.format.eventSync.validate(info):
+                        ## any change to a format on a device scheduled for
+                        ## destruction
+                        log.debug("** change %s format", current_action.device.name)
+                        event_sync = current_action.format.eventSync
+
+            if event_sync:
+                event_sync.notify()
+                event_sync.wait()
+                ret = True
+        elif action == "remove" and device and device.controlSync.stopping and \
+             not device.delegateControlEvents:
+            ## device teardown
+            log.debug("* teardown %s", device.name)
+            device.controlSync.notify()
+            device.controlSync.wait()
+            ret = True
+        elif action == "remove":
+            ## device destroy
+            current_action = None
+            if actions:
+                current_action = actions[0]
+
+            if current_action and \
+               current_action.isDestroy and current_action.isDevice and \
+               current_action.device.exists and \
+               current_action.device.modifySync.destroying and \
+               not current_action.device.delegateModifyEvents:
+                device = current_action.device
+                log.debug("* destroy %s", device.name)
+                event_sync = device.modifySync
+                # Only wait briefly since it's possible the device already
+                # notified the cv from _postDestroy.
+                event_sync.wait(1) # wait until the device says it's ready
+                event_sync.notify() # notify device that we received the event
+                event_sync.wait() # wait for the device to postprocess the event
+                ret = True
+
+        return ret
-- 
2.1.0

