]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/api/lvm.py
9a5907c5d7f994af3a6fc4460325ae03dde3aa42
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 #################################
790 # Code for LVM Logical Volumes
792 ###############################
794 LV_FIELDS
= 'lv_tags,lv_path,lv_name,vg_name,lv_uuid,lv_size'
795 LV_CMD_OPTIONS
= ['--noheadings', '--readonly', '--separator=";"', '-a',
796 '--units=b', '--nosuffix']
799 class Volume(object):
801 Represents a Logical Volume from LVM, with some top-level attributes like
802 ``lv_name`` and parsed tags as a dictionary of key/value pairs.
805 def __init__(self
, **kw
):
806 for k
, v
in kw
.items():
809 self
.name
= kw
['lv_name']
811 raise ValueError('Volume must have a non-empty name')
812 self
.tags
= parse_tags(kw
['lv_tags'])
813 self
.encrypted
= self
.tags
.get('ceph.encrypted', '0') == '1'
814 self
.used_by_ceph
= 'ceph.osd_id' in self
.tags
817 return '<%s>' % self
.lv_api
['lv_path']
820 return self
.__str
__()
824 obj
.update(self
.lv_api
)
825 obj
['tags'] = self
.tags
826 obj
['name'] = self
.name
827 obj
['type'] = self
.tags
['ceph.type']
828 obj
['path'] = self
.lv_path
832 if not self
.used_by_ceph
:
834 'name': self
.lv_name
,
835 'comment': 'not used by ceph'
838 type_
= self
.tags
['ceph.type']
840 'name': self
.lv_name
,
841 'osd_id': self
.tags
['ceph.osd_id'],
842 'cluster_name': self
.tags
.get('ceph.cluster_name', conf
.cluster
),
844 'osd_fsid': self
.tags
['ceph.osd_fsid'],
845 'cluster_fsid': self
.tags
['ceph.cluster_fsid'],
846 'osdspec_affinity': self
.tags
.get('ceph.osdspec_affinity', ''),
848 type_uuid
= '{}_uuid'.format(type_
)
849 report
[type_uuid
] = self
.tags
['ceph.{}'.format(type_uuid
)]
852 def _format_tag_args(self
, op
, tags
):
853 tag_args
= ['{}={}'.format(k
, v
) for k
, v
in tags
.items()]
854 # weird but efficient way of ziping two lists and getting a flat list
855 return list(sum(zip(repeat(op
), tag_args
), ()))
857 def clear_tags(self
, keys
=None):
859 Removes all or passed tags from the Logical Volume.
862 keys
= self
.tags
.keys()
864 del_tags
= {k
: self
.tags
[k
] for k
in keys
if k
in self
.tags
}
868 del_tag_args
= self
._format
_tag
_args
('--deltag', del_tags
)
869 # --deltag returns successful even if the to be deleted tag is not set
870 process
.call(['lvchange'] + del_tag_args
+ [self
.lv_path
], run_on_host
=True)
871 for k
in del_tags
.keys():
875 def set_tags(self
, tags
):
877 :param tags: A dictionary of tag names and values, like::
880 "ceph.osd_fsid": "aaa-fff-bbbb",
884 At the end of all modifications, the tags are refreshed to reflect
885 LVM's most current view.
887 self
.clear_tags(tags
.keys())
888 add_tag_args
= self
._format
_tag
_args
('--addtag', tags
)
889 process
.call(['lvchange'] + add_tag_args
+ [self
.lv_path
], run_on_host
=True)
890 for k
, v
in tags
.items():
894 def clear_tag(self
, key
):
895 if self
.tags
.get(key
):
896 current_value
= self
.tags
[key
]
897 tag
= "%s=%s" % (key
, current_value
)
898 process
.call(['lvchange', '--deltag', tag
, self
.lv_path
], run_on_host
=True)
902 def set_tag(self
, key
, value
):
904 Set the key/value pair as an LVM tag.
906 # remove it first if it exists
912 '--addtag', '%s=%s' % (key
, value
), self
.lv_path
916 self
.tags
[key
] = value
918 def deactivate(self
):
920 Deactivate the LV by calling lvchange -an
922 process
.call(['lvchange', '-an', self
.lv_path
], run_on_host
=True)
925 def create_lv(name_prefix
,
934 Create a Logical Volume in a Volume Group. Command looks like::
936 lvcreate -L 50G -n gfslv vg0
938 ``name_prefix`` is required. If ``size`` is provided its expected to be a
939 byte count. Tags are an optional dictionary and is expected to
940 conform to the convention of prefixing them with "ceph." like::
942 {"ceph.block_device": "/dev/ceph/osd-1"}
944 :param name_prefix: name prefix for the LV, typically somehting like ceph-osd-block
945 :param uuid: UUID to ensure uniqueness; is combined with name_prefix to
947 :param vg: optional, pass an existing VG to create LV
948 :param device: optional, device to use. Either device of vg must be passed
949 :param slots: optional, number of slots to divide vg up, LV will occupy one
950 one slot if enough space is available
951 :param extends: optional, how many lvm extends to use, supersedes slots
952 :param size: optional, target LV size in bytes, supersedes extents,
953 resulting LV might be smaller depending on extent
954 size of the underlying VG
955 :param tags: optional, a dict of lvm tags to set on the LV
957 name
= '{}-{}'.format(name_prefix
, uuid
)
960 raise RuntimeError("Must either specify vg or device, none given")
961 # check if a vgs starting with ceph already exists
962 vgs
= get_device_vgs(device
, 'ceph')
967 vg
= create_vg(device
, name_prefix
='ceph')
971 extents
= vg
.bytes_to_extents(size
)
972 logger
.debug('size was passed: {} -> {}'.format(size
, extents
))
973 elif slots
and not extents
:
974 extents
= vg
.slots_to_extents(slots
)
975 logger
.debug('slots was passed: {} -> {}'.format(slots
, extents
))
982 '{}'.format(extents
),
983 '-n', name
, vg
.vg_name
985 # create the lv with all the space available, this is needed because the
986 # system call is different for LVM
993 '-n', name
, vg
.vg_name
995 process
.run(command
, run_on_host
=True)
997 lv
= get_single_lv(filters
={'lv_name': name
, 'vg_name': vg
.vg_name
})
1001 "ceph.osd_id": "null",
1002 "ceph.type": "null",
1003 "ceph.cluster_fsid": "null",
1004 "ceph.osd_fsid": "null",
1006 # when creating a distinct type, the caller doesn't know what the path will
1007 # be so this function will set it after creation using the mapping
1008 # XXX add CEPH_VOLUME_LVM_DEBUG to enable -vvvv on lv operations
1010 'journal': 'ceph.journal_device',
1011 'data': 'ceph.data_device',
1012 'block': 'ceph.block_device',
1013 'wal': 'ceph.wal_device',
1014 'db': 'ceph.db_device',
1015 'lockbox': 'ceph.lockbox_device', # XXX might not ever need this lockbox sorcery
1017 path_tag
= type_path_tag
.get(tags
.get('ceph.type'))
1019 tags
.update({path_tag
: lv
.lv_path
})
1026 def create_lvs(volume_group
, parts
=None, size
=None, name_prefix
='ceph-lv'):
1028 Create multiple Logical Volumes from a Volume Group by calculating the
1029 proper extents from ``parts`` or ``size``. A custom prefix can be used
1030 (defaults to ``ceph-lv``), these names are always suffixed with a uuid.
1032 LV creation in ceph-volume will require tags, this is expected to be
1033 pre-computed by callers who know Ceph metadata like OSD IDs and FSIDs. It
1034 will probably not be the case when mass-creating LVs, so common/default
1035 tags will be set to ``"null"``.
1037 .. note:: LVs that are not in use can be detected by querying LVM for tags that are
1040 :param volume_group: The volume group (vg) to use for LV creation
1041 :type group: ``VolumeGroup()`` object
1042 :param parts: Number of LVs to create *instead of* ``size``.
1044 :param size: Size (in gigabytes) of LVs to create, e.g. "as many 10gb LVs as possible"
1046 :param extents: The number of LVM extents to use to create the LV. Useful if looking to have
1047 accurate LV sizes (LVM rounds sizes otherwise)
1049 if parts
is None and size
is None:
1050 # fallback to just one part (using 100% of the vg)
1054 "ceph.osd_id": "null",
1055 "ceph.type": "null",
1056 "ceph.cluster_fsid": "null",
1057 "ceph.osd_fsid": "null",
1059 sizing
= volume_group
.sizing(parts
=parts
, size
=size
)
1060 for part
in range(0, sizing
['parts']):
1061 size
= sizing
['sizes']
1062 extents
= sizing
['extents']
1064 create_lv(name_prefix
, uuid
.uuid4(), vg
=volume_group
, extents
=extents
, tags
=tags
)
1071 Removes a logical volume given it's absolute path.
1073 Will return True if the lv is successfully removed or
1074 raises a RuntimeError if the removal fails.
1076 :param lv: A ``Volume`` object or the path for an LV
1078 if isinstance(lv
, Volume
):
1083 stdout
, stderr
, returncode
= process
.call(
1092 terminal_verbose
=True,
1095 raise RuntimeError("Unable to remove %s" % path
)
1099 def get_lvs(fields
=LV_FIELDS
, filters
='', tags
=None):
1101 Return a list of LVs that are available on the system and match the
1102 filters and tags passed. Argument filters takes a dictionary containing
1103 arguments required by -S option of LVM. Passing a list of LVM tags can be
1104 quite tricky to pass as a dictionary within dictionary, therefore pass
1105 dictionary of tags via tags argument and tricky part will be taken care of
1106 by the helper methods.
1108 :param fields: string containing list of fields to be displayed by the
1110 :param sep: string containing separator to be used between two fields
1111 :param filters: dictionary containing LVM filters
1112 :param tags: dictionary containng LVM tags
1113 :returns: list of class Volume object representing LVs on the system
1115 filters
= make_filters_lvmcmd_ready(filters
, tags
)
1116 args
= ['lvs'] + LV_CMD_OPTIONS
+ ['-S', filters
, '-o', fields
]
1118 stdout
, stderr
, returncode
= process
.call(args
, run_on_host
=True, verbose_on_failure
=False)
1119 lvs_report
= _output_parser(stdout
, fields
)
1120 return [Volume(**lv_report
) for lv_report
in lvs_report
]
1123 def get_single_lv(fields
=LV_FIELDS
, filters
=None, tags
=None):
1125 Wrapper of get_lvs() meant to be a convenience method to avoid the phrase::
1130 lvs
= get_lvs(fields
=fields
, filters
=filters
, tags
=tags
)
1135 raise RuntimeError('Filters {} matched more than 1 LV present on this host.'.format(str(filters
)))
1140 def get_lvs_from_osd_id(osd_id
):
1141 return get_lvs(tags
={'ceph.osd_id': osd_id
})
1144 def get_single_lv_from_osd_id(osd_id
):
1145 return get_single_lv(tags
={'ceph.osd_id': osd_id
})
1148 def get_lv_by_name(name
):
1149 stdout
, stderr
, returncode
= process
.call(
1150 ['lvs', '--noheadings', '-o', LV_FIELDS
, '-S',
1151 'lv_name={}'.format(name
)],
1153 verbose_on_failure
=False
1155 lvs
= _output_parser(stdout
, LV_FIELDS
)
1156 return [Volume(**lv
) for lv
in lvs
]
1159 def get_lvs_by_tag(lv_tag
):
1160 stdout
, stderr
, returncode
= process
.call(
1161 ['lvs', '--noheadings', '--separator=";"', '-a', '-o', LV_FIELDS
, '-S',
1162 'lv_tags={{{}}}'.format(lv_tag
)],
1164 verbose_on_failure
=False
1166 lvs
= _output_parser(stdout
, LV_FIELDS
)
1167 return [Volume(**lv
) for lv
in lvs
]
1170 def get_device_lvs(device
, name_prefix
=''):
1171 stdout
, stderr
, returncode
= process
.call(
1172 ['pvs'] + LV_CMD_OPTIONS
+ ['-o', LV_FIELDS
, device
],
1174 verbose_on_failure
=False
1176 lvs
= _output_parser(stdout
, LV_FIELDS
)
1177 return [Volume(**lv
) for lv
in lvs
if lv
['lv_name'] and
1178 lv
['lv_name'].startswith(name_prefix
)]
1180 def get_lvs_from_path(devpath
):
1182 if os
.path
.isabs(devpath
):
1183 # we have a block device
1184 lvs
= get_device_lvs(devpath
)
1186 # maybe this was a LV path /dev/vg_name/lv_name or /dev/mapper/
1187 lvs
= get_lvs(filters
={'path': devpath
})
1191 def get_lv_by_fullname(full_name
):
1193 returns LV by the specified LV's full name (formatted as vg_name/lv_name)
1196 vg_name
, lv_name
= full_name
.split('/')
1197 res_lv
= get_single_lv(filters
={'lv_name': lv_name
,
1198 'vg_name': vg_name
})