]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/api/lvm.py
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
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_first_pv(filter={'pv_name': self
.pv_name
,
380 'pv_uuid': self
.pv_uuid
})
381 self
.tags
= pv_object
.tags
383 def set_tag(self
, key
, value
):
385 Set the key/value pair as an LVM tag. Does not "refresh" the values of
386 the current object for its tags. Meant to be a "fire and forget" type
389 **warning**: Altering tags on a PV has to be done ensuring that the
390 device is actually the one intended. ``pv_name`` is *not* a persistent
391 value, only ``pv_uuid`` is. Using ``pv_uuid`` is the best way to make
392 sure the device getting changed is the one needed.
394 # remove it first if it exists
395 if self
.tags
.get(key
):
396 current_value
= self
.tags
[key
]
397 tag
= "%s=%s" % (key
, current_value
)
398 process
.call(['pvchange', '--deltag', tag
, self
.pv_name
])
403 '--addtag', '%s=%s' % (key
, value
), self
.pv_name
408 def create_pv(device
):
410 Create a physical volume from a device, useful when devices need to be later mapped
417 '--yes', # answer yes to any prompts
422 def remove_pv(pv_name
):
424 Removes a physical volume using a double `-f` to prevent prompts and fully
425 remove anything related to LVM. This is tremendously destructive, but so is all other actions
426 when zapping a device.
428 In the case where multiple PVs are found, it will ignore that fact and
429 continue with the removal, specifically in the case of messages like::
431 WARNING: PV $UUID /dev/DEV-1 was already found on /dev/DEV-2
433 These situations can be avoided with custom filtering rules, which this API
434 cannot handle while accommodating custom user filters.
436 fail_msg
= "Unable to remove vg %s" % pv_name
449 def get_pvs(fields
=PV_FIELDS
, filters
='', tags
=None):
451 Return a list of PVs that are available on the system and match the
452 filters and tags passed. Argument filters takes a dictionary containing
453 arguments required by -S option of LVM. Passing a list of LVM tags can be
454 quite tricky to pass as a dictionary within dictionary, therefore pass
455 dictionary of tags via tags argument and tricky part will be taken care of
456 by the helper methods.
458 :param fields: string containing list of fields to be displayed by the
460 :param sep: string containing separator to be used between two fields
461 :param filters: dictionary containing LVM filters
462 :param tags: dictionary containng LVM tags
463 :returns: list of class PVolume object representing pvs on the system
465 filters
= make_filters_lvmcmd_ready(filters
, tags
)
466 args
= ['pvs', '--noheadings', '--readonly', '--separator=";"', '-S',
467 filters
, '-o', fields
]
469 stdout
, stderr
, returncode
= process
.call(args
, verbose_on_failure
=False)
470 pvs_report
= _output_parser(stdout
, fields
)
471 return [PVolume(**pv_report
) for pv_report
in pvs_report
]
474 def get_first_pv(fields
=PV_FIELDS
, filters
=None, tags
=None):
476 Wrapper of get_pv meant to be a convenience method to avoid the phrase::
481 pvs
= get_pvs(fields
=fields
, filters
=filters
, tags
=tags
)
482 return pvs
[0] if len(pvs
) > 0 else []
485 ################################
487 # Code for LVM Volume Groups
489 #############################
491 VG_FIELDS
= 'vg_name,pv_count,lv_count,vg_attr,vg_extent_count,vg_free_count,vg_extent_size'
492 VG_CMD_OPTIONS
= ['--noheadings', '--readonly', '--units=b', '--nosuffix', '--separator=";"']
495 class VolumeGroup(object):
497 Represents an LVM group, with some top-level attributes like ``vg_name``
500 def __init__(self
, **kw
):
501 for k
, v
in kw
.items():
503 self
.name
= kw
['vg_name']
505 raise ValueError('VolumeGroup must have a non-empty name')
506 self
.tags
= parse_tags(kw
.get('vg_tags', ''))
509 return '<%s>' % self
.name
512 return self
.__str
__()
517 Return free space in VG in bytes
519 return int(self
.vg_extent_size
) * int(self
.vg_free_count
)
522 def free_percent(self
):
524 Return free space in VG in bytes
526 return int(self
.vg_free_count
) / int(self
.vg_extent_count
)
531 Returns VG size in bytes
533 return int(self
.vg_extent_size
) * int(self
.vg_extent_count
)
535 def sizing(self
, parts
=None, size
=None):
537 Calculate proper sizing to fully utilize the volume group in the most
538 efficient way possible. To prevent situations where LVM might accept
539 a percentage that is beyond the vg's capabilities, it will refuse with
540 an error when requesting a larger-than-possible parameter, in addition
541 to rounding down calculations.
543 A dictionary with different sizing parameters is returned, to make it
544 easier for others to choose what they need in order to create logical
549 >>> data_vg.sizing(parts=4)
550 {'parts': 4, 'sizes': 256, 'percentages': 25}
551 >>> data_vg.sizing(size=512)
552 {'parts': 2, 'sizes': 512, 'percentages': 50}
555 :param parts: Number of parts to create LVs from
556 :param size: Size in gigabytes to divide the VG into
558 :raises SizeAllocationError: When requested size cannot be allocated with
559 :raises ValueError: If both ``parts`` and ``size`` are given
561 if parts
is not None and size
is not None:
563 "Cannot process sizing with both parts (%s) and size (%s)" % (parts
, size
)
566 # if size is given we need to map that to extents so that we avoid
567 # issues when trying to get this right with a size in gigabytes find
568 # the percentage first, cheating, because these values are thrown out
569 vg_free_count
= util
.str_to_int(self
.vg_free_count
)
572 size
= size
* 1024 * 1024 * 1024
573 extents
= int(size
/ int(self
.vg_extent_size
))
574 disk_sizing
= sizing(self
.free
, size
=size
, parts
=parts
)
576 if parts
is not None:
577 # Prevent parts being 0, falling back to 1 (100% usage)
579 size
= int(self
.free
/ parts
)
580 extents
= size
* vg_free_count
/ self
.free
581 disk_sizing
= sizing(self
.free
, parts
=parts
)
583 extent_sizing
= sizing(vg_free_count
, size
=extents
)
585 disk_sizing
['extents'] = int(extents
)
586 disk_sizing
['percentages'] = extent_sizing
['percentages']
589 def bytes_to_extents(self
, size
):
591 Return a how many free extents we can fit into a size in bytes. This has
592 some uncertainty involved. If size/extent_size is within 1% of the
593 actual free extents we will return the extent count, otherwise we'll
595 This accomodates for the size calculation in batch. We need to report
596 the OSD layout but have not yet created any LVM structures. We use the
597 disk size in batch if no VG is present and that will overshoot the
598 actual free_extent count due to LVM overhead.
601 b_to_ext
= int(size
/ int(self
.vg_extent_size
))
602 if b_to_ext
< int(self
.vg_free_count
):
603 # return bytes in extents if there is more space
605 elif b_to_ext
/ int(self
.vg_free_count
) - 1 < 0.01:
606 # return vg_fre_count if its less then 1% off
608 'bytes_to_extents results in {} but only {} '
609 'are available, adjusting the latter'.format(b_to_ext
,
611 return int(self
.vg_free_count
)
612 # else raise an exception
613 raise RuntimeError('Can\'t convert {} to free extents, only {} ({} '
614 'bytes) are free'.format(size
, self
.vg_free_count
,
617 def slots_to_extents(self
, slots
):
619 Return how many extents fit the VG slot times
621 return int(int(self
.vg_extent_count
) / slots
)
624 def create_vg(devices
, name
=None, name_prefix
=None):
626 Create a Volume Group. Command looks like::
628 vgcreate --force --yes group_name device
630 Once created the volume group is returned as a ``VolumeGroup`` object
632 :param devices: A list of devices to create a VG. Optionally, a single
633 device (as a string) can be used.
634 :param name: Optionally set the name of the VG, defaults to 'ceph-{uuid}'
635 :param name_prefix: Optionally prefix the name of the VG, which will get combined
638 if isinstance(devices
, set):
639 devices
= list(devices
)
640 if not isinstance(devices
, list):
643 name
= "%s-%s" % (name_prefix
, str(uuid
.uuid4()))
645 name
= "ceph-%s" % str(uuid
.uuid4())
653 return get_first_vg(filters
={'vg_name': name
})
656 def extend_vg(vg
, devices
):
658 Extend a Volume Group. Command looks like::
660 vgextend --force --yes group_name [device, ...]
662 Once created the volume group is extended and returned as a ``VolumeGroup`` object
664 :param vg: A VolumeGroup object
665 :param devices: A list of devices to extend the VG. Optionally, a single
666 device (as a string) can be used.
668 if not isinstance(devices
, list):
677 return get_first_vg(filters
={'vg_name': vg
.name
})
680 def reduce_vg(vg
, devices
):
682 Reduce a Volume Group. Command looks like::
684 vgreduce --force --yes group_name [device, ...]
686 :param vg: A VolumeGroup object
687 :param devices: A list of devices to remove from the VG. Optionally, a
688 single device (as a string) can be used.
690 if not isinstance(devices
, list):
699 return get_first_vg(filter={'vg_name': vg
.name
})
702 def remove_vg(vg_name
):
704 Removes a volume group.
707 logger
.warning('Skipping removal of invalid VG name: "%s"', vg_name
)
709 fail_msg
= "Unable to remove vg %s" % vg_name
721 def get_vgs(fields
=VG_FIELDS
, filters
='', tags
=None):
723 Return a list of VGs that are available on the system and match the
724 filters and tags passed. Argument filters takes a dictionary containing
725 arguments required by -S option of LVM. Passing a list of LVM tags can be
726 quite tricky to pass as a dictionary within dictionary, therefore pass
727 dictionary of tags via tags argument and tricky part will be taken care of
728 by the helper methods.
730 :param fields: string containing list of fields to be displayed by the
732 :param sep: string containing separator to be used between two fields
733 :param filters: dictionary containing LVM filters
734 :param tags: dictionary containng LVM tags
735 :returns: list of class VolumeGroup object representing vgs on the system
737 filters
= make_filters_lvmcmd_ready(filters
, tags
)
738 args
= ['vgs'] + VG_CMD_OPTIONS
+ ['-S', filters
, '-o', fields
]
740 stdout
, stderr
, returncode
= process
.call(args
, verbose_on_failure
=False)
741 vgs_report
=_output_parser(stdout
, fields
)
742 return [VolumeGroup(**vg_report
) for vg_report
in vgs_report
]
745 def get_first_vg(fields
=VG_FIELDS
, filters
=None, tags
=None):
747 Wrapper of get_vg meant to be a convenience method to avoid the phrase::
752 vgs
= get_vgs(fields
=fields
, filters
=filters
, tags
=tags
)
753 return vgs
[0] if len(vgs
) > 0 else []
756 def get_device_vgs(device
, name_prefix
=''):
757 stdout
, stderr
, returncode
= process
.call(
758 ['pvs'] + VG_CMD_OPTIONS
+ ['-o', VG_FIELDS
, device
],
759 verbose_on_failure
=False
761 vgs
= _output_parser(stdout
, VG_FIELDS
)
762 return [VolumeGroup(**vg
) for vg
in vgs
if vg
['vg_name'] and vg
['vg_name'].startswith(name_prefix
)]
765 #################################
767 # Code for LVM Logical Volumes
769 ###############################
771 LV_FIELDS
= 'lv_tags,lv_path,lv_name,vg_name,lv_uuid,lv_size'
772 LV_CMD_OPTIONS
= ['--noheadings', '--readonly', '--separator=";"', '-a',
773 '--units=b', '--nosuffix']
776 class Volume(object):
778 Represents a Logical Volume from LVM, with some top-level attributes like
779 ``lv_name`` and parsed tags as a dictionary of key/value pairs.
782 def __init__(self
, **kw
):
783 for k
, v
in kw
.items():
786 self
.name
= kw
['lv_name']
788 raise ValueError('Volume must have a non-empty name')
789 self
.tags
= parse_tags(kw
['lv_tags'])
790 self
.encrypted
= self
.tags
.get('ceph.encrypted', '0') == '1'
791 self
.used_by_ceph
= 'ceph.osd_id' in self
.tags
794 return '<%s>' % self
.lv_api
['lv_path']
797 return self
.__str
__()
801 obj
.update(self
.lv_api
)
802 obj
['tags'] = self
.tags
803 obj
['name'] = self
.name
804 obj
['type'] = self
.tags
['ceph.type']
805 obj
['path'] = self
.lv_path
809 if not self
.used_by_ceph
:
811 'name': self
.lv_name
,
812 'comment': 'not used by ceph'
815 type_
= self
.tags
['ceph.type']
817 'name': self
.lv_name
,
818 'osd_id': self
.tags
['ceph.osd_id'],
819 'cluster_name': self
.tags
['ceph.cluster_name'],
821 'osd_fsid': self
.tags
['ceph.osd_fsid'],
822 'cluster_fsid': self
.tags
['ceph.cluster_fsid'],
823 'osdspec_affinity': self
.tags
.get('ceph.osdspec_affinity', ''),
825 type_uuid
= '{}_uuid'.format(type_
)
826 report
[type_uuid
] = self
.tags
['ceph.{}'.format(type_uuid
)]
829 def _format_tag_args(self
, op
, tags
):
830 tag_args
= ['{}={}'.format(k
, v
) for k
, v
in tags
.items()]
831 # weird but efficient way of ziping two lists and getting a flat list
832 return list(sum(zip(repeat(op
), tag_args
), ()))
834 def clear_tags(self
, keys
=None):
836 Removes all or passed tags from the Logical Volume.
839 keys
= self
.tags
.keys()
841 del_tags
= {k
: self
.tags
[k
] for k
in keys
if k
in self
.tags
}
845 del_tag_args
= self
._format
_tag
_args
('--deltag', del_tags
)
846 # --deltag returns successful even if the to be deleted tag is not set
847 process
.call(['lvchange'] + del_tag_args
+ [self
.lv_path
])
848 for k
in del_tags
.keys():
852 def set_tags(self
, tags
):
854 :param tags: A dictionary of tag names and values, like::
857 "ceph.osd_fsid": "aaa-fff-bbbb",
861 At the end of all modifications, the tags are refreshed to reflect
862 LVM's most current view.
864 self
.clear_tags(tags
.keys())
865 add_tag_args
= self
._format
_tag
_args
('--addtag', tags
)
866 process
.call(['lvchange'] + add_tag_args
+ [self
.lv_path
])
867 for k
, v
in tags
.items():
871 def clear_tag(self
, key
):
872 if self
.tags
.get(key
):
873 current_value
= self
.tags
[key
]
874 tag
= "%s=%s" % (key
, current_value
)
875 process
.call(['lvchange', '--deltag', tag
, self
.lv_path
])
879 def set_tag(self
, key
, value
):
881 Set the key/value pair as an LVM tag.
883 # remove it first if it exists
889 '--addtag', '%s=%s' % (key
, value
), self
.lv_path
892 self
.tags
[key
] = value
894 def deactivate(self
):
896 Deactivate the LV by calling lvchange -an
898 process
.call(['lvchange', '-an', self
.lv_path
])
901 def create_lv(name_prefix
,
910 Create a Logical Volume in a Volume Group. Command looks like::
912 lvcreate -L 50G -n gfslv vg0
914 ``name_prefix`` is required. If ``size`` is provided its expected to be a
915 byte count. Tags are an optional dictionary and is expected to
916 conform to the convention of prefixing them with "ceph." like::
918 {"ceph.block_device": "/dev/ceph/osd-1"}
920 :param name_prefix: name prefix for the LV, typically somehting like ceph-osd-block
921 :param uuid: UUID to ensure uniqueness; is combined with name_prefix to
923 :param vg: optional, pass an existing VG to create LV
924 :param device: optional, device to use. Either device of vg must be passed
925 :param slots: optional, number of slots to divide vg up, LV will occupy one
926 one slot if enough space is available
927 :param extends: optional, how many lvm extends to use, supersedes slots
928 :param size: optional, target LV size in bytes, supersedes extents,
929 resulting LV might be smaller depending on extent
930 size of the underlying VG
931 :param tags: optional, a dict of lvm tags to set on the LV
933 name
= '{}-{}'.format(name_prefix
, uuid
)
936 raise RuntimeError("Must either specify vg or device, none given")
937 # check if a vgs starting with ceph already exists
938 vgs
= get_device_vgs(device
, 'ceph')
943 vg
= create_vg(device
, name_prefix
='ceph')
947 extents
= vg
.bytes_to_extents(size
)
948 logger
.debug('size was passed: {} -> {}'.format(size
, extents
))
949 elif slots
and not extents
:
950 extents
= vg
.slots_to_extents(slots
)
951 logger
.debug('slots was passed: {} -> {}'.format(slots
, extents
))
958 '{}'.format(extents
),
959 '-n', name
, vg
.vg_name
961 # create the lv with all the space available, this is needed because the
962 # system call is different for LVM
969 '-n', name
, vg
.vg_name
973 lv
= get_first_lv(filters
={'lv_name': name
, 'vg_name': vg
.vg_name
})
977 "ceph.osd_id": "null",
979 "ceph.cluster_fsid": "null",
980 "ceph.osd_fsid": "null",
982 # when creating a distinct type, the caller doesn't know what the path will
983 # be so this function will set it after creation using the mapping
984 # XXX add CEPH_VOLUME_LVM_DEBUG to enable -vvvv on lv operations
986 'journal': 'ceph.journal_device',
987 'data': 'ceph.data_device',
988 'block': 'ceph.block_device',
989 'wal': 'ceph.wal_device',
990 'db': 'ceph.db_device',
991 'lockbox': 'ceph.lockbox_device', # XXX might not ever need this lockbox sorcery
993 path_tag
= type_path_tag
.get(tags
.get('ceph.type'))
995 tags
.update({path_tag
: lv
.lv_path
})
1002 def create_lvs(volume_group
, parts
=None, size
=None, name_prefix
='ceph-lv'):
1004 Create multiple Logical Volumes from a Volume Group by calculating the
1005 proper extents from ``parts`` or ``size``. A custom prefix can be used
1006 (defaults to ``ceph-lv``), these names are always suffixed with a uuid.
1008 LV creation in ceph-volume will require tags, this is expected to be
1009 pre-computed by callers who know Ceph metadata like OSD IDs and FSIDs. It
1010 will probably not be the case when mass-creating LVs, so common/default
1011 tags will be set to ``"null"``.
1013 .. note:: LVs that are not in use can be detected by querying LVM for tags that are
1016 :param volume_group: The volume group (vg) to use for LV creation
1017 :type group: ``VolumeGroup()`` object
1018 :param parts: Number of LVs to create *instead of* ``size``.
1020 :param size: Size (in gigabytes) of LVs to create, e.g. "as many 10gb LVs as possible"
1022 :param extents: The number of LVM extents to use to create the LV. Useful if looking to have
1023 accurate LV sizes (LVM rounds sizes otherwise)
1025 if parts
is None and size
is None:
1026 # fallback to just one part (using 100% of the vg)
1030 "ceph.osd_id": "null",
1031 "ceph.type": "null",
1032 "ceph.cluster_fsid": "null",
1033 "ceph.osd_fsid": "null",
1035 sizing
= volume_group
.sizing(parts
=parts
, size
=size
)
1036 for part
in range(0, sizing
['parts']):
1037 size
= sizing
['sizes']
1038 extents
= sizing
['extents']
1040 create_lv(name_prefix
, uuid
.uuid4(), vg
=volume_group
, extents
=extents
, tags
=tags
)
1047 Removes a logical volume given it's absolute path.
1049 Will return True if the lv is successfully removed or
1050 raises a RuntimeError if the removal fails.
1052 :param lv: A ``Volume`` object or the path for an LV
1054 if isinstance(lv
, Volume
):
1059 stdout
, stderr
, returncode
= process
.call(
1067 terminal_verbose
=True,
1070 raise RuntimeError("Unable to remove %s" % path
)
1074 def get_lvs(fields
=LV_FIELDS
, filters
='', tags
=None):
1076 Return a list of LVs that are available on the system and match the
1077 filters and tags passed. Argument filters takes a dictionary containing
1078 arguments required by -S option of LVM. Passing a list of LVM tags can be
1079 quite tricky to pass as a dictionary within dictionary, therefore pass
1080 dictionary of tags via tags argument and tricky part will be taken care of
1081 by the helper methods.
1083 :param fields: string containing list of fields to be displayed by the
1085 :param sep: string containing separator to be used between two fields
1086 :param filters: dictionary containing LVM filters
1087 :param tags: dictionary containng LVM tags
1088 :returns: list of class Volume object representing LVs on the system
1090 filters
= make_filters_lvmcmd_ready(filters
, tags
)
1091 args
= ['lvs'] + LV_CMD_OPTIONS
+ ['-S', filters
, '-o', fields
]
1093 stdout
, stderr
, returncode
= process
.call(args
, verbose_on_failure
=False)
1094 lvs_report
= _output_parser(stdout
, fields
)
1095 return [Volume(**lv_report
) for lv_report
in lvs_report
]
1098 def get_first_lv(fields
=LV_FIELDS
, filters
=None, tags
=None):
1100 Wrapper of get_lv meant to be a convenience method to avoid the phrase::
1105 lvs
= get_lvs(fields
=fields
, filters
=filters
, tags
=tags
)
1106 return lvs
[0] if len(lvs
) > 0 else []
1109 def get_lv_by_name(name
):
1110 stdout
, stderr
, returncode
= process
.call(
1111 ['lvs', '--noheadings', '-o', LV_FIELDS
, '-S',
1112 'lv_name={}'.format(name
)],
1113 verbose_on_failure
=False
1115 lvs
= _output_parser(stdout
, LV_FIELDS
)
1116 return [Volume(**lv
) for lv
in lvs
]
1119 def get_lvs_by_tag(lv_tag
):
1120 stdout
, stderr
, returncode
= process
.call(
1121 ['lvs', '--noheadings', '--separator=";"', '-a', '-o', LV_FIELDS
, '-S',
1122 'lv_tags={{{}}}'.format(lv_tag
)],
1123 verbose_on_failure
=False
1125 lvs
= _output_parser(stdout
, LV_FIELDS
)
1126 return [Volume(**lv
) for lv
in lvs
]
1129 def get_device_lvs(device
, name_prefix
=''):
1130 stdout
, stderr
, returncode
= process
.call(
1131 ['pvs'] + LV_CMD_OPTIONS
+ ['-o', LV_FIELDS
, device
],
1132 verbose_on_failure
=False
1134 lvs
= _output_parser(stdout
, LV_FIELDS
)
1135 return [Volume(**lv
) for lv
in lvs
if lv
['lv_name'] and
1136 lv
['lv_name'].startswith(name_prefix
)]
1138 def get_lv_by_fullname(full_name
):
1140 returns LV by the specified LV's full name (formatted as vg_name/lv_name)
1143 vg_name
, lv_name
= full_name
.split('/')
1144 res_lv
= get_first_lv(filters
={'lv_name': lv_name
,
1145 'vg_name': vg_name
})