66import logging
77import os
88import uuid
9- from itertools import repeat
109from math import floor
1110from ceph_volume import process , util , conf
1211from ceph_volume .exceptions import SizeAllocationError
@@ -323,17 +322,103 @@ def dmsetup_splitname(dev: str) -> Dict[str, Any]:
323322
324323
325324def is_ceph_device (lv : "Volume" ) -> bool :
326- try :
327- lv .tags ['ceph.osd_id' ]
328- except (KeyError , AttributeError ):
329- logger .warning ('device is not part of ceph: %s' , lv )
330- return False
325+ osd_id = lv .tags .get ('ceph.osd_id' , 'null' )
331326
332- if lv .tags ['ceph.osd_id' ] == 'null' :
333- return False
334- else :
335- return True
327+ if osd_id == 'null' :
328+ logger .warning ('device is not part of ceph: %s' , lv )
329+ return False
330+
331+ return True
336332
333+ class Lvm :
334+ def __init__ (self , name_key : str , tags_key : str , ** kw : Any ) -> None :
335+ self .name : str = kw .get (name_key , '' )
336+ self .binary_change : str = ''
337+ self .path : str = ''
338+ if not self .name :
339+ raise ValueError (f'{ self .__class__ .__name__ } must have a non-empty name' )
340+
341+ self .api_data = kw
342+ self .tags = parse_tags (kw .get (tags_key , '' ))
343+
344+ for k , v in kw .items ():
345+ setattr (self , k , v )
346+
347+ def __str__ (self ) -> str :
348+ return f'<{ self .name } >'
349+
350+ def __repr__ (self ) -> str :
351+ return self .__str__ ()
352+
353+ def _format_tag_args (self , op : str , tags : Dict [str , Any ]) -> List [str ]:
354+ result : List [str ] = []
355+ for k , v in tags .items ():
356+ result .extend ([op , f'{ k } ={ v } ' ])
357+ return result
358+
359+ def clear_tags (self , keys : Optional [List [str ]] = None ) -> None :
360+ """
361+ Removes all or passed tags.
362+ """
363+ if not keys :
364+ keys = list (self .tags .keys ())
365+
366+ del_tags = {k : self .tags [k ] for k in keys if k in self .tags }
367+ if not del_tags :
368+ # nothing to clear
369+ return
370+ del_tag_args = self ._format_tag_args ('--deltag' , del_tags )
371+ # --deltag returns successful even if the to be deleted tag is not set
372+ process .call ([self .binary_change ] + del_tag_args + [self .path ], run_on_host = True )
373+ for k in del_tags .keys ():
374+ del self .tags [k ]
375+
376+ def clear_tag (self , key : str ) -> None :
377+ if self .tags .get (key ):
378+ current_value = self .tags [key ]
379+ tag = "%s=%s" % (key , current_value )
380+ process .call ([self .binary_change , '--deltag' , tag , self .path ], run_on_host = True )
381+ del self .tags [key ]
382+
383+ def set_tag (self , key : str , value : str ) -> None :
384+ """
385+ Set the key/value pair as an LVM tag.
386+ """
387+ # remove it first if it exists
388+ self .clear_tag (key )
389+
390+ process .call (
391+ [
392+ self .binary_change ,
393+ '--addtag' , '%s=%s' % (key , value ), self .path
394+ ],
395+ run_on_host = True
396+ )
397+ self .tags [key ] = value
398+
399+ def set_tags (self , tags : Dict [str , Any ]) -> None :
400+ """
401+ :param tags: A dictionary of tag names and values, like::
402+
403+ {
404+ "ceph.osd_fsid": "aaa-fff-bbbb",
405+ "ceph.osd_id": "0"
406+ }
407+
408+ At the end of all modifications, the tags are refreshed to reflect
409+ LVM's most current view.
410+ """
411+ self .clear_tags (list (tags .keys ()))
412+ add_tag_args = self ._format_tag_args ('--addtag' , tags )
413+ process .call ([self .binary_change ] + add_tag_args + [self .path ], run_on_host = True )
414+ for k , v in tags .items ():
415+ self .tags [k ] = v
416+
417+ def deactivate (self ) -> None :
418+ """
419+ Deactivate the LV by calling lvchange -an
420+ """
421+ process .call ([self .binary_change , '-an' , self .path ], run_on_host = True )
337422
338423####################################
339424#
@@ -343,7 +428,7 @@ def is_ceph_device(lv: "Volume") -> bool:
343428
344429PV_FIELDS = 'pv_name,pv_tags,pv_uuid,vg_name,lv_uuid'
345430
346- class PVolume :
431+ class PVolume ( Lvm ) :
347432 """
348433 Represents a Physical Volume from LVM, with some top-level attributes like
349434 ``pv_name`` and parsed tags as a dictionary of key/value pairs.
@@ -352,18 +437,10 @@ class PVolume:
352437 def __init__ (self , ** kw : Any ) -> None :
353438 self .pv_name : str = ''
354439 self .pv_uuid : str = ''
355- self .lv_uuid : str = ''
356- for k , v in kw .items ():
357- setattr (self , k , v )
440+ super ().__init__ ('pv_name' , 'pv_tags' , ** kw )
358441 self .pv_api = kw
359- self .name = kw ['pv_name' ]
360- self .tags = parse_tags (kw ['pv_tags' ])
361-
362- def __str__ (self ) -> str :
363- return '<%s>' % self .pv_api ['pv_name' ]
364-
365- def __repr__ (self ) -> str :
366- return self .__str__ ()
442+ self .binary_change : str = 'pvchange'
443+ self .path : str = self .pv_name
367444
368445 def set_tags (self , tags : Dict [str , Any ]) -> None :
369446 """
@@ -513,7 +590,7 @@ def get_single_pv(fields: str = PV_FIELDS, filters: Optional[Dict[str, Any]] = N
513590VG_CMD_OPTIONS = ['--noheadings' , '--readonly' , '--units=b' , '--nosuffix' , '--separator=";"' ]
514591
515592
516- class VolumeGroup (object ):
593+ class VolumeGroup (Lvm ):
517594 """
518595 Represents an LVM group, with some top-level attributes like ``vg_name``
519596 """
@@ -524,18 +601,8 @@ def __init__(self, **kw: Any) -> None:
524601 self .vg_free_count : str = ''
525602 self .vg_extent_size : str = ''
526603 self .vg_extent_count : str = ''
527- for k , v in kw .items ():
528- setattr (self , k , v )
529- self .name = kw ['vg_name' ]
530- if not self .name :
531- raise ValueError ('VolumeGroup must have a non-empty name' )
532- self .tags = parse_tags (kw .get ('vg_tags' , '' ))
533-
534- def __str__ (self ) -> str :
535- return '<%s>' % self .name
536-
537- def __repr__ (self ) -> str :
538- return self .__str__ ()
604+ super ().__init__ ('vg_name' , 'vg_tags' , ** kw )
605+ self .vg_api = kw
539606
540607 @property
541608 def free (self ) -> int :
@@ -825,7 +892,7 @@ def get_all_devices_vgs(name_prefix: str = '') -> List[VolumeGroup]:
825892 '--units=b' , '--nosuffix' ]
826893
827894
828- class Volume :
895+ class Volume ( Lvm ) :
829896 """
830897 Represents a Logical Volume from LVM, with some top-level attributes like
831898 ``lv_name`` and parsed tags as a dictionary of key/value pairs.
@@ -837,22 +904,17 @@ def __init__(self, **kw: Any) -> None:
837904 self .lv_uuid : str = ''
838905 self .vg_name : str = ''
839906 self .lv_size : str = ''
907+ self .tags : Dict [str , Any ] = {}
840908 self .lv_tags : Dict [str , Any ] = {}
841- for k , v in kw .items ():
842- setattr (self , k , v )
909+ super ().__init__ ('lv_name' , 'lv_tags' , ** kw )
843910 self .lv_api = kw
844- self .name = kw ['lv_name' ]
845- if not self .name :
846- raise ValueError ('Volume must have a non-empty name' )
847- self .tags = parse_tags (kw ['lv_tags' ])
848911 self .encrypted = self .tags .get ('ceph.encrypted' , '0' ) == '1'
849912 self .used_by_ceph = 'ceph.osd_id' in self .tags
913+ self .binary_change : str = 'lvchange'
914+ self .path : str = self .lv_path
850915
851916 def __str__ (self ) -> str :
852- return '<%s>' % self .lv_api ['lv_path' ]
853-
854- def __repr__ (self ) -> str :
855- return self .__str__ ()
917+ return f'<{ self .api_data .get ("lv_path" , self .name )} >'
856918
857919 def as_dict (self ) -> Dict [Any , Any ]:
858920 obj : Dict [Any , Any ] = {}
@@ -884,79 +946,6 @@ def report(self) -> Dict[str, Any]:
884946 report [type_uuid ] = self .tags ['ceph.{}' .format (type_uuid )]
885947 return report
886948
887- def _format_tag_args (self , op : str , tags : Dict [str , Any ]) -> List [str ]:
888- tag_args = ['{}={}' .format (k , v ) for k , v in tags .items ()]
889- # weird but efficient way of ziping two lists and getting a flat list
890- return list (sum (zip (repeat (op ), tag_args ), ()))
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-
960949def create_lv (name_prefix : str ,
961950 uuid : str ,
962951 vg : Optional [VolumeGroup ] = None ,
0 commit comments