Skip to content

Commit 2ba7fbe

Browse files
committed
Do not allow not found exceptions to propagate in list context
zbus crate omits property values from the GetManagedObjects result if it can not get them. Instead of a stack trace, return a TABLE_UNKNOWN_STRING for printing. Signed-off-by: mulhern <amulhern@redhat.com>
1 parent 46b38d4 commit 2ba7fbe

5 files changed

Lines changed: 168 additions & 45 deletions

File tree

src/stratis_cli/_actions/_formatting.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717

1818
# isort: STDLIB
1919
import sys
20+
from functools import wraps
2021
from typing import Any, Callable, List, Optional
2122
from uuid import UUID
2223

2324
# isort: THIRDPARTY
2425
from dbus import Struct
2526
from wcwidth import wcswidth
2627

28+
# isort: FIRSTPARTY
29+
from dbus_client_gen import DbusClientMissingPropertyError
30+
2731
# placeholder for tables where a desired value was not obtained from stratisd
2832
# when the value should be supported.
2933
TABLE_FAILURE_STRING = "FAILURE"
@@ -160,3 +164,23 @@ def get_uuid_formatter(unhyphenated: bool) -> Callable:
160164
return (
161165
(lambda u: UUID(str(u)).hex) if unhyphenated else (lambda u: str(UUID(str(u))))
162166
)
167+
168+
169+
def catch_missing_property(
170+
prop_to_str: Callable[[Any], Any], default: Any
171+
) -> Callable[[Any], Any]:
172+
"""
173+
Return a function to just return a default if a property is missing.
174+
"""
175+
176+
@wraps(prop_to_str)
177+
def inner(mo: Any) -> str:
178+
"""
179+
Catch the exception and return a default
180+
"""
181+
try:
182+
return prop_to_str(mo)
183+
except DbusClientMissingPropertyError:
184+
return default
185+
186+
return inner

src/stratis_cli/_actions/_list_filesystem.py

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
from ._constants import TOP_OBJECT
2929
from ._formatting import (
3030
TABLE_FAILURE_STRING,
31+
TABLE_UNKNOWN_STRING,
3132
TOTAL_USED_FREE,
33+
catch_missing_property,
3234
get_property,
3335
print_table,
3436
)
@@ -124,7 +126,12 @@ def size_triple(mofs: Any) -> SizeTriple:
124126
"""
125127
Calculate size triple
126128
"""
127-
return SizeTriple(Range(mofs.Size()), get_property(mofs.Used(), Range, None))
129+
return SizeTriple(
130+
catch_missing_property(lambda mo: Range(mo.Size()), None)(mofs),
131+
catch_missing_property(
132+
lambda mo: get_property(mo.Used(), Range, None), None
133+
)(mofs),
134+
)
128135

129136

130137
class Table(ListFilesystem): # pylint: disable=too-few-public-methods
@@ -158,16 +165,31 @@ def filesystem_size_quartet(
158165
)
159166
return f"{triple_str} / {limit}"
160167

168+
def missing_property(func: Callable[[Any], str]) -> Callable[[Any], str]:
169+
return catch_missing_property(func, TABLE_UNKNOWN_STRING)
170+
171+
pool_name_func = missing_property(
172+
lambda mo: self.pool_object_path_to_pool_name.get(
173+
mo.Pool(), TABLE_UNKNOWN_STRING
174+
)
175+
)
176+
name_func = missing_property(lambda mofs: mofs.Name())
177+
size_func = missing_property(
178+
lambda mofs: filesystem_size_quartet(
179+
ListFilesystem.size_triple(mofs),
180+
get_property(mofs.SizeLimit(), Range, None),
181+
)
182+
)
183+
devnode_func = missing_property(lambda mofs: mofs.Devnode())
184+
uuid_func = missing_property(lambda mofs: self.uuid_formatter(mofs.Uuid()))
185+
161186
tables = [
162187
(
163-
self.pool_object_path_to_pool_name[mofilesystem.Pool()],
164-
mofilesystem.Name(),
165-
filesystem_size_quartet(
166-
ListFilesystem.size_triple(mofilesystem),
167-
get_property(mofilesystem.SizeLimit(), Range, None),
168-
),
169-
mofilesystem.Devnode(),
170-
self.uuid_formatter(mofilesystem.Uuid()),
188+
pool_name_func(mofilesystem),
189+
name_func(mofilesystem),
190+
size_func(mofilesystem),
191+
devnode_func(mofilesystem),
192+
uuid_func(mofilesystem),
171193
)
172194
for mofilesystem in self.filesystems_with_props
173195
]
@@ -198,29 +220,52 @@ def display(self):
198220

199221
fs = self.filesystems_with_props[0]
200222

