From: David Lehman <dlehman(a)redhat.com>
This is far more efficient given the frequency of device
lookups by object path as necessitated by the dbus interface.
---
blivet/dbus/blivet.py | 21 ++++++++-------------
tests/dbus_test.py | 3 ++-
2 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/blivet/dbus/blivet.py b/blivet/dbus/blivet.py
index e893681..d69f7b8 100644
--- a/blivet/dbus/blivet.py
+++ b/blivet/dbus/blivet.py
@@ -17,6 +17,7 @@
#
# Red Hat Author(s): David Lehman <dlehman(a)redhat.com>
#
+from collections import OrderedDict
import sys
import dbus
@@ -36,7 +37,7 @@ class DBusBlivet(DBusObject):
"""
def __init__(self, manager):
super().__init__()
- self._dbus_devices = list()
+ self._dbus_devices = OrderedDict()
self._manager = manager # provides ObjectManager interface
self._blivet = Blivet()
self._set_up_callbacks()
@@ -61,21 +62,16 @@ def properties(self):
def _device_removed(self, device):
""" Update ObjectManager interface after a device is removed. """
removed_object_path = DBusDevice.get_object_path_by_id(device.id)
- removed = next((d for d in self._dbus_devices if d.object_path == removed_object_path))
+ removed = self._dbus_devices[removed_object_path]
self._manager.remove_object(removed)
- self._dbus_devices.remove(removed)
+ del self._dbus_devices[removed_object_path]
def _device_added(self, device):
""" Update ObjectManager interface after a device is added. """
added = DBusDevice(device)
- self._dbus_devices.append(added)
+ self._dbus_devices[added.object_path] = added
self._manager.add_object(added)
- def _get_device_by_object_path(self, object_path):
- """ Return the :class:`blivet.dbus.device.DBusDevice` at the given path. """
- # FIXME: This is inefficient. Implement an object_path->dbus_device dictionary.
- return next(d._device for d in self._dbus_devices if d.object_path == object_path)
-
@dbus.service.method(dbus_interface=BLIVET_INTERFACE)
def Reset(self):
""" Reset the Blivet instance and populate the device tree. """
@@ -93,7 +89,7 @@ def Exit(self):
@dbus.service.method(dbus_interface=BLIVET_INTERFACE, out_signature='ao')
def ListDevices(self):
""" Return a list of strings describing the devices in this system. """
- return dbus.Array([d.object_path for d in self._dbus_devices], signature='o')
+ return dbus.Array(list(self._dbus_devices.keys()), signature='o')
@dbus.service.method(dbus_interface=BLIVET_INTERFACE, in_signature='s', out_signature='o')
def ResolveDevice(self, spec):
@@ -105,12 +101,11 @@ def ResolveDevice(self, spec):
'No device was found that matches the device '
'descriptor "%s".' % spec)
- dbus_device = next(d for d in self._dbus_devices if d._device == device)
- object_path = dbus_device.object_path
+ object_path = next(p for (p, d) in self._dbus_devices.items() if d._device == device)
return object_path
@dbus.service.method(dbus_interface=BLIVET_INTERFACE, in_signature='o')
def RemoveDevice(self, object_path):
""" Remove a device and all devices built on it. """
- device = self._get_device_by_object_path(object_path)
+ device = self._dbus_devices[object_path]
self._blivet.devicetree.recursive_remove(device)
diff --git a/tests/dbus_test.py b/tests/dbus_test.py
index cca70e3..913e720 100644
--- a/tests/dbus_test.py
+++ b/tests/dbus_test.py
@@ -1,3 +1,4 @@
+from collections import OrderedDict
import random
from unittest import TestCase
from unittest.mock import Mock, patch, sentinel
@@ -25,7 +26,7 @@ def test_ListDevices(self):
dbus object path of each device in the DBusBlivet.
"""
object_paths = dbus.Array([sentinel.dev1, sentinel.dev2, sentinel.dev3], signature='o')
- dbus_devices = [Mock(object_path=p) for p in object_paths]
+ dbus_devices = OrderedDict((p, Mock(object_path=p)) for p in object_paths)
self.dbus_object._dbus_devices = dbus_devices
self.assertEqual(self.dbus_object.ListDevices(), object_paths)
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/1a8dfe21dfa1086dade81bed98f933…
From: David Lehman <dlehman(a)redhat.com>
There is no reason to disallow this that I can think of as
we can tear things down right before we commit the changes.
---
blivet/devices/storage.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py
index d20a52e..0ba515c 100644
--- a/blivet/devices/storage.py
+++ b/blivet/devices/storage.py
@@ -701,9 +701,6 @@ def _set_format(self, fmt):
log_method_call(self, self.name, type=fmt.type,
current=getattr(self._format, "type", None))
- if self._format and self._format.status:
- # FIXME: self.format.status doesn't mean much
- raise errors.DeviceError("cannot replace active format", self.name)
# check device size against format limits
if not fmt.exists:
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/802fe844643d64726dab3d1bc46377…
From: David Lehman <dlehman(a)redhat.com>
---
blivet/actionlist.py | 4 ++
blivet/callbacks.py | 162 ++++++++++++++++++++++++++++++++++++++++++++++
blivet/devices/storage.py | 3 +
blivet/devicetree.py | 3 +
4 files changed, 172 insertions(+)
diff --git a/blivet/actionlist.py b/blivet/actionlist.py
index 8b29690..83f172d 100644
--- a/blivet/actionlist.py
+++ b/blivet/actionlist.py
@@ -23,6 +23,7 @@
import copy
from functools import wraps
+from .callbacks import callbacks as _callbacks
from .deviceaction import ActionCreateDevice
from .deviceaction import action_type_from_string, action_object_from_string
from .devicelibs import lvm
@@ -74,6 +75,7 @@ def add(self, action):
# apply the action before adding it in case apply raises an exception
action.apply()
self._actions.append(action)
+ _callbacks.action_added(action=action)
log.info("registered action: %s", action)
def remove(self, action):
@@ -83,6 +85,7 @@ def remove(self, action):
action.cancel()
self._actions.remove(action)
record_change(ActionCanceled(action=action))
+ _callbacks.action_removed(action=action)
log.info("canceled action %s", action)
def find(self, device=None, action_type=None, object_type=None,
@@ -338,5 +341,6 @@ def process(self, callbacks=None, devices=None, dry_run=None):
device.format.device = device.path
self._completed_actions.append(self._actions.pop(0))
+ _callbacks.action_executed(action=action)
self._post_process(devices=devices)
diff --git a/blivet/callbacks.py b/blivet/callbacks.py
index 8947f13..56edd88 100644
--- a/blivet/callbacks.py
+++ b/blivet/callbacks.py
@@ -79,3 +79,165 @@ def create_new_callbacks_register(create_format_pre=None,
["msg", "min_entropy"])
ReportProgressData = namedtuple("ReportProgressData",
["msg"])
+
+
+#
+# Callbacks for changes to the model.
+#
+class CallbackList:
+ def __init__(self):
+ self._cb_list = list()
+
+ def add(self, cb):
+ """ Add a callback. """
+ self._cb_list.append(cb)
+
+ def remove(self, cb):
+ """ Remove a callback. """
+ self._cb_list.remove(cb)
+
+ def __call__(self, *args, **kwargs):
+ """ Run all of the callbacks. """
+ for cb in self._cb_list:
+ cb(*args, **kwargs)
+
+
+class Callbacks:
+ """A collection of callbacks for various events
+
+ Each trigger/event gets a list of callbacks to run, represented by an
+ instance of :class:`~.callbacks.CallbackList`.
+ """
+ def __init__(self):
+ self.device_added = CallbackList()
+ """callback list for when a device is added to the devicetree"""
+
+ self.device_removed = CallbackList()
+ """callback list for when a device is removed from the devicetree"""
+
+ self.format_added = CallbackList()
+ """callback list for when a format is added to a device"""
+
+ self.format_removed = CallbackList()
+ """callback list for when a format is removed from a device"""
+
+ self.action_added = CallbackList()
+ """ callback list for when an action is added/registered/scheduled"""
+
+ self.action_removed = CallbackList()
+ """ callback list for when an action is removed/canceled"""
+
+ self.action_executed = CallbackList()
+ """ callback list for when an action is executed"""
+
+ self.parent_added = CallbackList()
+ """ callback list for when a member device is added to a container device"""
+
+ self.parent_removed = CallbackList()
+ """ callback list for when a member device is removed from a container device"""
+
+ self.attribute_changed = CallbackList()
+ """ callback list for when a device or format attribute's value is changed"""
+
+"""
+ .. data:: callbacks
+
+ .. note::
+ The arguments for these callbacks are provided by name, so any callbacks
+ you provide should be able to handle that.
+
+ .. function:: device_added_cb(device)
+
+ A device was added to the devicetree.
+
+ :param device: the device that was added
+ :type device: :class:`~.devices.StorageDevice`
+
+
+ .. function:: device_removed_cb(device)
+
+ A device was removed from the devicetree.
+
+ :param device: the device instance that was removed
+ :type device: :class:`~.devices.StorageDevice`
+
+
+ .. function:: format_added_cb(device, fmt)
+
+ A new format was added to the device.
+
+ :param device: the device
+ :type device: :class:`~.devices.StorageDevice`
+ :param fmt: the added format
+ :type fmt: :class:`~.formats.DeviceFormat`
+
+
+ .. function:: format_removed_cb(device, fmt)
+
+ A format was removed from the device.
+
+ :param device: the device
+ :type device: :class:`~.devices.StorageDevice`
+ :param fmt: the removed format
+ :type fmt: :class:`~.formats.DeviceFormat`
+
+
+ .. function:: action_added_cb(action)
+
+ An action was scheduled/registered/added.
+
+ :param action: the action
+ :type action: :class:`~.deviceaction.DeviceAction`
+
+
+ .. function:: action_removed_cb(action)
+
+ An action was canceled/removed.
+
+ :param action: the action
+ :type action: :class:`~.deviceaction.DeviceAction`
+
+
+ .. function:: action_executed_cb(action)
+
+ An action was executed/completed.
+
+ :param action: the action
+ :type action: :class:`~.deviceaction.DeviceAction`
+
+
+ .. function:: parent_added_cb(device, parent)
+
+ A member device was added to a container device.
+
+ :param device: the member device
+ :type device: :class:`~.devices.StorageDevice`
+ :param device: the container device
+ :type device: :class:`~.devices.StorageDevice`
+
+
+ .. function:: parent_removed_cb(device, parent)
+
+ A member device was removed from a container device.
+
+ :param device: the device instance that was removed
+ :type device: :class:`~.devices.StorageDevice`
+ :param device: the container device
+ :type device: :class:`~.devices.StorageDevice`
+
+
+ .. function:: attribute_changed_cb(device, attr, old, new, fmt=None)
+
+ An attribute value was changed.
+
+ :param device: the device
+ :type device: :class:`~.devices.StorageDevice`
+ :param str attr: the attribute name
+ :param old: the old value
+ :param new: the new value
+ :keyword fmt: the format, if a format attribute is what changed
+ :type fmt: :class:`~.formats.DeviceFormat`
+
+
+"""
+callbacks = Callbacks()
diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py
index 0ba515c..a618171 100644
--- a/blivet/devices/storage.py
+++ b/blivet/devices/storage.py
@@ -24,6 +24,7 @@
import copy
import pyudev
+from ..callbacks import callbacks
from .. import errors
from .. import util
from ..flags import flags
@@ -709,9 +710,11 @@ def _set_format(self, fmt):
elif fmt.min_size and fmt.min_size > self.size:
raise errors.DeviceError("device is too small for new format")
+ callbacks.format_removed(device=self, fmt=self._format)
self._format = fmt
self._format.device = self.path
self._update_netdev_mount_option()
+ callbacks.format_added(device=self, fmt=self._format)
def _update_netdev_mount_option(self):
""" Fix mount options to include or exclude _netdev as appropriate. """
diff --git a/blivet/devicetree.py b/blivet/devicetree.py
index 832dce5..d30bf5d 100644
--- a/blivet/devicetree.py
+++ b/blivet/devicetree.py
@@ -30,6 +30,7 @@
from gi.repository import BlockDev as blockdev
from .actionlist import ActionList
+from .callbacks import callbacks
from .errors import DeviceError, DeviceTreeError, StorageError
from .deviceaction import ActionDestroyDevice, ActionDestroyFormat
from .devices import BTRFSDevice, NoDevice, PartitionDevice
@@ -162,6 +163,7 @@ def _add_device(self, newdev, new=True):
newdev.name not in self.names):
self.names.append(newdev.name)
record_change(DeviceAdded(device=newdev))
+ callbacks.device_added(device=newdev)
log.info("added %s %s (id %d) to device tree", newdev.type,
newdev.name,
newdev.id)
@@ -205,6 +207,7 @@ def _remove_device(self, dev, force=None, modparent=True):
self._devices.remove(dev)
record_change(DeviceRemoved(device=dev))
+ callbacks.device_removed(device=dev)
log.info("removed %s %s (id %d) from device tree", dev.type,
dev.name,
dev.id)
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/0ee712ef229b3f045d1e0aa29db5ab…
This is implemented via `DeviceTree.recursive_remove`, so I'm open to different names, including `RecursiveRemove`. Since #392 isn't merged yet its commits are appearing here, too. This one starts with 7b2ebfc and currently has six commits.
--
To view this pull request on github, visit https://github.com/rhinstaller/blivet/pull/394
From: David Lehman <dlehman(a)redhat.com>
---
examples/dbus_client.py | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 examples/dbus_client.py
diff --git a/examples/dbus_client.py b/examples/dbus_client.py
new file mode 100644
index 0000000..2fdcc25
--- /dev/null
+++ b/examples/dbus_client.py
@@ -0,0 +1,18 @@
+
+import dbus
+
+bus = dbus.SystemBus()
+
+# This adds a signal match so that the client gets signals sent by Blivet1's
+# ObjectManager. These signals are used to notify clients of changes to the
+# managed objects (for blivet, this will be devices, formats, and actions).
+bus.add_match_string("type='signal',sender='com.redhat.Blivet1',path_namespace='/com/redhat/Blivet1'")
+
+blivet = bus.get_object('com.redhat.Blivet1', '/com/redhat/Blivet1/Blivet')
+blivet.Reset()
+
+object_manager = bus.get_object('com.redhat.Blivet1', '/com/redhat/Blivet1')
+objects = object_manager.GetManagedObjects()
+for object_path in blivet.ListDevices():
+ device = objects[object_path]['com.redhat.Blivet1.Device']
+ print(device['Name'], device['Type'], device['Size'], device['FormatType'])
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/f29dab9a82b44fac483d71c432a40a…