Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Avoid some tracebacks during populate #785

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions blivet/populator/populator.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from ..devices import MDRaidArrayDevice
from ..devices import MultipathDevice
from ..devices import NoDevice
from ..devices import StorageDevice
from ..devicelibs import disk as disklib
from ..devicelibs import lvm
from .. import formats
Expand Down Expand Up @@ -262,7 +263,20 @@ def handle_device(self, info, update_orig_fmt=False):
helper_class = self._get_device_helper(info)

if helper_class is not None:
device = helper_class(self, info).run()
try:
device = helper_class(self, info).run()
except DeviceTreeError as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What could be really important here is that every run() method should make sure to gather as much information as possible before throwing the exception. Otherwise the information about the problematic device may be very incomplete.

log.error("error handling device %s: %s", name, str(e))
device = self.get_device_by_name(name, incomplete=True, hidden=True)
if device is None:
try:
parents = self._add_slave_devices(info)
except DeviceTreeError:
parents = []

device = StorageDevice(name, parents=parents, uuid=udev.device_get_uuid(info),
exists=True)
self._add_device(device)

if not device:
log.debug("no device obtained for %s", name)
Expand Down Expand Up @@ -302,7 +316,10 @@ def handle_format(self, info, device):

helper_class = self._get_format_helper(info, device=device)
if helper_class is not None:
helper_class(self, info, device).run()
try:
helper_class(self, info, device).run()
except DeviceTreeError as e:
log.error("error handling formatting on %s: %s", device.name, str(e))

log.info("got format: %s", device.format)

Expand Down
64 changes: 64 additions & 0 deletions tests/populator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from blivet.devices import MDRaidArrayDevice, MultipathDevice, OpticalDevice
from blivet.devices import PartitionDevice, StorageDevice, NVDIMMNamespaceDevice
from blivet.devicetree import DeviceTree
from blivet.errors import DeviceTreeError
from blivet.formats import get_device_format_class, get_format, DeviceFormat
from blivet.formats.disklabel import DiskLabel
from blivet.populator.helpers import DiskDevicePopulator, DMDevicePopulator, LoopDevicePopulator
Expand All @@ -25,6 +26,69 @@
from blivet.size import Size


class PopulatorTestCase(unittest.TestCase):
@patch.object(DeviceTree, "_get_format_helper")
def test_handle_format_error_handling(self, *args):
""" Test handling of errors raised from populator (format) helpers' run methods.

The piece we want to test is that DeviceTreeError being raised from the
helper's run method results in no crash and an unset format on the device.
There is no need to test the various helpers individually since the only
machinery is in Populator.handle_format.
"""
get_format_helper_patch = args[0]

devicetree = DeviceTree()

# Set up info to look like a specific format type
name = 'fhtest1'
fmt_type = 'xfs'
info = dict(SYS_NAME=name, ID_FS_TYPE=fmt_type)
device = StorageDevice(name, size=Size('50g'), exists=True)

# Set up helper to raise an exn in run()
helper = Mock()
helper.side_effect = DeviceTreeError("failed to populate format")
get_format_helper_patch.return_value = helper

devicetree.handle_format(info, device)

self.assertEqual(device.format.type, None)

@patch.object(DeviceTree, "_reason_to_skip_device", return_value=None)
@patch.object(DeviceTree, "_clear_new_multipath_member")
@patch.object(DeviceTree, "handle_format")
@patch.object(DeviceTree, "_add_slave_devices")
@patch.object(DeviceTree, "_get_device_helper")
def test_handle_device_error_handling(self, *args):
""" Test handling of errors raised from populator (device) helpers' run methods.

When the helper's run method raises DeviceTreeError we should end up with a
StorageDevice (and no traceback). There is no need to test the various
helper classes since all of the machinery is in Populator.handle_device.
"""
get_device_helper_patch = args[0]
add_slave_devices_patch = args[1]

devicetree = DeviceTree()

# Set up info to look like a specific device type
name = 'dhtest1'
info = dict(SYS_NAME=name, SYS_PATH='/fake/sys/path/dhtest1')

# Set up helper to raise an exn in run()
helper = Mock()
helper.side_effect = DeviceTreeError("failed to populate device")
get_device_helper_patch.return_value = helper

add_slave_devices_patch.return_value = list()
devicetree.handle_device(info)

device = devicetree.get_device_by_name(name)
self.assertIsNotNone(device)
self.assertIsInstance(device, StorageDevice)


class PopulatorHelperTestCase(unittest.TestCase):
helper_class = None

Expand Down