From 9f13951414e5e4378a0473c9ddef7e641f4e9d3f Mon Sep 17 00:00:00 2001 From: Alfredo Deza Date: Thu, 12 Jul 2018 15:45:20 -0400 Subject: [PATCH] ceph-volume lvm.strategies add bluestore with validators Signed-off-by: Alfredo Deza --- .../devices/lvm/strategies/bluestore.py | 265 ++++++++++++++++++ .../devices/lvm/strategies/validators.py | 13 + 2 files changed, 278 insertions(+) create mode 100644 src/ceph-volume/ceph_volume/devices/lvm/strategies/bluestore.py create mode 100644 src/ceph-volume/ceph_volume/devices/lvm/strategies/validators.py diff --git a/src/ceph-volume/ceph_volume/devices/lvm/strategies/bluestore.py b/src/ceph-volume/ceph_volume/devices/lvm/strategies/bluestore.py new file mode 100644 index 00000000000..bb3d6be4fe3 --- /dev/null +++ b/src/ceph-volume/ceph_volume/devices/lvm/strategies/bluestore.py @@ -0,0 +1,265 @@ +from __future__ import print_function +import json +from ceph_volume.util import disk +from ceph_volume.api import lvm +from . import validators +from ceph_volume.devices.lvm.create import Create + +# TODO: get these templates out so filestore can re-use them + +osd_header_template = """ +{:-^80}""".format('') + + +osd_component_titles = """ + Type Path LV Size % of device""" + + +osd_component_template = """ + {_type: <15} {path: <25} {size: <15} {percent}%""" + + +header_template = """ +Total OSDs: {total_osds} +""" + +vg_template = """ +Solid State VG: + Targets: {target: <25} Total size: {total_lv_size: <25} + Total LVs: {total_lvs: <25} Size per LV: {lv_size: <25} + Devices: {block_db_devices} +""" + + +class SingleType(object): + """ + Support for all SSDs, or all HDDS + """ + + def __init__(self, devices, args): + self.args = args + self.devices = devices + self.hdds = [device for device in devices if device['rotational'] == '1'] + self.ssds = [device for device in devices if device['rotational'] == '0'] + self.computed = {'osds': [], 'vgs': []} + self.validate() + self.compute() + + def report_json(self): + print(json.dumps(self.computed, indent=4, sort_keys=True)) + + def report_pretty(self): + string = "" + string += header_template.format( + total_osds=len(self.hdds) or len(self.ssds) * 2 + ) + string += osd_component_titles + + for osd in self.computed['osds']: + string += osd_header_template + string += osd_component_template.format( + _type='[data]', + path=osd['data']['path'], + size=osd['data']['human_readable_size'], + percent=osd['data']['percentage'], + ) + + print(string) + + def validate(self): + """ + Ensure that the minimum requirements for this type of scenario is + met, raise an error if the provided devices would not work + """ + # validate minimum size for all devices + validators.minimum_device_size(self.devices) + + def compute(self): + """ + Go through the rules needed to properly size the lvs, return + a dictionary with the result + """ + osds = self.computed['osds'] + vgs = self.computed['vgs'] + for device in self.hdds: + vgs.append({'devices': [device['path']], 'parts': 1}) + osd = {'data': {}, 'block.db': {}} + osd['data']['path'] = device['path'] + osd['data']['size'] = device['size'] + osd['data']['parts'] = 1 + osd['data']['percentage'] = 100 + osd['data']['human_readable_size'] = str(disk.Size(b=device['size'])) + osds.append(osd) + + for device in self.ssds: + # TODO: creates 2 OSDs per device, make this configurable (env var?) + extents = lvm.sizing(device['size'], parts=2) + vgs.append({'devices': [device['path']], 'parts': 2}) + for ssd in range(2): + osd = {'data': {}, 'block.db': {}} + osd['data']['path'] = device['path'] + osd['data']['size'] = extents['sizes'] + osd['data']['parts'] = extents['parts'] + osd['data']['percentage'] = 50 + osd['data']['human_readable_size'] = str(disk.Size(b=extents['sizes'])) + osds.append(osd) + + def execute(self): + """ + Create vgs/lvs from the incoming set of devices, assign their roles + (block, block.db, block.wal, etc..) and offload the OSD creation to + ``lvm create`` + """ + osd_vgs = dict([(osd['data']['path'], None) for osd in self.computed['osds']]) + + # create the vgs first, mapping them to the device path + for osd in self.computed['osds']: + vg = osd_vgs.get(osd['data']['path']) + if not vg: + vg = lvm.create_vg(osd['data']['path']) + osd_vgs[osd['data']['path']] = {'vg': vg, 'parts': osd['data']['parts']} + + # create the lvs from the vgs captured in the beginning + for create in osd_vgs.values(): + lvs = lvm.create_lvs(create['vg'], parts=create['parts'], name_prefix='osd-data') + vg_name = create['vg'].name + for lv in lvs: + # FIXME: no support for dmcrypt, crush class, etc... + Create([ + '--bluestore', + '--data', "%s/%s" % (vg_name, lv.name), + ]).main() + + +class MixedType(object): + + def __init__(self, devices, args): + self.args = args + self.devices = devices + self.hdds = [device for device in devices if device['rotational'] == '1'] + self.ssds = [device for device in devices if device['rotational'] == '0'] + self.computed = {'osds': [], 'vgs': []} + self.block_db_size = None + # For every HDD we get 1 block.db + self.db_lvs = len(self.hdds) + self.validate() + self.compute() + + def report_json(self): + print(json.dumps(self.computed, indent=4, sort_keys=True)) + + def report_pretty(self): + vg_extents = lvm.sizing(self.total_ssd_size.b, parts=self.db_lvs) + db_size = str(disk.Size(b=(vg_extents['sizes']))) + + string = "" + string += header_template.format( + targets='block.db', + total_lv_size=str(self.total_ssd_size), + total_lvs=vg_extents['parts'], + block_lv_size=db_size, + block_db_devices=', '.join([ssd['path'] for ssd in self.ssds]), + lv_size=str(disk.Size(b=(vg_extents['sizes']))), + total_osds=len(self.hdds) + ) + string += vg_template.format( + target='block.db', + total_lv_size=str(self.total_ssd_size), + total_lvs=vg_extents['parts'], + block_lv_size=db_size, + block_db_devices=', '.join([ssd['path'] for ssd in self.ssds]), + lv_size=str(disk.Size(b=(vg_extents['sizes']))), + total_osds=len(self.hdds) + ) + + string += osd_component_titles + for osd in self.computed['osds']: + string += osd_header_template + string += osd_component_template.format( + _type='[data]', + path=osd['data']['path'], + size=osd['data']['human_readable_size'], + percent=osd['data']['percentage']) + + string += osd_component_template.format( + _type='[block.db]', + path='(volume-group/lv)', + size=osd['block.db']['human_readable_size'], + percent=osd['block.db']['percentage']) + + print(string) + + def compute(self): + osds = self.computed['osds'] + for device in self.hdds: + osd = {'data': {}, 'block.db': {}} + osd['data']['path'] = device['path'] + osd['data']['size'] = device['size'] + osd['data']['percentage'] = 100 + osd['data']['human_readable_size'] = str(disk.Size(b=(device['size']))) + osd['block.db']['path'] = None + osd['block.db']['size'] = int(self.block_db_size.b) + osd['block.db']['human_readable_size'] = str(self.block_db_size) + osd['block.db']['percentage'] = self.vg_extents['percentages'] + osds.append(osd) + + self.computed['vgs'] = [{ + 'devices': [d['path'] for d in self.ssds], + 'parts': self.db_lvs, + 'percentages': self.vg_extents['percentages'], + 'sizes': self.vg_extents['sizes'], + 'size': int(self.total_ssd_size.b), + 'human_readable_sizes': str(disk.Size(b=self.vg_extents['sizes'])), + 'human_readable_size': str(self.total_ssd_size), + }] + + def execute(self): + """ + Create vgs/lvs from the incoming set of devices, assign their roles + (block, block.db, block.wal, etc..) and offload the OSD creation to + ``lvm create`` + """ + # create the single vg for all block.db lv's first + vg_info = self.computed['vgs'][0] + vg = lvm.create_vg(vg_info['devices']) + + # now produce all the block.db lvs needed from that single vg + db_lvs = lvm.create_lvs(vg, parts=vg_info['parts'], name_prefix='osd-block-db') + + # create the data lvs, and create the OSD with the matching block.db lvs from before + for osd in self.computed['osds']: + vg = lvm.create_vg(osd['data']['path']) + from uuid import uuid4 + data_lv = lvm.create_lv('osd-data-%s' % str(uuid4()), vg.name) + db_lv = db_lvs.pop() + # FIXME: no support for dmcrypt, crush class, etc... + Create([ + '--bluestore', + '--data', "%s/%s" % (data_lv.vg_name, data_lv.name), + '--block.db', '%s/%s' % (db_lv.vg_name, db_lv.name) + ]).main() + + def validate(self): + """ + HDDs represent data devices, and solid state devices are for block.db, + make sure that the number of data devices would have enough LVs and + those LVs would be large enough to accommodate a block.db + """ + # validate minimum size for all devices + validators.minimum_device_size(self.devices) + + # add all the size available in solid drives and divide it by the + # expected number of osds, the expected output should be larger than + # the minimum alllowed for block.db + self.total_ssd_size = disk.Size(b=0) + for ssd in self.ssds: + self.total_ssd_size + disk.Size(b=ssd['size']) + + self.block_db_size = self.total_ssd_size / self.db_lvs + self.vg_extents = lvm.sizing(self.total_ssd_size.b, parts=self.db_lvs) + + # min 2GB of block.db is allowed + msg = 'Total solid size (%s) is not enough for block.db LVs larger than 2 GB' + if self.block_db_size < disk.Size(gb=2): + # use ad-hoc exception here + raise RuntimeError(msg % self.total_ssd_size) diff --git a/src/ceph-volume/ceph_volume/devices/lvm/strategies/validators.py b/src/ceph-volume/ceph_volume/devices/lvm/strategies/validators.py new file mode 100644 index 00000000000..aa618f58c8a --- /dev/null +++ b/src/ceph-volume/ceph_volume/devices/lvm/strategies/validators.py @@ -0,0 +1,13 @@ +from ceph_volume.util import disk + + +def minimum_device_size(devices): + """ + Ensure that the minimum requirements for this type of scenario is + met, raise an error if the provided devices would not work + """ + msg = 'Unable to use device smaller than 5GB: %s (%s)' + for device in devices: + device_size = disk.Size(b=device['size']) + if device_size < disk.Size(gb=5): + raise RuntimeError(msg % (device, device_size)) -- 2.39.5