Skip to content

Commit d0ec14a

Browse files
committed
Add parser option to use /dev/stratis to identify filesystems
Signed-off-by: mulhern <amulhern@redhat.com>
1 parent 83ae558 commit d0ec14a

File tree

3 files changed

+177
-58
lines changed

3 files changed

+177
-58
lines changed

src/stratis_cli/_actions/_list_filesystem.py

Lines changed: 163 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@
1616
"""
1717

1818
# isort: STDLIB
19+
import glob
20+
import os
1921
from abc import ABC, abstractmethod
22+
from collections import defaultdict
23+
from pathlib import Path
2024
from typing import Any, Callable, Dict, List, Optional
2125

2226
# isort: THIRDPARTY
2327
from dateutil import parser as date_parser
2428
from dbus import ObjectPath, String
2529
from justbytes import Range
2630

31+
from .._constants import FilesystemId, IdType
2732
from ._connection import get_object
2833
from ._constants import TOP_OBJECT
2934
from ._formatting import (
@@ -35,8 +40,37 @@
3540
from ._utils import SizeTriple
3641

3742

43+
def _read_filesystem_symlinks(
44+
*, pool_name: Optional[str] = None, fs_name: Optional[str] = None
45+
) -> defaultdict[str, set[str]]:
46+
"""
47+
Return a dict of pool names to filesystem names based on reading the
48+
directory of filesystem links at /dev/stratis.
49+
50+
Restrict to just one pool if pool_name is specified.
51+
"""
52+
pools_and_fss = defaultdict(set)
53+
for path in glob.glob(
54+
os.path.join(
55+
"/",
56+
*(
57+
["dev", "stratis"]
58+
+ (["*"] if pool_name is None else [pool_name])
59+
+ (["*"] if fs_name is None else [fs_name])
60+
),
61+
)
62+
):
63+
p_path = Path(path)
64+
pools_and_fss[p_path.parent.name].add(p_path.name)
65+
return pools_and_fss
66+
67+
3868
def list_filesystems(
39-
uuid_formatter: Callable, *, pool_name=None, fs_id=None
69+
uuid_formatter: Callable,
70+
*,
71+
pool_name: Optional[str] = None,
72+
fs_id: Optional[FilesystemId] = None,
73+
use_dev_dir: bool = False,
4074
): # pylint: disable=too-many-locals
4175
"""
4276
List the specified information about filesystems.
@@ -46,14 +80,26 @@ def list_filesystems(
4680
# pylint: disable=import-outside-toplevel
4781
from ._data import MOFilesystem, MOPool, ObjectManager, filesystems, pools
4882

83+
pools_and_fss = (
84+
_read_filesystem_symlinks(
85+
pool_name=pool_name,
86+
fs_name=(
87+
fs_id.id_value
88+
if fs_id is not None and fs_id.id_type is IdType.UUID
89+
else None
90+
),
91+
)
92+
if use_dev_dir
93+
else None
94+
)
95+
4996
proxy = get_object(TOP_OBJECT)
5097
managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
5198

5299
if pool_name is None:
53100
props = None
54101
pool_object_path = None
55102
fs_props = None
56-
requires_unique = False
57103
else:
58104
props = {"Name": pool_name}
59105
pool_object_path = next(
@@ -62,7 +108,6 @@ def list_filesystems(
62108
fs_props = {"Pool": pool_object_path} | (
63109
{} if fs_id is None else fs_id.managed_objects_key()
64110
)
65-
requires_unique = fs_id is not None
66111

67112
pool_object_path_to_pool_name = dict(
68113
(path, MOPool(info).Name())
@@ -72,49 +117,46 @@ def list_filesystems(
72117
filesystems_with_props = [
73118
MOFilesystem(info)
74119
for objpath, info in filesystems(props=fs_props)
75-
.require_unique_match(requires_unique)
120+
.require_unique_match(
121+
(not use_dev_dir) and pool_name is not None and fs_id is not None
122+
)
76123
.search(managed_objects)
77124
]
78125

79-
if fs_id is None:
80-
klass = Table(
81-
uuid_formatter,
82-
filesystems_with_props,
83-
pool_object_path_to_pool_name,
84-
)
85-
else:
86-
klass = Detail(
87-
uuid_formatter,
88-
filesystems_with_props,
89-
pool_object_path_to_pool_name,
90-
)
91-
92-
klass.display()
126+
klass = (Table if fs_id is None else Detail)(uuid_formatter)
127+
klass.display(
128+
filesystems_with_props,
129+
pool_object_path_to_pool_name,
130+
system_pools_and_fss=pools_and_fss,
131+
pool_name=pool_name,
132+
fs_id=fs_id,
133+
)
93134

94135

95136
class ListFilesystem(ABC): # pylint: disable=too-few-public-methods
96137
"""
97138
Handle listing a filesystem or filesystems.
98139
"""
99140

100-
def __init__(
101-
self,
102-
uuid_formatter: Callable,
103-
filesystems_with_props: List[Any],
104-
pool_object_path_to_pool_name: Dict[ObjectPath, String],
105-
):
141+
def __init__(self, uuid_formatter: Callable):
106142
"""
107143
Initialize a List object.
108144
:param uuid_formatter: function to format a UUID str or UUID
109145
:param uuid_formatter: str or UUID -> str
110146
:param bool stopped: whether to list stopped pools
111147
"""
112148
self.uuid_formatter = uuid_formatter
113-
self.filesystems_with_props = filesystems_with_props
114-
self.pool_object_path_to_pool_name = pool_object_path_to_pool_name
115149

116150
@abstractmethod
117-
def display(self):
151+
def display( # pylint: disable=too-many-arguments
152+
self,
153+
filesystems_with_props: List[Any],
154+
pool_object_path_to_pool_name: Dict[ObjectPath, String],
155+
*,
156+
system_pools_and_fss: Optional[defaultdict[str, set(str)]] = None,
157+
pool_name: Optional[str] = None,
158+
fs_id: Optional[FilesystemId] = None,
159+
):
118160
"""
119161
List filesystems.
120162
"""
@@ -125,7 +167,15 @@ class Table(ListFilesystem): # pylint: disable=too-few-public-methods
125167
List filesystems using table format.
126168
"""
127169

128-
def display(self):
170+
def display( # pylint: disable=too-many-arguments
171+
self,
172+
filesystems_with_props: List[Any],
173+
pool_object_path_to_pool_name: Dict[ObjectPath, String],
174+
*,
175+
system_pools_and_fss: Optional[defaultdict[str, set(str)]] = None,
176+
pool_name: Optional[str] = None,
177+
fs_id: Optional[FilesystemId] = None,
178+
):
129179
"""
130180
List the filesystems.
131181
"""
@@ -152,20 +202,52 @@ def filesystem_size_quartet(
152202
)
153203
return f'{triple_str} / {"None" if limit is None else limit}'
154204

155-
tables = [
156-
(
157-
self.pool_object_path_to_pool_name[mofilesystem.Pool()],
158-
mofilesystem.Name(),
159-
filesystem_size_quartet(
160-
Range(mofilesystem.Size()),
161-
get_property(mofilesystem.Used(), Range, None),
162-
get_property(mofilesystem.SizeLimit(), Range, None),
163-
),
164-
mofilesystem.Devnode(),
165-
self.uuid_formatter(mofilesystem.Uuid()),
166-
)
167-
for mofilesystem in self.filesystems_with_props
168-
]
205+
if system_pools_and_fss is not None:
206+
tables = []
207+
for mofilesystem in filesystems_with_props:
208+
pool_name = pool_object_path_to_pool_name[mofilesystem.Pool()]
209+
fs_name = mofilesystem.Name()
210+
if fs_name in system_pools_and_fss.get(pool_name, []):
211+
tables.append(
212+
(
213+
pool_name,
214+
fs_name,
215+
filesystem_size_quartet(
216+
Range(mofilesystem.Size()),
217+
get_property(mofilesystem.Used(), Range, None),
218+
get_property(mofilesystem.SizeLimit(), Range, None),
219+
),
220+
mofilesystem.Devnode(),
221+
self.uuid_formatter(mofilesystem.Uuid()),
222+
)
223+
)
224+
system_pools_and_fss[pool_name].remove(fs_name)
225+
for pool, fss in system_pools_and_fss:
226+
for fs in fss:
227+
tables.append(
228+
(
229+
pool,
230+
fs,
231+
"<UNAVAILABLE> / <UNAVAILABLE> / <UNAVAILABLE> / <UNAVAILABLE>",
232+
os.path.join("/", "dev", "stratis", pool, fs),
233+
"<UNAVAILABLE>",
234+
)
235+
)
236+
else:
237+
tables = [
238+
(
239+
pool_object_path_to_pool_name[mofilesystem.Pool()],
240+
mofilesystem.Name(),
241+
filesystem_size_quartet(
242+
Range(mofilesystem.Size()),
243+
get_property(mofilesystem.Used(), Range, None),
244+
get_property(mofilesystem.SizeLimit(), Range, None),
245+
),
246+
mofilesystem.Devnode(),
247+
self.uuid_formatter(mofilesystem.Uuid()),
248+
)
249+
for mofilesystem in filesystems_with_props
250+
]
169251

170252
print_table(
171253
[
@@ -185,31 +267,55 @@ class Detail(ListFilesystem): # pylint: disable=too-few-public-methods
185267
Do a detailed listing of filesystems.
186268
"""
187269

188-
def display(self):
270+
def display( # pylint: disable=too-many-arguments, too-many-locals
271+
self,
272+
filesystems_with_props: List[Any],
273+
pool_object_path_to_pool_name: Dict[ObjectPath, String],
274+
*,
275+
system_pools_and_fss: Optional[defaultdict[str, set(str)]] = None,
276+
pool_name: Optional[str] = None,
277+
fs_id: Optional[FilesystemId] = None,
278+
):
189279
"""
190280
List the filesystems.
191281
"""
192-
assert len(self.filesystems_with_props) == 1
282+
if system_pools_and_fss is None:
283+
assert len(filesystems_with_props) == 1
193284

194-
fs = self.filesystems_with_props[0]
285+
fs = filesystems_with_props[0]
195286

196-
size_triple = SizeTriple(Range(fs.Size()), get_property(fs.Used(), Range, None))
197-
limit = get_property(fs.SizeLimit(), Range, None)
198-
created = (
199-
date_parser.isoparse(fs.Created()).astimezone().strftime("%b %d %Y %H:%M")
200-
)
287+
size_triple = SizeTriple(
288+
Range(fs.Size()), get_property(fs.Used(), Range, None)
289+
)
290+
291+
limit = get_property(fs.SizeLimit(), Range, None)
292+
limit_str = "None" if limit is None else limit
293+
294+
created = (
295+
date_parser.isoparse(fs.Created())
296+
.astimezone()
297+
.strftime("%b %d %Y %H:%M")
298+
)
299+
300+
origin = get_property(fs.Origin(), self.uuid_formatter, None)
301+
origin_str = "None" if origin is None else origin
201302

202-
origin = get_property(fs.Origin(), self.uuid_formatter, None)
303+
uuid = self.uuid_formatter(fs.Uuid())
304+
name = fs.Name()
305+
pool = pool_object_path_to_pool_name[fs.Pool()]
306+
devnode = fs.Devnode()
307+
else:
308+
pass
203309

204-
print(f"UUID: {self.uuid_formatter(fs.Uuid())}")
205-
print(f"Name: {fs.Name()}")
206-
print(f"Pool: {self.pool_object_path_to_pool_name[fs.Pool()]}")
310+
print(f"UUID: {uuid}")
311+
print(f"Name: {name}")
312+
print(f"Pool: {pool}")
207313
print()
208-
print(f"Device: {fs.Devnode()}")
314+
print(f"Device: {devnode}")
209315
print()
210316
print(f"Created: {created}")
211317
print()
212-
print(f'Snapshot origin: {"None" if origin is None else origin}')
318+
print(f"Snapshot origin: {origin_str}")
213319
if origin is not None:
214320
scheduled = "Yes" if fs.MergeScheduled() else "No"
215321
print(f" Revert scheduled: {scheduled}")
@@ -225,4 +331,4 @@ def display(self):
225331
f"{TABLE_FAILURE_STRING if size_triple.free() is None else size_triple.free()}"
226332
)
227333
print()
228-
print(f" Size Limit: {'None' if limit is None else limit}")
334+
print(f" Size Limit: {limit_str}")

src/stratis_cli/_actions/_logical.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,10 @@ def list_volumes(namespace: Namespace):
136136

137137
uuid_formatter = get_uuid_formatter(namespace.unhyphenated_uuids)
138138
list_filesystems(
139-
uuid_formatter, pool_name=getattr(namespace, "pool_name", None), fs_id=fs_id
139+
uuid_formatter,
140+
pool_name=getattr(namespace, "pool_name", None),
141+
fs_id=fs_id,
142+
use_dev_dir=getattr(namespace, "use_dev_dir", False),
140143
)
141144

142145
@staticmethod

src/stratis_cli/_parser/_logical.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ def verify(self, namespace: Namespace, parser: ArgumentParser):
139139
"help": "Pool name",
140140
},
141141
),
142+
(
143+
"--use-dev-dir",
144+
{
145+
"action": "store_true",
146+
"help": (
147+
'Use links in "/dev/stratis" to identify what '
148+
"filesystems are present."
149+
),
150+
},
151+
),
142152
],
143153
"func": LogicalActions.list_volumes,
144154
},

0 commit comments

Comments
 (0)