201-
size_triple = ListFilesystem.size_triple(fs)
202-
limit = get_property(fs.SizeLimit(), Range, None)
203-
created = (
204-
date_parser.isoparse(fs.Created()).astimezone().strftime("%b %d %Y %H:%M")
205-
)
223+
def missing_property(func: Callable[[Any], str]) -> Callable[[Any], str]:
224+
return catch_missing_property(func, TABLE_UNKNOWN_STRING)(fs)
206225

207-
origin = get_property(fs.Origin(), self.uuid_formatter, None)
226+
uuid = missing_property(lambda mo: self.uuid_formatter(mo.Uuid()))
227+
print(f"UUID: {uuid}")
228+
229+
name = missing_property(lambda mo: mo.Name())
230+
print(f"Name: {name}")
231+
232+
pool = missing_property(
233+
lambda mo: self.pool_object_path_to_pool_name.get(
234+
mo.Pool(), TABLE_UNKNOWN_STRING
235+
),
236+
)
237+
print(f"Pool: {pool}")
208238

209-
print(f"UUID: {self.uuid_formatter(fs.Uuid())}")
210-
print(f"Name: {fs.Name()}")
211-
print(f"Pool: {self.pool_object_path_to_pool_name[fs.Pool()]}")
239+
devnode = missing_property(lambda mo: mo.Devnode())
212240
print()
213-
print(f"Device: {fs.Devnode()}")
241+
print(f"Device: {devnode}")
242+
243+
created = missing_property(
244+
lambda mo: date_parser.isoparse(mo.Created())
245+
.astimezone()
246+
.strftime("%b %d %Y %H:%M"),
247+
)
214248
print()
215249
print(f"Created: {created}")
250+
251+
origin = missing_property(
252+
lambda mo: get_property(mo.Origin(), self.uuid_formatter, str(None))
253+
)
216254
print()
217255
print(f"Snapshot origin: {origin}")
218256
if origin is not None:
219-
scheduled = "Yes" if fs.MergeScheduled() else "No"
257+
scheduled = missing_property(
258+
lambda mo: "Yes" if mo.MergeScheduled() else "No"
259+
)
220260
print(f" Revert scheduled: {scheduled}")
261+
262+
size_triple = ListFilesystem.size_triple(fs)
221263
print()
222264
print("Sizes:")
223-
print(f" Logical size of thin device: {size_triple.total()}")
265+
print(
266+
" Logical size of thin device: "
267+
f"{TABLE_FAILURE_STRING if size_triple.total() is None else size_triple.total()}"
268+
)
224269
print(
225270
" Total used (including XFS metadata): "
226271
f"{TABLE_FAILURE_STRING if size_triple.used() is None else size_triple.used()}"
@@ -229,5 +274,9 @@ def display(self):
229274
" Free: "
230275
f"{TABLE_FAILURE_STRING if size_triple.free() is None else size_triple.free()}"
231276
)
277+
278+
limit = missing_property(
279+
lambda mo: get_property(mo.SizeLimit(), lambda x: str(Range(x)), str(None))
280+
)
232281
print()
233282
print(f" Size Limit: {limit}")

src/stratis_cli/_actions/_list_pool.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
from ._constants import TOP_OBJECT
4545
from ._formatting import (
4646
TABLE_FAILURE_STRING,
47+
TABLE_UNKNOWN_STRING,
4748
TOTAL_USED_FREE,
49+
catch_missing_property,
4850
get_property,
4951
print_table,
5052
)
@@ -452,7 +454,7 @@ def __init__(self, uuid_formatter: Callable[[str | UUID], str]):
452454
"""
453455
self.uuid_formatter = uuid_formatter
454456

455-
def display(self):
457+
def display(self): # pylint: disable=too-many-locals
456458
"""
457459
List pools in table view.
458460
"""
@@ -526,21 +528,34 @@ def gen_string(has_property: bool, code: str) -> str:
526528
(objpath, MOPool(info)) for objpath, info in pools().search(managed_objects)
527529
]
528530

531+
name_func = catch_missing_property(lambda mo: mo.Name(), TABLE_UNKNOWN_STRING)
532+
size_func = catch_missing_property(physical_size_triple, TABLE_UNKNOWN_STRING)
533+
properties_func = catch_missing_property(
534+
properties_string, TABLE_UNKNOWN_STRING
535+
)
536+
uuid_func = catch_missing_property(
537+
lambda mo: self.uuid_formatter(mo.Uuid()), TABLE_UNKNOWN_STRING
538+
)
539+
540+
def alert_func(pool_object_path: str, mopool: Any) -> List[str]:
541+
"""
542+
Combined alert codes.
543+
"""
544+
return [
545+
str(code)
546+
for code in catch_missing_property(
547+
Default.alert_codes, default=[TABLE_UNKNOWN_STRING]
548+
)(mopool)
549+
+ alerts.alert_codes(pool_object_path)
550+
]
551+
529552
tables = [
530553
(
531-
mopool.Name(),
532-
physical_size_triple(mopool),
533-
properties_string(mopool),
534-
self.uuid_formatter(mopool.Uuid()),
535-
", ".join(
536-
sorted(
537-
str(code)
538-
for code in (
539-
Default.alert_codes(mopool)
540-
+ alerts.alert_codes(pool_object_path)
541-
)
542-
)
543-
),
554+
name_func(mopool),
555+
size_func(mopool),
556+
properties_func(mopool),
557+
uuid_func(mopool),
558+
", ".join(sorted(alert_func(pool_object_path, mopool))),
544559
)
545560
for (pool_object_path, mopool) in pools_with_props
546561
]

src/stratis_cli/_actions/_physical.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
# isort: STDLIB
1919
from argparse import Namespace
20+
from typing import Any, Callable
2021

2122
# isort: THIRDPARTY
2223
from justbytes import Range
@@ -26,6 +27,7 @@
2627
from ._constants import TOP_OBJECT
2728
from ._formatting import (
2829
TABLE_UNKNOWN_STRING,
30+
catch_missing_property,
2931
get_property,
3032
get_uuid_formatter,
3133
print_table,
@@ -79,7 +81,7 @@ def list_devices(namespace: Namespace): # pylint: disable=too-many-locals
7981
).search(managed_objects)
8082
)
8183

82-
def paths(modev):
84+
def paths(modev: Any) -> str:
8385
"""
8486
Return <physical_path> (<metadata_path>) if they are different,
8587
otherwise, just <metadata_path>.
@@ -100,7 +102,7 @@ def paths(modev):
100102
else f"{physical_path} ({metadata_path})"
101103
)
102104

