Skip to content

Commit fc08540

Browse files
committed
ceph-volume: Introduce new Lvm base class to unify LVM object handling
This commit introduces a new `Lvm` base class to streamline LVM related objects (`PVolume`, `VolumeGroup`, and `Volume`) by consolidating shared logic. Key changes: - `Lvm` centralizes common attributes like `name`, `tags`, `path`, and `binary_change`. - `clear_tags`, `clear_tag`, `set_tag`, and `set_tags` are now defined in `Lvm`, reducing code duplication. - `PVolume`, `VolumeGroup`, and `Volume` inherit from `Lvm`, simplifying their constructors. - The redundant `_format_tag_args` and tag manipulation methods in child classes are removed. This refactor improves maintainability by reducing code duplication while preserving the existing API behavior. Signed-off-by: Guillaume Abrioux <[email protected]>
1 parent 3e9ff5c commit fc08540

File tree

1 file changed

+101
-110
lines changed
  • src/ceph-volume/ceph_volume/api

1 file changed

+101
-110
lines changed

src/ceph-volume/ceph_volume/api/lvm.py

Lines changed: 101 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,95 @@ def is_ceph_device(lv: "Volume") -> bool:
333333
else:
334334
return True
335335

336+
class Lvm:
337+
def __init__(self, name_key: str, tags_key: str, **kw: Any) -> None:
338+
self.name: str = kw.get(name_key, '')
339+
self.binary_change: str = ''
340+
self.path: str = ''
341+
if not self.name:
342+
raise ValueError(f'{self.__class__.__name__} must have a non-empty name')
343+
344+
self.api_data = kw
345+
self.tags = parse_tags(kw.get(tags_key, ''))
346+
347+
for k, v in kw.items():
348+
setattr(self, k, v)
349+
350+
def __str__(self) -> str:
351+
return f'<{self.name}>'
352+
353+
def __repr__(self) -> str:
354+
return self.__str__()
355+
356+
def _format_tag_args(self, op: str, tags: Dict[str, Any]) -> List[str]:
357+
result: List[str] = []
358+
for k, v in tags.items():
359+
result.extend([op, f'{k}={v}'])
360+
return result
361+
362+
def clear_tags(self, keys: Optional[List[str]] = None) -> None:
363+
"""
364+
Removes all or passed tags.
365+
"""
366+
if not keys:
367+
keys = list(self.tags.keys())
368+
369+
del_tags = {k: self.tags[k] for k in keys if k in self.tags}
370+
if not del_tags:
371+
# nothing to clear
372+
return
373+
del_tag_args = self._format_tag_args('--deltag', del_tags)
374+
# --deltag returns successful even if the to be deleted tag is not set
375+
process.call([self.binary_change] + del_tag_args + [self.path], run_on_host=True)
376+
for k in del_tags.keys():
377+
del self.tags[k]
378+
379+
def clear_tag(self, key: str) -> None:
380+
if self.tags.get(key):
381+
current_value = self.tags[key]
382+
tag = "%s=%s" % (key, current_value)
383+
process.call([self.binary_change, '--deltag', tag, self.path], run_on_host=True)
384+
del self.tags[key]
385+
386+
def set_tag(self, key: str, value: str) -> None:
387+
"""
388+
Set the key/value pair as an LVM tag.
389+
"""
390+
# remove it first if it exists
391+
self.clear_tag(key)
392+
393+
process.call(
394+
[
395+
self.binary_change,
396+
'--addtag', '%s=%s' % (key, value), self.path
397+
],
398+
run_on_host=True
399+
)
400+
self.tags[key] = value
401+
402+
def set_tags(self, tags: Dict[str, Any]) -> None:
403+
"""
404+
:param tags: A dictionary of tag names and values, like::
405+
406+
{
407+
"ceph.osd_fsid": "aaa-fff-bbbb",
408+
"ceph.osd_id": "0"
409+
}
410+
411+
At the end of all modifications, the tags are refreshed to reflect
412+
LVM's most current view.
413+
"""
414+
self.clear_tags(list(tags.keys()))
415+
add_tag_args = self._format_tag_args('--addtag', tags)
416+
process.call([self.binary_change] + add_tag_args + [self.path], run_on_host=True)
417+
for k, v in tags.items():
418+
self.tags[k] = v
419+
420+
def deactivate(self) -> None:
421+
"""
422+
Deactivate the LV by calling lvchange -an
423+
"""
424+
process.call([self.binary_change, '-an', self.path], run_on_host=True)
336425

