Skip to content

Commit c81a666

Browse files
mgr/smb: reorganize internal.py to use fewer if-isinstance blocks
While working on adding a new top-level resource type I realized that this code can be a bit error prone and un-DRY. Use a single method for mapping between resource classes and resource entry classes and refactor the rest of the module around that idea. Signed-off-by: John Mulligan <[email protected]>
1 parent d5fb7f8 commit c81a666

File tree

1 file changed

+97
-59
lines changed

1 file changed

+97
-59
lines changed

src/pybind/mgr/smb/internal.py

Lines changed: 97 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Support for working with the internal data store and the strucutured
22
resources that the internal store holds.
33
"""
4-
from typing import Collection, Tuple, Type, TypeVar
4+
from typing import Collection, Tuple, Type, TypeVar, Union
55

66
from . import resources
77
from .enums import ConfigNS, State
@@ -10,6 +10,7 @@
1010
ConfigStore,
1111
ConfigStoreListing,
1212
EntryKey,
13+
ResourceKey,
1314
Self,
1415
Simplifiable,
1516
)
@@ -19,37 +20,26 @@
1920
T = TypeVar('T')
2021

2122

22-
def cluster_key(cluster_id: str) -> EntryKey:
23-
"""Return store entry key for a cluster entry."""
24-
return str(ConfigNS.CLUSTERS), cluster_id
23+
class ResourceIDKey:
24+
"""A ResourceKey for resources with only one ID."""
2525

26+
def __init__(self, resource_id: str) -> None:
27+
self.resource_id = resource_id
2628

27-
def share_key(cluster_id: str, share_id: str) -> EntryKey:
28-
"""Return store entry key for a share entry."""
29-
return str(ConfigNS.SHARES), f'{cluster_id}.{share_id}'
29+
def __str__(self) -> str:
30+
return self.resource_id
3031

3132

32-
def join_auth_key(auth_id: str) -> EntryKey:
33-
"""Return store entry key for a join auth entry."""
34-
return str(ConfigNS.JOIN_AUTHS), auth_id
33+
class ChildResourceIDKey:
34+
"""A ResourceKey for resources with both a parent and child ID."""
3535

36+
def __init__(self, parent_id: str, resource_id: str) -> None:
37+
self.parent_id = parent_id
38+
self.resource_id = resource_id
39+
self._key = f'{parent_id}.{resource_id}'
3640

37-
def users_and_groups_key(users_groups_id: str) -> EntryKey:
38-
"""Return store entry key for a users-and-groups entry."""
39-
return str(ConfigNS.USERS_AND_GROUPS), users_groups_id
40-
41-
42-
def resource_key(resource: SMBResource) -> EntryKey:
43-
"""Return a store entry key for an smb resource object."""
44-
if isinstance(resource, (resources.Cluster, resources.RemovedCluster)):
45-
return cluster_key(resource.cluster_id)
46-
elif isinstance(resource, (resources.Share, resources.RemovedShare)):
47-
return share_key(resource.cluster_id, resource.share_id)
48-
elif isinstance(resource, resources.JoinAuth):
49-
return join_auth_key(resource.auth_id)
50-
elif isinstance(resource, resources.UsersAndGroups):
51-
return users_and_groups_key(resource.users_groups_id)
52-
raise TypeError('not a valid smb resource')
41+
def __str__(self) -> str:
42+
return self._key
5343

5444

5545
class ResourceEntry:
@@ -70,7 +60,7 @@ def get(self) -> SMBResource:
7060

7161
def get_resource_type(self, cls: Type[T]) -> T:
7262
obj = self.get()
73-
assert isinstance(obj, cls)
63+
assert isinstance(obj, cls), f"{obj!r} is not a {cls}"
7464
return obj
7565

7666
def set(self, resource: Simplifiable) -> None:
@@ -90,20 +80,49 @@ def create_or_update(self, resource: Simplifiable) -> State:
9080
def remove(self) -> bool:
9181
return self.config_entry.remove()
9282

83+
@classmethod
84+
def ids(
85+
cls, store: ConfigStoreListing
86+
) -> Union[Collection[str], Collection[Tuple[str, str]]]:
87+
raise NotImplementedError()
9388

94-
class ClusterEntry(ResourceEntry):
95-
"""Cluster resource getter/setter for the smb internal data store(s)."""
89+
@classmethod
90+
def from_store_by_key(
91+
cls, store: ConfigStore, key: Union[str, ResourceKey]
92+
) -> Self:
93+
_key = str(key)
94+
return cls(_key, store[str(cls.namespace), _key])
95+
96+
@classmethod
97+
def to_key(cls, resource: SMBResource) -> ResourceKey:
98+
raise NotImplementedError()
9699

97-
namespace = ConfigNS.CLUSTERS
100+
101+
class CommonResourceEntry(ResourceEntry):
102+
"""Common resource getter/setter for objects with a single ID value in the
103+
smb internal data store(s).
104+
"""
98105

99106
@classmethod
100-
def from_store(cls, store: ConfigStore, cluster_id: str) -> Self:
101-
return cls(cluster_id, store[str(cls.namespace), cluster_id])
107+
def from_store(cls, store: ConfigStore, resource_id: str) -> Self:
108+
return cls.from_store_by_key(store, resource_id)
102109

103110
@classmethod
104111
def ids(cls, store: ConfigStoreListing) -> Collection[str]:
105112
return store.contents(str(cls.namespace))
106113

114+
115+
class ClusterEntry(CommonResourceEntry):
116+
"""Cluster resource getter/setter for the smb internal data store(s)."""
117+
118+
namespace = ConfigNS.CLUSTERS
119+
_for_resource = (resources.Cluster, resources.RemovedCluster)
120+
121+
@classmethod
122+
def to_key(cls, resource: SMBResource) -> ResourceKey:
123+
assert isinstance(resource, cls._for_resource)
124+
return ResourceIDKey(resource.cluster_id)
125+
107126
def get_cluster(self) -> resources.Cluster:
108127
return self.get_resource_type(resources.Cluster)
109128

@@ -112,73 +131,92 @@ class ShareEntry(ResourceEntry):
112131
"""Share resource getter/setter for the smb internal data store(s)."""
113132

114133
namespace = ConfigNS.SHARES
134+
_for_resource = (resources.Share, resources.RemovedShare)
115135

116136
@classmethod
117137
def from_store(
118138
cls, store: ConfigStore, cluster_id: str, share_id: str
119139
) -> Self:
120-
key = f'{cluster_id}.{share_id}'
121-
return cls(key, store[str(cls.namespace), key])
140+
key = ChildResourceIDKey(cluster_id, share_id)
141+
return cls.from_store_by_key(store, str(key))
122142

123143
@classmethod
124144
def ids(cls, store: ConfigStoreListing) -> Collection[Tuple[str, str]]:
125145
return [_split(k) for k in store.contents(str(cls.namespace))]
126146

147+
@classmethod
148+
def to_key(cls, resource: SMBResource) -> ResourceKey:
149+
assert isinstance(resource, cls._for_resource)
150+
return ChildResourceIDKey(resource.cluster_id, resource.share_id)
151+
127152
def get_share(self) -> resources.Share:
128153
return self.get_resource_type(resources.Share)
129154

130155

131-
class JoinAuthEntry(ResourceEntry):
156+
class JoinAuthEntry(CommonResourceEntry):
132157
"""JoinAuth resource getter/setter for the smb internal data store(s)."""
133158

134159
namespace = ConfigNS.JOIN_AUTHS
160+
_for_resource = resources.JoinAuth
135161

136162
@classmethod
137-
def from_store(cls, store: ConfigStore, auth_id: str) -> Self:
138-
return cls(auth_id, store[str(cls.namespace), auth_id])
139-
140-
@classmethod
141-
def ids(cls, store: ConfigStoreListing) -> Collection[str]:
142-
return store.contents(str(cls.namespace))
163+
def to_key(cls, resource: SMBResource) -> ResourceKey:
164+
assert isinstance(resource, cls._for_resource)
165+
return ResourceIDKey(resource.auth_id)
143166

144167
def get_join_auth(self) -> resources.JoinAuth:
145168
return self.get_resource_type(resources.JoinAuth)
146169

147170

148-
class UsersAndGroupsEntry(ResourceEntry):
171+
class UsersAndGroupsEntry(CommonResourceEntry):
149172
"""UsersAndGroupsEntry resource getter/setter for the smb internal data
150173
store(s).
151174
"""
152175

153176
namespace = ConfigNS.USERS_AND_GROUPS
177+
_for_resource = resources.UsersAndGroups
154178

155179
@classmethod
156-
def from_store(cls, store: ConfigStore, auth_id: str) -> Self:
157-
return cls(auth_id, store[str(cls.namespace), auth_id])
158-
159-
@classmethod
160-
def ids(cls, store: ConfigStoreListing) -> Collection[str]:
161-
return store.contents(str(cls.namespace))
180+
def to_key(cls, resource: SMBResource) -> ResourceKey:
181+
assert isinstance(resource, cls._for_resource)
182+
return ResourceIDKey(resource.users_groups_id)
162183

163184
def get_users_and_groups(self) -> resources.UsersAndGroups:
164185
return self.get_resource_type(resources.UsersAndGroups)
165186

166187

188+
def _map_resource_entry(
189+
resource: Union[SMBResource, Type[SMBResource]]
190+
) -> Type[ResourceEntry]:
191+
rcls = resource if isinstance(resource, type) else type(resource)
192+
_map = {
193+
resources.Cluster: ClusterEntry,
194+
resources.RemovedCluster: ClusterEntry,
195+
resources.Share: ShareEntry,
196+
resources.RemovedShare: ShareEntry,
197+
resources.JoinAuth: JoinAuthEntry,
198+
resources.UsersAndGroups: UsersAndGroupsEntry,
199+
}
200+
try:
201+
return _map[rcls]
202+
except KeyError:
203+
raise TypeError('not a valid smb resource')
204+
205+
167206
def resource_entry(
168207
store: ConfigStore, resource: SMBResource
169208
) -> ResourceEntry:
170209
"""Return a bound store entry object given a resource object."""
171-
if isinstance(resource, (resources.Cluster, resources.RemovedCluster)):
172-
return ClusterEntry.from_store(store, resource.cluster_id)
173-
elif isinstance(resource, (resources.Share, resources.RemovedShare)):
174-
return ShareEntry.from_store(
175-
store, resource.cluster_id, resource.share_id
176-
)
177-
elif isinstance(resource, resources.JoinAuth):
178-
return JoinAuthEntry.from_store(store, resource.auth_id)
179-
elif isinstance(resource, resources.UsersAndGroups):
180-
return UsersAndGroupsEntry.from_store(store, resource.users_groups_id)
181-
raise TypeError('not a valid smb resource')
210+
entry_cls = _map_resource_entry(resource)
211+
key = entry_cls.to_key(resource)
212+
return entry_cls.from_store_by_key(store, key)
213+
214+
215+
def resource_key(resource: SMBResource) -> EntryKey:
216+
"""Return a store entry key for an smb resource object."""
217+
entry_cls = _map_resource_entry(resource)
218+
key = entry_cls.to_key(resource)
219+
return str(entry_cls.namespace), str(key)
182220

183221

184222
def _split(share_key: str) -> Tuple[str, str]:

0 commit comments

Comments
 (0)