]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/api/lvm.py
b23466b7aa3cc53f315685e2059295cdad73ec65
2 API for CRUD lvm tag operations. Follows the Ceph LVM tag naming convention
3 that prefixes tags with ``ceph.`` and uses ``=`` for assignment, and provides
4 set of utilities for interacting with LVM.
9 from itertools
import repeat
10 from math
import floor
11 from ceph_volume
import process
, util
, conf
12 from ceph_volume
.exceptions
import SizeAllocationError
14 logger
= logging
.getLogger(__name__
)
17 def convert_filters_to_str(filters
):
19 Convert filter args from dictionary to following format -
20 filters={filter_name=filter_val,...}
26 for k
, v
in filters
.items():
27 filter_arg
+= k
+ '=' + v
+ ','
28 # get rid of extra comma at the end
29 filter_arg
= filter_arg
[:len(filter_arg
) - 1]
34 def convert_tags_to_str(tags
):
36 Convert tags from dictionary to following format -
37 tags={tag_name=tag_val,...}
43 for k
, v
in tags
.items():
44 tag_arg
+= k
+ '=' + v
+ ','
45 # get rid of extra comma at the end
46 tag_arg
= tag_arg
[:len(tag_arg
) - 1] + '}'
51 def make_filters_lvmcmd_ready(filters
, tags
):
53 Convert filters (including tags) from dictionary to following format -
54 filter_name=filter_val...,tags={tag_name=tag_val,...}
56 The command will look as follows =
57 lvs -S filter_name=filter_val...,tags={tag_name=tag_val,...}
59 filters
= convert_filters_to_str(filters
)
60 tags
= convert_tags_to_str(tags
)
63 return filters
+ ',' + tags
64 if filters
and not tags
:
66 if not filters
and tags
:
72 def _output_parser(output
, fields
):
74 Newer versions of LVM allow ``--reportformat=json``, but older versions,
75 like the one included in Xenial do not. LVM has the ability to filter and
76 format its output so we assume the output will be in a format this parser
77 can handle (using ';' as a delimiter)
79 :param fields: A string, possibly using ',' to group many items, as it
80 would be used on the CLI
81 :param output: The CLI output from the LVM call
83 field_items
= fields
.split(',')
86 # clear the leading/trailing whitespace
89 # remove the extra '"' in each field
90 line
= line
.replace('"', '')
92 # prevent moving forward with empty contents
96 # splitting on ';' because that is what the lvm call uses as
98 output_items
= [i
.strip() for i
in line
.split(';')]
99 # map the output to the fields
101 dict(zip(field_items
, output_items
))
107 def _splitname_parser(line
):
109 Parses the output from ``dmsetup splitname``, that should contain prefixes
110 (--nameprefixes) and set the separator to ";"
112 Output for /dev/mapper/vg-lv will usually look like::
114 DM_VG_NAME='/dev/mapper/vg';DM_LV_NAME='lv';DM_LV_LAYER=''
117 The ``VG_NAME`` will usually not be what other callers need (e.g. just 'vg'
118 in the example), so this utility will split ``/dev/mapper/`` out, so that
119 the actual volume group name is kept
121 :returns: dictionary with stripped prefixes
125 parts
= line
[0].split(';')
127 logger
.exception('Unable to parse mapper device: %s', line
)
131 part
= part
.replace("'", '')
132 key
, value
= part
.split('=')
133 if 'DM_VG_NAME' in key
:
134 value
= value
.split('/dev/mapper/')[-1]
135 key
= key
.split('DM_')[-1]
141 def sizing(device_size
, parts
=None, size
=None):
143 Calculate proper sizing to fully utilize the volume group in the most
144 efficient way possible. To prevent situations where LVM might accept
145 a percentage that is beyond the vg's capabilities, it will refuse with
146 an error when requesting a larger-than-possible parameter, in addition
147 to rounding down calculations.
149 A dictionary with different sizing parameters is returned, to make it
150 easier for others to choose what they need in order to create logical
153 >>> sizing(100, parts=2)
154 >>> {'parts': 2, 'percentages': 50, 'sizes': 50}
157 if parts
is not None and size
is not None:
159 "Cannot process sizing with both parts (%s) and size (%s)" % (parts
, size
)
162 if size
and size
> device_size
:
163 raise SizeAllocationError(size
, device_size
)
165 def get_percentage(parts
):
166 return int(floor(100 / float(parts
)))
168 if parts
is not None:
169 # Prevent parts being 0, falling back to 1 (100% usage)
171 percentages
= get_percentage(parts
)
174 parts
= int(device_size
/ size
) or 1
175 percentages
= get_percentage(parts
)
177 sizes
= device_size
/ parts
if parts
else int(floor(device_size
))
181 'percentages': percentages
,
182 'sizes': int(sizes
/1024/1024/1024),
186 def parse_tags(lv_tags
):
188 Return a dictionary mapping of all the tags associated with
189 a Volume from the comma-separated tags coming from the LVM API
193 "ceph.osd_fsid=aaa-fff-bbbb,ceph.osd_id=0"
195 For the above example, the expected return value would be::
198 "ceph.osd_fsid": "aaa-fff-bbbb",
205 tags
= lv_tags
.split(',')
206 for tag_assignment
in tags
:
207 if not tag_assignment
.startswith('ceph.'):
209 key
, value
= tag_assignment
.split('=', 1)
210 tag_mapping
[key
] = value
215 def _vdo_parents(devices
):
217 It is possible we didn't get a logical volume, or a mapper path, but
218 a device like /dev/sda2, to resolve this, we must look at all the slaves of
219 every single device in /sys/block and if any of those devices is related to
220 VDO devices, then we can add the parent
223 for parent
in os
.listdir('/sys/block'):
224 for slave
in os
.listdir('/sys/block/%s/slaves' % parent
):
226 parent_devices
.append('/dev/%s' % parent
)
227 parent_devices
.append(parent
)
228 return parent_devices
231 def _vdo_slaves(vdo_names
):
233 find all the slaves associated with each vdo name (from realpath) by going
234 into /sys/block/<realpath>/slaves
237 for vdo_name
in vdo_names
:
238 mapper_path
= '/dev/mapper/%s' % vdo_name
239 if not os
.path
.exists(mapper_path
):
241 # resolve the realpath and realname of the vdo mapper
242 vdo_realpath
= os
.path
.realpath(mapper_path
)
243 vdo_realname
= vdo_realpath
.split('/')[-1]
244 slaves_path
= '/sys/block/%s/slaves' % vdo_realname
245 if not os
.path
.exists(slaves_path
):
247 devices
.append(vdo_realpath
)
248 devices
.append(mapper_path
)
249 devices
.append(vdo_realname
)
250 for slave
in os
.listdir(slaves_path
):
251 devices
.append('/dev/%s' % slave
)
252 devices
.append(slave
)
258 A VDO device can be composed from many different devices, go through each
259 one of those devices and its slaves (if any) and correlate them back to
260 /dev/mapper and their realpaths, and then check if they appear as part of
261 /sys/kvdo/<name>/statistics
263 From the realpath of a logical volume, determine if it is a VDO device or
264 not, by correlating it to the presence of the name in
265 /sys/kvdo/<name>/statistics and all the previously captured devices
267 if not os
.path
.isdir('/sys/kvdo'):
269 realpath
= os
.path
.realpath(path
)
270 realpath_name
= realpath
.split('/')[-1]
273 # get all the vdo names
274 for dirname
in os
.listdir('/sys/kvdo/'):
275 if os
.path
.isdir('/sys/kvdo/%s/statistics' % dirname
):
276 vdo_names
.add(dirname
)
278 # find all the slaves associated with each vdo name (from realpath) by
279 # going into /sys/block/<realpath>/slaves
280 devices
.extend(_vdo_slaves(vdo_names
))
282 # Find all possible parents, looking into slaves that are related to VDO
283 devices
.extend(_vdo_parents(devices
))
288 realpath_name
in devices
])
293 Detect if a path is backed by VDO, proxying the actual call to _is_vdo so
294 that we can prevent an exception breaking OSD creation. If an exception is
295 raised, it will get captured and logged to file, while returning
303 logger
.exception('Unable to properly detect device as VDO: %s', path
)
307 def dmsetup_splitname(dev
):
309 Run ``dmsetup splitname`` and parse the results.
311 .. warning:: This call does not ensure that the device is correct or that
312 it exists. ``dmsetup`` will happily take a non existing path and still
313 return a 0 exit status.
316 'dmsetup', 'splitname', '--noheadings',
317 "--separator=';'", '--nameprefixes', dev
319 out
, err
, rc
= process
.call(command
)
320 return _splitname_parser(out
)
323 def is_ceph_device(lv
):
325 lv
.tags
['ceph.osd_id']
326 except (KeyError, AttributeError):
327 logger
.warning('device is not part of ceph: %s', lv
)
330 if lv
.tags
['ceph.osd_id'] == 'null':
336 ####################################
338 # Code for LVM Physical Volumes
340 ################################
342 PV_FIELDS
= 'pv_name,pv_tags,pv_uuid,vg_name,lv_uuid'
344 class PVolume(object):
346 Represents a Physical Volume from LVM, with some top-level attributes like
347 ``pv_name`` and parsed tags as a dictionary of key/value pairs.
350 def __init__(self
, **kw
):
351 for k
, v
in kw
.items():
354 self
.name
= kw
['pv_name']
355 self
.tags
= parse_tags(kw
['pv_tags'])
358 return '<%s>' % self
.pv_api
['pv_name']
361 return self
.__str
__()
363 def set_tags(self
, tags
):
365 :param tags: A dictionary of tag names and values, like::
368 "ceph.osd_fsid": "aaa-fff-bbbb",
372 At the end of all modifications, the tags are refreshed to reflect
373 LVM's most current view.
375 for k
, v
in tags
.items():
377 # after setting all the tags, refresh them for the current object, use the
378 # pv_* identifiers to filter because those shouldn't change
379 pv_object
= self
.get_single_pv(filter={'pv_name': self
.pv_name
,
380 'pv_uuid': self
.pv_uuid
})
383 raise RuntimeError('No PV was found.')
385 self
.tags
= pv_object
.tags
387 def set_tag(self
, key
, value
):
389 Set the key/value pair as an LVM tag. Does not "refresh" the values of
390 the current object for its tags. Meant to be a "fire and forget" type
393 **warning**: Altering tags on a PV has to be done ensuring that the
394 device is actually the one intended. ``pv_name`` is *not* a persistent
395 value, only ``pv_uuid`` is. Using ``pv_uuid`` is the best way to make
396 sure the device getting changed is the one needed.
398 # remove it first if it exists
399 if self
.tags
.get(key
):
400 current_value
= self
.tags
[key
]
401 tag
= "%s=%s" % (key
, current_value
)
402 process
.call(['pvchange', '--deltag', tag
, self
.pv_name
], run_on_host
=True)
407 '--addtag', '%s=%s' % (key
, value
), self
.pv_name
413 def create_pv(device
):
415 Create a physical volume from a device, useful when devices need to be later mapped
422 '--yes', # answer yes to any prompts
427 def remove_pv(pv_name
):
429 Removes a physical volume using a double `-f` to prevent prompts and fully
430 remove anything related to LVM. This is tremendously destructive, but so is all other actions
431 when zapping a device.
433 In the case where multiple PVs are found, it will ignore that fact and
434 continue with the removal, specifically in the case of messages like::
436 WARNING: PV $UUID /dev/DEV-1 was already found on /dev/DEV-2
438 These situations can be avoided with custom filtering rules, which this API
439 cannot handle while accommodating custom user filters.
441 fail_msg
= "Unable to remove vg %s" % pv_name
455 def get_pvs(fields
=PV_FIELDS
, filters
='', tags
=None):
457 Return a list of PVs that are available on the system and match the
458 filters and tags passed. Argument filters takes a dictionary containing
459 arguments required by -S option of LVM. Passing a list of LVM tags can be
460 quite tricky to pass as a dictionary within dictionary, therefore pass
461 dictionary of tags via tags argument and tricky part will be taken care of
462 by the helper methods.
464 :param fields: string containing list of fields to be displayed by the
466 :param sep: string containing separator to be used between two fields
467 :param filters: dictionary containing LVM filters
468 :param tags: dictionary containng LVM tags
469 :returns: list of class PVolume object representing pvs on the system
471 filters
= make_filters_lvmcmd_ready(filters
, tags
)
472 args
= ['pvs', '--noheadings', '--readonly', '--separator=";"', '-S',
473 filters
, '-o', fields
]
475 stdout
, stderr
, returncode
= process
.call(args
, run_on_host
=True, verbose_on_failure
=False)
476 pvs_report
= _output_parser(stdout
, fields
)
477 return [PVolume(**pv_report
) for pv_report
in pvs_report
]
480 def get_single_pv(fields
=PV_FIELDS
, filters
=None, tags
=None):
482 Wrapper of get_pvs() meant to be a convenience method to avoid the phrase::
487 pvs
= get_pvs(fields
=fields
, filters
=filters
, tags
=tags
)
492 raise RuntimeError('Filters {} matched more than 1 PV present on this host.'.format(str(filters
)))
497 ################################
499 # Code for LVM Volume Groups
501 #############################
503 VG_FIELDS
= 'vg_name,pv_count,lv_count,vg_attr,vg_extent_count,vg_free_count,vg_extent_size'
504 VG_CMD_OPTIONS
= ['--noheadings', '--readonly', '--units=b', '--nosuffix', '--separator=";"']
507 class VolumeGroup(object):
509 Represents an LVM group, with some top-level attributes like ``vg_name``
512 def __init__(self
, **kw
):
513 for k
, v
in kw
.items():
515 self
.name
= kw
['vg_name']
517 raise ValueError('VolumeGroup must have a non-empty name')
518 self
.tags
= parse_tags(kw
.get('vg_tags', ''))
521 return '<%s>' % self
.name
524 return self
.__str
__()
529 Return free space in VG in bytes
531 return int(self
.vg_extent_size
) * int(self
.vg_free_count
)
534 def free_percent(self
):
536 Return free space in VG in bytes
538 return int(self
.vg_free_count
) / int(self
.vg_extent_count
)
543 Returns VG size in bytes
545 return int(self
.vg_extent_size
) * int(self
.vg_extent_count
)
547 def sizing(self
, parts
=None, size
=None):
549 Calculate proper sizing to fully utilize the volume group in the most
550 efficient way possible. To prevent situations where LVM might accept
551 a percentage that is beyond the vg's capabilities, it will refuse with
552 an error when requesting a larger-than-possible parameter, in addition
553 to rounding down calculations.
555 A dictionary with different sizing parameters is returned, to make it
556 easier for others to choose what they need in order to create logical
561 >>> data_vg.sizing(parts=4)
562 {'parts': 4, 'sizes': 256, 'percentages': 25}
563 >>> data_vg.sizing(size=512)
564 {'parts': 2, 'sizes': 512, 'percentages': 50}
567 :param parts: Number of parts to create LVs from
568 :param size: Size in gigabytes to divide the VG into
570 :raises SizeAllocationError: When requested size cannot be allocated with
571 :raises ValueError: If both ``parts`` and ``size`` are given
573 if parts
is not None and size
is not None:
575 "Cannot process sizing with both parts (%s) and size (%s)" % (parts
, size
)
578 # if size is given we need to map that to extents so that we avoid
579 # issues when trying to get this right with a size in gigabytes find
580 # the percentage first, cheating, because these values are thrown out
581 vg_free_count
= util
.str_to_int(self
.vg_free_count
)
584 size
= size
* 1024 * 1024 * 1024
585 extents
= int(size
/ int(self
.vg_extent_size
))
586 disk_sizing
= sizing(self
.free
, size
=size
, parts
=parts
)
588 if parts
is not None:
589 # Prevent parts being 0, falling back to 1 (100% usage)
591 size
= int(self
.free
/ parts
)
592 extents
= size
* vg_free_count
/ self
.free
593 disk_sizing
= sizing(self
.free
, parts
=parts
)
595 extent_sizing
= sizing(vg_free_count
, size
=extents
)
597 disk_sizing
['extents'] = int(extents
)
598 disk_sizing
['percentages'] = extent_sizing
['percentages']
601 def bytes_to_extents(self
, size
):
603 Return a how many free extents we can fit into a size in bytes. This has
604 some uncertainty involved. If size/extent_size is within 1% of the
605 actual free extents we will return the extent count, otherwise we'll
607 This accomodates for the size calculation in batch. We need to report
608 the OSD layout but have not yet created any LVM structures. We use the
609 disk size in batch if no VG is present and that will overshoot the
610 actual free_extent count due to LVM overhead.
613 b_to_ext
= int(size
/ int(self
.vg_extent_size
))
614 if b_to_ext
< int(self
.vg_free_count
):
615 # return bytes in extents if there is more space
617 elif b_to_ext
/ int(self
.vg_free_count
) - 1 < 0.01:
618 # return vg_fre_count if its less then 1% off
620 'bytes_to_extents results in {} but only {} '
621 'are available, adjusting the latter'.format(b_to_ext
,
623 return int(self
.vg_free_count
)
624 # else raise an exception
625 raise RuntimeError('Can\'t convert {} to free extents, only {} ({} '
626 'bytes) are free'.format(size
, self
.vg_free_count
,
629 def slots_to_extents(self
, slots
):
631 Return how many extents fit the VG slot times
633 return int(int(self
.vg_extent_count
) / slots
)
636 def create_vg(devices
, name
=None, name_prefix
=None):
638 Create a Volume Group. Command looks like::
640 vgcreate --force --yes group_name device
642 Once created the volume group is returned as a ``VolumeGroup`` object
644 :param devices: A list of devices to create a VG. Optionally, a single
645 device (as a string) can be used.
646 :param name: Optionally set the name of the VG, defaults to 'ceph-{uuid}'
647 :param name_prefix: Optionally prefix the name of the VG, which will get combined
650 if isinstance(devices
, set):
651 devices
= list(devices
)
652 if not isinstance(devices
, list):
655 name
= "%s-%s" % (name_prefix
, str(uuid
.uuid4()))
657 name
= "ceph-%s" % str(uuid
.uuid4())
666 return get_single_vg(filters
={'vg_name': name
})
669 def extend_vg(vg
, devices
):
671 Extend a Volume Group. Command looks like::
673 vgextend --force --yes group_name [device, ...]
675 Once created the volume group is extended and returned as a ``VolumeGroup`` object
677 :param vg: A VolumeGroup object
678 :param devices: A list of devices to extend the VG. Optionally, a single
679 device (as a string) can be used.
681 if not isinstance(devices
, list):
691 return get_single_vg(filters
={'vg_name': vg
.name
})
694 def reduce_vg(vg
, devices
):
696 Reduce a Volume Group. Command looks like::
698 vgreduce --force --yes group_name [device, ...]
700 :param vg: A VolumeGroup object
701 :param devices: A list of devices to remove from the VG. Optionally, a
702 single device (as a string) can be used.
704 if not isinstance(devices
, list):
714 return get_single_vg(filter={'vg_name': vg
.name
})
717 def remove_vg(vg_name
):
719 Removes a volume group.
722 logger
.warning('Skipping removal of invalid VG name: "%s"', vg_name
)
724 fail_msg
= "Unable to remove vg %s" % vg_name
737 def get_vgs(fields
=VG_FIELDS
, filters
='', tags
=None):
739 Return a list of VGs that are available on the system and match the
740 filters and tags passed. Argument filters takes a dictionary containing
741 arguments required by -S option of LVM. Passing a list of LVM tags can be
742 quite tricky to pass as a dictionary within dictionary, therefore pass
743 dictionary of tags via tags argument and tricky part will be taken care of
744 by the helper methods.
746 :param fields: string containing list of fields to be displayed by the
748 :param sep: string containing separator to be used between two fields
749 :param filters: dictionary containing LVM filters
750 :param tags: dictionary containng LVM tags
751 :returns: list of class VolumeGroup object representing vgs on the system
753 filters
= make_filters_lvmcmd_ready(filters
, tags
)
754 args
= ['vgs'] + VG_CMD_OPTIONS
+ ['-S', filters
, '-o', fields
]
756 stdout
, stderr
, returncode
= process
.call(args
, run_on_host
=True, verbose_on_failure
=False)
757 vgs_report
=_output_parser(stdout
, fields
)
758 return [VolumeGroup(**vg_report
) for vg_report
in vgs_report
]
761 def get_single_vg(fields
=VG_FIELDS
, filters
=None, tags
=None):
763 Wrapper of get_vgs() meant to be a convenience method to avoid the phrase::
768 vgs
= get_vgs(fields
=fields
, filters
=filters
, tags
=tags
)
773 raise RuntimeError('Filters {} matched more than 1 VG present on this host.'.format(str(filters
)))
778 def get_device_vgs(device
, name_prefix
=''):
779 stdout
, stderr
, returncode
= process
.call(
780 ['pvs'] + VG_CMD_OPTIONS
+ ['-o', VG_FIELDS
, device
],
782 verbose_on_failure
=False
784 vgs
= _output_parser(stdout
, VG_FIELDS
)
785 return [VolumeGroup(**vg
) for vg
in vgs
if vg
['vg_name'] and vg
['vg_name'].startswith(name_prefix
)]
788 def get_all_devices_vgs(name_prefix
=''):
789 vg_fields
= f
'pv_name,{VG_FIELDS}'
790 cmd
= ['pvs'] + VG_CMD_OPTIONS
+ ['-o', vg_fields
]
791 stdout
, stderr
, returncode
= process
.call(
794 verbose_on_failure
=False
796 vgs
= _output_parser(stdout
, vg_fields
)
797 return [VolumeGroup(**vg
) for vg
in vgs
]
799 #################################
801 # Code for LVM Logical Volumes
803 ###############################
805 LV_FIELDS
= 'lv_tags,lv_path,lv_name,vg_name,lv_uuid,lv_size'
806 LV_CMD_OPTIONS
= ['--noheadings', '--readonly', '--separator=";"', '-a',
807 '--units=b', '--nosuffix']
810 class Volume(object):
812 Represents a Logical Volume from LVM, with some top-level attributes like
813 ``lv_name`` and parsed tags as a dictionary of key/value pairs.
816 def __init__(self
, **kw
):
817 for k
, v
in kw
.items():
820 self
.name
= kw
['lv_name']
822 raise ValueError('Volume must have a non-empty name')
823 self
.tags
= parse_tags(kw
['lv_tags'])
824 self
.encrypted
= self
.tags
.get('ceph.encrypted', '0') == '1'
825 self
.used_by_ceph
= 'ceph.osd_id' in self
.tags
828 return '<%s>' % self
.lv_api
['lv_path']
831 return self
.__str
__()
835 obj
.update(self
.lv_api
)
836 obj
['tags'] = self
.tags
837 obj
['name'] = self
.name
838 obj
['type'] = self
.tags
['ceph.type']
839 obj
['path'] = self
.lv_path
843 if not self
.used_by_ceph
:
845 'name': self
.lv_name
,
846 'comment': 'not used by ceph'
849 type_
= self
.tags
['ceph.type']
851 'name': self
.lv_name
,
852 'osd_id': self
.tags
['ceph.osd_id'],
853 'cluster_name': self
.tags
.get('ceph.cluster_name', conf
.cluster
),
855 'osd_fsid': self
.tags
['ceph.osd_fsid'],
856 'cluster_fsid': self
.tags
['ceph.cluster_fsid'],
857 'osdspec_affinity': self
.tags
.get('ceph.osdspec_affinity', ''),
859 type_uuid
= '{}_uuid'.format(type_
)
860 report
[type_uuid
] = self
.tags
['ceph.{}'.format(type_uuid
)]
863 def _format_tag_args(self
, op
, tags
):
864 tag_args
= ['{}={}'.format(k
, v
) for k
, v
in tags
.items()]
865 # weird but efficient way of ziping two lists and getting a flat list
866 return list(sum(zip(repeat(op
), tag_args
), ()))
868 def clear_tags(self
, keys
=None):
870 Removes all or passed tags from the Logical Volume.
873 keys
= self
.tags
.keys()
875 del_tags
= {k
: self
.tags
[k
] for k
in keys
if k
in self
.tags
}
879 del_tag_args
= self
._format
_tag
_args
('--deltag', del_tags
)
880 # --deltag returns successful even if the to be deleted tag is not set
881 process
.call(['lvchange'] + del_tag_args
+ [self
.lv_path
], run_on_host
=True)
882 for k
in del_tags
.keys():
886 def set_tags(self
, tags
):
888 :param tags: A dictionary of tag names and values, like::
891 "ceph.osd_fsid": "aaa-fff-bbbb",
895 At the end of all modifications, the tags are refreshed to reflect
896 LVM's most current view.
898 self
.clear_tags(tags
.keys())
899 add_tag_args
= self
._format
_tag
_args
('--addtag', tags
)
900 process
.call(['lvchange'] + add_tag_args
+ [self
.lv_path
], run_on_host
=True)
901 for k
, v
in tags
.items():
905 def clear_tag(self
, key
):
906 if self
.tags
.get(key
):
907 current_value
= self
.tags
[key
]
908 tag
= "%s=%s" % (key
, current_value
)
909 process
.call(['lvchange', '--deltag', tag
, self
.lv_path
], run_on_host
=True)
913 def set_tag(self
, key
, value
):
915 Set the key/value pair as an LVM tag.
917 # remove it first if it exists
923 '--addtag', '%s=%s' % (key
, value
), self
.lv_path
927 self
.tags
[key
] = value
929 def deactivate(self
):
931 Deactivate the LV by calling lvchange -an
933 process
.call(['lvchange', '-an', self
.lv_path
], run_on_host
=True)
936 def create_lv(name_prefix
,
945 Create a Logical Volume in a Volume Group. Command looks like::
947 lvcreate -L 50G -n gfslv vg0
949 ``name_prefix`` is required. If ``size`` is provided its expected to be a
950 byte count. Tags are an optional dictionary and is expected to
951 conform to the convention of prefixing them with "ceph." like::
953 {"ceph.block_device": "/dev/ceph/osd-1"}
955 :param name_prefix: name prefix for the LV, typically somehting like ceph-osd-block
956 :param uuid: UUID to ensure uniqueness; is combined with name_prefix to
958 :param vg: optional, pass an existing VG to create LV
959 :param device: optional, device to use. Either device of vg must be passed
960 :param slots: optional, number of slots to divide vg up, LV will occupy one
961 one slot if enough space is available
962 :param extends: optional, how many lvm extends to use, supersedes slots
963 :param size: optional, target LV size in bytes, supersedes extents,
964 resulting LV might be smaller depending on extent
965 size of the underlying VG
966 :param tags: optional, a dict of lvm tags to set on the LV
968 name
= '{}-{}'.format(name_prefix
, uuid
)
971 raise RuntimeError("Must either specify vg or device, none given")
972 # check if a vgs starting with ceph already exists
973 vgs
= get_device_vgs(device
, 'ceph')
978 vg
= create_vg(device
, name_prefix
='ceph')
982 extents
= vg
.bytes_to_extents(size
)
983 logger
.debug('size was passed: {} -> {}'.format(size
, extents
))
984 elif slots
and not extents
:
985 extents
= vg
.slots_to_extents(slots
)
986 logger
.debug('slots was passed: {} -> {}'.format(slots
, extents
))
993 '{}'.format(extents
),
994 '-n', name
, vg
.vg_name
996 # create the lv with all the space available, this is needed because the
997 # system call is different for LVM
1004 '-n', name
, vg
.vg_name
1006 process
.run(command
, run_on_host
=True)
1008 lv
= get_single_lv(filters
={'lv_name': name
, 'vg_name': vg
.vg_name
})
1012 "ceph.osd_id": "null",
1013 "ceph.type": "null",
1014 "ceph.cluster_fsid": "null",
1015 "ceph.osd_fsid": "null",
1017 # when creating a distinct type, the caller doesn't know what the path will
1018 # be so this function will set it after creation using the mapping
1019 # XXX add CEPH_VOLUME_LVM_DEBUG to enable -vvvv on lv operations
1021 'journal': 'ceph.journal_device',
1022 'data': 'ceph.data_device',
1023 'block': 'ceph.block_device',
1024 'wal': 'ceph.wal_device',
1025 'db': 'ceph.db_device',
1026 'lockbox': 'ceph.lockbox_device', # XXX might not ever need this lockbox sorcery
1028 path_tag
= type_path_tag
.get(tags
.get('ceph.type'))
1030 tags
.update({path_tag
: lv
.lv_path
})
1037 def create_lvs(volume_group
, parts
=None, size
=None, name_prefix
='ceph-lv'):
1039 Create multiple Logical Volumes from a Volume Group by calculating the
1040 proper extents from ``parts`` or ``size``. A custom prefix can be used
1041 (defaults to ``ceph-lv``), these names are always suffixed with a uuid.
1043 LV creation in ceph-volume will require tags, this is expected to be
1044 pre-computed by callers who know Ceph metadata like OSD IDs and FSIDs. It
1045 will probably not be the case when mass-creating LVs, so common/default
1046 tags will be set to ``"null"``.
1048 .. note:: LVs that are not in use can be detected by querying LVM for tags that are
1051 :param volume_group: The volume group (vg) to use for LV creation
1052 :type group: ``VolumeGroup()`` object
1053 :param parts: Number of LVs to create *instead of* ``size``.
1055 :param size: Size (in gigabytes) of LVs to create, e.g. "as many 10gb LVs as possible"
1057 :param extents: The number of LVM extents to use to create the LV. Useful if looking to have
1058 accurate LV sizes (LVM rounds sizes otherwise)
1060 if parts
is None and size
is None:
1061 # fallback to just one part (using 100% of the vg)
1065 "ceph.osd_id": "null",
1066 "ceph.type": "null",
1067 "ceph.cluster_fsid": "null",
1068 "ceph.osd_fsid": "null",
1070 sizing
= volume_group
.sizing(parts
=parts
, size
=size
)
1071 for part
in range(0, sizing
['parts']):
1072 size
= sizing
['sizes']
1073 extents
= sizing
['extents']
1075 create_lv(name_prefix
, uuid
.uuid4(), vg
=volume_group
, extents
=extents
, tags
=tags
)
1082 Removes a logical volume given it's absolute path.
1084 Will return True if the lv is successfully removed or
1085 raises a RuntimeError if the removal fails.
1087 :param lv: A ``Volume`` object or the path for an LV
1089 if isinstance(lv
, Volume
):
1094 stdout
, stderr
, returncode
= process
.call(
1103 terminal_verbose
=True,
1106 raise RuntimeError("Unable to remove %s" % path
)
1110 def get_lvs(fields
=LV_FIELDS
, filters
='', tags
=None):
1112 Return a list of LVs that are available on the system and match the
1113 filters and tags passed. Argument filters takes a dictionary containing
1114 arguments required by -S option of LVM. Passing a list of LVM tags can be
1115 quite tricky to pass as a dictionary within dictionary, therefore pass
1116 dictionary of tags via tags argument and tricky part will be taken care of
1117 by the helper methods.
1119 :param fields: string containing list of fields to be displayed by the
1121 :param sep: string containing separator to be used between two fields
1122 :param filters: dictionary containing LVM filters
1123 :param tags: dictionary containng LVM tags
1124 :returns: list of class Volume object representing LVs on the system
1126 filters
= make_filters_lvmcmd_ready(filters
, tags
)
1127 args
= ['lvs'] + LV_CMD_OPTIONS
+ ['-S', filters
, '-o', fields
]
1129 stdout
, stderr
, returncode
= process
.call(args
, run_on_host
=True, verbose_on_failure
=False)
1130 lvs_report
= _output_parser(stdout
, fields
)
1131 return [Volume(**lv_report
) for lv_report
in lvs_report
]
1134 def get_single_lv(fields
=LV_FIELDS
, filters
=None, tags
=None):
1136 Wrapper of get_lvs() meant to be a convenience method to avoid the phrase::
1141 lvs
= get_lvs(fields
=fields
, filters
=filters
, tags
=tags
)
1146 raise RuntimeError('Filters {} matched more than 1 LV present on this host.'.format(str(filters
)))
1151 def get_lvs_from_osd_id(osd_id
):
1152 return get_lvs(tags
={'ceph.osd_id': osd_id
})
1155 def get_single_lv_from_osd_id(osd_id
):
1156 return get_single_lv(tags
={'ceph.osd_id': osd_id
})
1159 def get_lv_by_name(name
):
1160 stdout
, stderr
, returncode
= process
.call(
1161 ['lvs', '--noheadings', '-o', LV_FIELDS
, '-S',
1162 'lv_name={}'.format(name
)],
1164 verbose_on_failure
=False
1166 lvs
= _output_parser(stdout
, LV_FIELDS
)
1167 return [Volume(**lv
) for lv
in lvs
]
1170 def get_lvs_by_tag(lv_tag
):
1171 stdout
, stderr
, returncode
= process
.call(
1172 ['lvs', '--noheadings', '--separator=";"', '-a', '-o', LV_FIELDS
, '-S',
1173 'lv_tags={{{}}}'.format(lv_tag
)],
1175 verbose_on_failure
=False
1177 lvs
= _output_parser(stdout
, LV_FIELDS
)
1178 return [Volume(**lv
) for lv
in lvs
]
1181 def get_device_lvs(device
, name_prefix
=''):
1182 stdout
, stderr
, returncode
= process
.call(
1183 ['pvs'] + LV_CMD_OPTIONS
+ ['-o', LV_FIELDS
, device
],
1185 verbose_on_failure
=False
1187 lvs
= _output_parser(stdout
, LV_FIELDS
)
1188 return [Volume(**lv
) for lv
in lvs
if lv
['lv_name'] and
1189 lv
['lv_name'].startswith(name_prefix
)]
1191 def get_lvs_from_path(devpath
):
1193 if os
.path
.isabs(devpath
):
1194 # we have a block device
1195 lvs
= get_device_lvs(devpath
)
1197 # maybe this was a LV path /dev/vg_name/lv_name or /dev/mapper/
1198 lvs
= get_lvs(filters
={'path': devpath
})
1202 def get_lv_by_fullname(full_name
):
1204 returns LV by the specified LV's full name (formatted as vg_name/lv_name)
1207 vg_name
, lv_name
= full_name
.split('/')
1208 res_lv
= get_single_lv(filters
={'lv_name': lv_name
,
1209 'vg_name': vg_name
})