337426
####################################
338427
#
@@ -342,7 +431,7 @@ def is_ceph_device(lv: "Volume") -> bool:
342431

343432
PV_FIELDS = 'pv_name,pv_tags,pv_uuid,vg_name,lv_uuid'
344433

345-
class PVolume:
434+
class PVolume(Lvm):
346435
"""
347436
Represents a Physical Volume from LVM, with some top-level attributes like
348437
``pv_name`` and parsed tags as a dictionary of key/value pairs.
@@ -351,18 +440,10 @@ class PVolume:
351440
def __init__(self, **kw: Any) -> None:
352441
self.pv_name: str = ''
353442
self.pv_uuid: str = ''
354-
self.lv_uuid: str = ''
355-
for k, v in kw.items():
356-
setattr(self, k, v)
443+
super().__init__('pv_name', 'pv_tags', **kw)
357444
self.pv_api = kw
358-
self.name = kw['pv_name']
359-
self.tags = parse_tags(kw['pv_tags'])
360-
361-
def __str__(self) -> str:
362-
return '<%s>' % self.pv_api['pv_name']
363-
364-
def __repr__(self) -> str:
365-
return self.__str__()
445+
self.binary_change: str = 'pvchange'
446+
self.path: str = self.pv_name
366447

367448
def set_tags(self, tags: Dict[str, Any]) -> None:
368449
"""
@@ -512,7 +593,7 @@ def get_single_pv(fields: str = PV_FIELDS, filters: Optional[Dict[str, Any]] = N
512593
VG_CMD_OPTIONS = ['--noheadings', '--readonly', '--units=b', '--nosuffix', '--separator=";"']
513594

514595

515-
class VolumeGroup(object):
596+
class VolumeGroup(Lvm):
516597
"""
517598
Represents an LVM group, with some top-level attributes like ``vg_name``
518599
"""
@@ -523,18 +604,8 @@ def __init__(self, **kw: Any) -> None:
523604
self.vg_free_count: str = ''
524605
self.vg_extent_size: str = ''
525606
self.vg_extent_count: str = ''
526-
for k, v in kw.items():
527-
setattr(self, k, v)
528-
self.name = kw['vg_name']
529-
if not self.name:
530-
raise ValueError('VolumeGroup must have a non-empty name')
531-
self.tags = parse_tags(kw.get('vg_tags', ''))
532-
533-
def __str__(self) -> str:
534-
return '<%s>' % self.name
535-
536-
def __repr__(self) -> str:
537-
return self.__str__()
607+
super().__init__('vg_name', 'vg_tags', **kw)
608+
self.vg_api = kw
538609

539610
@property
540611
def free(self) -> int:
@@ -824,7 +895,7 @@ def get_all_devices_vgs(name_prefix: str = '') -> List[VolumeGroup]:
824895
'--units=b', '--nosuffix']
825896

826897