103-
def size(modev):
105+
def size_str(modev: Any) -> str:
104106
"""
105107
Return in-use size (observed size) if they are different, otherwise
106108
just in-use size.
@@ -113,24 +115,35 @@ def size(modev):
113115
else f"{in_use_size} ({observed_size})"
114116
)
115117

116-
def tier_str(value):
118+
def tier_str(modev: Any) -> str:
117119
"""
118120
String representation of a tier.
119121
"""
120122
try:
121-
return str(BlockDevTiers(value))
123+
return str(BlockDevTiers(modev.Tier()))
122124
except ValueError: # pragma: no cover
123125
return TABLE_UNKNOWN_STRING
124126

125127
format_uuid = get_uuid_formatter(namespace.unhyphenated_uuids)
126128

129+
def missing_property(func: Callable[[Any], str]) -> Callable[[Any], str]:
130+
return catch_missing_property(func, TABLE_UNKNOWN_STRING)
131+
132+
pool_name_func = missing_property(
133+
lambda mo: path_to_name.get(mo.Pool(), TABLE_UNKNOWN_STRING)
134+
)
135+
paths_func = missing_property(paths)
136+
size_func = missing_property(size_str)
137+
tier_func = missing_property(tier_str)
138+
uuid_func = missing_property(lambda modev: format_uuid(modev.Uuid()))
139+
127140
tables = [
128141
[
129-
path_to_name.get(modev.Pool(), TABLE_UNKNOWN_STRING),
130-
paths(modev),
131-
size(modev),
132-
tier_str(modev.Tier()),
133-
format_uuid(modev.Uuid()),
142+
pool_name_func(modev),
143+
paths_func(modev),
144+
size_func(modev),
145+
tier_func(modev),
146+
uuid_func(modev),
134147
]
135148
for modev in modevs
136149
]

tests/integration/pool/test_list.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
"""
1717

1818
# isort: STDLIB
19+
from unittest.mock import patch
1920
from uuid import uuid4
2021

2122
# isort: FIRSTPARTY
22-
from dbus_client_gen import DbusClientUniqueResultError
23+
from dbus_client_gen import DbusClientMissingPropertyError, DbusClientUniqueResultError
2324

2425
# isort: LOCAL
2526
from stratis_cli import StratisCliErrorCodes
@@ -316,3 +317,24 @@ def test_list_detail(self):
316317
Test detail view on running pool.
317318
"""
318319
TEST_RUNNER(self._MENU + [f"--name={self._POOLNAME}"])
320+
321+
def test_list_no_size(self):
322+
"""
323+
Test listing the pool when size information not included in
324+
GetManagedObjects result.
325+
"""
326+
# isort: LOCAL
327+
import stratis_cli # pylint: disable=import-outside-toplevel
328+
329+
with patch.object(
330+
# pylint: disable=protected-access
331+
stratis_cli._actions._list_pool.Default, # pyright: ignore
332+
"size_triple",
333+
autospec=True,
334+
side_effect=DbusClientMissingPropertyError(
335+
"oops",
336+
stratis_cli._actions._constants.POOL_INTERFACE, # pyright: ignore
337+
"TotalPhysicalUsed",
338+
),
339+
):
340+
TEST_RUNNER(self._MENU)

0 commit comments

Comments
 (0)