Skip to content

Commit 9e13df6

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 38635d2 commit 9e13df6

5 files changed

Lines changed: 167 additions & 43 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: 69 additions & 19 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
)
@@ -158,16 +160,36 @@ def filesystem_size_quartet(
158160
)
159161
return f"{triple_str} / {limit}"
160162

163+
pool_name_func = catch_missing_property(
164+
lambda mo: self.pool_object_path_to_pool_name.get(
165+
mo.Pool(), TABLE_UNKNOWN_STRING
166+
),
167+
TABLE_UNKNOWN_STRING,
168+
)
169+
name_func = catch_missing_property(
170+
lambda mofs: mofs.Name(), TABLE_UNKNOWN_STRING
171+
)
172+
size_func = catch_missing_property(
173+
lambda mofs: filesystem_size_quartet(
174+
ListFilesystem.size_triple(mofs),
175+
get_property(mofs.SizeLimit(), Range, None),
176+
),
177+
TABLE_UNKNOWN_STRING,
178+
)
179+
devnode_func = catch_missing_property(
180+
lambda mofs: mofs.Devnode(), TABLE_UNKNOWN_STRING
181+
)
182+
uuid_func = catch_missing_property(
183+
lambda mofs: self.uuid_formatter(mofs.Uuid()), TABLE_UNKNOWN_STRING
184+
)
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,26 +220,50 @@ 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+
uuid = catch_missing_property(
224+
lambda mo: self.uuid_formatter(mo.Uuid()), TABLE_UNKNOWN_STRING
225+
)(fs)
226+
print(f"UUID: {uuid}")
206227

207-
origin = get_property(fs.Origin(), self.uuid_formatter, None)
228+
name = catch_missing_property(lambda mo: mo.Name(), TABLE_UNKNOWN_STRING)(fs)
229+
print(f"Name: {name}")
208230

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()]}")
231+
pool = catch_missing_property(
232+
lambda mo: self.pool_object_path_to_pool_name.get(
233+
fs.Pool(), TABLE_UNKNOWN_STRING
234+
),
235+
TABLE_UNKNOWN_STRING,
236+
)
237+
print(f"Pool: {pool}")
238+
239+
devnode = catch_missing_property(lambda mo: mo.Devnode(), TABLE_UNKNOWN_STRING)
212240
print()
213-
print(f"Device: {fs.Devnode()}")
241+
print(f"Device: {devnode}")
242+
243+
created = catch_missing_property(
244+
lambda mo: date_parser.isoparse(mo.Created())
245+
.astimezone()
246+
.strftime("%b %d %Y %H:%M"),
247+
TABLE_UNKNOWN_STRING,
248+
)(fs)
214249
print()
215250
print(f"Created: {created}")
251+
252+
origin = catch_missing_property(
253+
lambda mo: get_property(mo.Origin(), self.uuid_formatter, None),
254+
TABLE_UNKNOWN_STRING,
255+
)(fs)
216256
print()
217257
print(f"Snapshot origin: {origin}")
218258
if origin is not None:
219-
scheduled = "Yes" if fs.MergeScheduled() else "No"
259+
scheduled = catch_missing_property(
260+
lambda mo: "Yes" if fs.MergeScheduled() else "No", TABLE_UNKNOWN_STRING
261+
)(fs)
220262
print(f" Revert scheduled: {scheduled}")
263+
264+
size_triple = catch_missing_property(
265+
ListFilesystem.size_triple, TABLE_UNKNOWN_STRING
266+
)(fs)
221267
print()
222268
print("Sizes:")
223269
print(f" Logical size of thin device: {size_triple.total()}")
@@ -229,5 +275,9 @@ def display(self):
229275
" Free: "
230276
f"{TABLE_FAILURE_STRING if size_triple.free() is None else size_triple.free()}"
231277
)
278+
279+
limit = catch_missing_property(
280+
lambda mo: get_property(mo.SizeLimit(), Range, None), TABLE_UNKNOWN_STRING
281+
)(fs)
232282
print()
233283
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
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+
pool_name_func = catch_missing_property(
130+
lambda mo: path_to_name.get(mo.Pool(), TABLE_UNKNOWN_STRING),
131+
TABLE_UNKNOWN_STRING,
132+
)
133+
paths_func = catch_missing_property(paths, TABLE_UNKNOWN_STRING)
134+
size_func = catch_missing_property(size_str, TABLE_UNKNOWN_STRING)
135+
tier_func = catch_missing_property(tier_str, TABLE_UNKNOWN_STRING)
136+
uuid_func = catch_missing_property(
137+
lambda modev: format_uuid(modev.Uuid()), TABLE_UNKNOWN_STRING
138+
)
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)