827-
class Volume:
898+
class Volume(Lvm):
828899
"""
829900
Represents a Logical Volume from LVM, with some top-level attributes like
830901
``lv_name`` and parsed tags as a dictionary of key/value pairs.
@@ -837,21 +908,15 @@ def __init__(self, **kw: Any) -> None:
837908
self.vg_name: str = ''
838909
self.lv_size: str = ''
839910
self.lv_tags: Dict[str, Any] = {}
840-
for k, v in kw.items():
841-
setattr(self, k, v)
911+
super().__init__('lv_name', 'lv_tags', **kw)
842912
self.lv_api = kw
843-
self.name = kw['lv_name']
844-
if not self.name:
845-
raise ValueError('Volume must have a non-empty name')
846-
self.tags = parse_tags(kw['lv_tags'])
847913
self.encrypted = self.tags.get('ceph.encrypted', '0') == '1'
848914
self.used_by_ceph = 'ceph.osd_id' in self.tags
915+
self.binary_change: str = 'lvchange'
916+
self.path: str = self.lv_path
849917

850918
def __str__(self) -> str:
851-
return '<%s>' % self.lv_api['lv_path']
852-
853-
def __repr__(self) -> str:
854-
return self.__str__()
919+
return f'<{self.api_data.get("lv_path", self.name)}>'
855920

856921
def as_dict(self) -> Dict[Any, Any]:
857922
obj: Dict[Any, Any] = {}
@@ -883,80 +948,6 @@ def report(self) -> Dict[str, Any]:
883948
report[type_uuid] = self.tags['ceph.{}'.format(type_uuid)]
884949
return report
885950

886-
def _format_tag_args(self, op: str, tags: Dict[str, Any]) -> List[str]:
887-
result: List[str] = []
888-
for k, v in tags.items():
889-
result.extend([op, f'{k}={v}'])
890-
return result
891-
892-
def clear_tags(self, keys: Optional[List[str]] = None) -> None:
893-
"""
894-
Removes all or passed tags from the Logical Volume.
895-
"""
896-
if not keys:
897-
keys = list(self.tags.keys())
898-
899-
del_tags = {k: self.tags[k] for k in keys if k in self.tags}
900-
if not del_tags:
901-
# nothing to clear
902-
return
903-
del_tag_args = self._format_tag_args('--deltag', del_tags)
904-
# --deltag returns successful even if the to be deleted tag is not set
905-
process.call(['lvchange'] + del_tag_args + [self.lv_path], run_on_host=True)
906-
for k in del_tags.keys():
907-
del self.tags[k]
908-
909-
910-
def set_tags(self, tags: Dict[str, Any]) -> None:
911-
"""
912-
:param tags: A dictionary of tag names and values, like::
913-
914-
{
915-
"ceph.osd_fsid": "aaa-fff-bbbb",
916-
"ceph.osd_id": "0"
917-
}
918-
919-
At the end of all modifications, the tags are refreshed to reflect
920-
LVM's most current view.
921-
"""
922-
self.clear_tags(list(tags.keys()))
923-
add_tag_args = self._format_tag_args('--addtag', tags)
924-
process.call(['lvchange'] + add_tag_args + [self.lv_path], run_on_host=True)
925-
for k, v in tags.items():
926-
self.tags[k] = v
927-
928-
929-
def clear_tag(self, key: str) -> None:
930-
if self.tags.get(key):
931-
current_value = self.tags[key]
932-
tag = "%s=%s" % (key, current_value)
933-
process.call(['lvchange', '--deltag', tag, self.lv_path], run_on_host=True)
934-
del self.tags[key]
935-
936-
937-
def set_tag(self, key: str, value: str) -> None:
938-
"""
939-
Set the key/value pair as an LVM tag.
940-
"""
941-
# remove it first if it exists
942-
self.clear_tag(key)
943-
944-
process.call(
945-
[
946-
'lvchange',
947-
'--addtag', '%s=%s' % (key, value), self.lv_path
948-
],
949-
run_on_host=True
950-
)
951-
self.tags[key] = value
952-
953-
def deactivate(self) -> None:
954-
"""
955-
Deactivate the LV by calling lvchange -an
956-
"""
957-
process.call(['lvchange', '-an', self.lv_path], run_on_host=True)
958-
959-
960951
def create_lv(name_prefix: str,
961952
uuid: str,
962953
vg: Optional[VolumeGroup] = None,

0 commit comments

Comments
 (0)