]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume lvm.strategies add bluestore with validators
authorAlfredo Deza <adeza@redhat.com>
Thu, 12 Jul 2018 19:45:20 +0000 (15:45 -0400)
committerAndrew Schoen <aschoen@redhat.com>
Tue, 28 Aug 2018 15:04:38 +0000 (10:04 -0500)
Signed-off-by: Alfredo Deza <adeza@redhat.com>
(cherry picked from commit 9f13951414e5e4378a0473c9ddef7e641f4e9d3f)

src/ceph-volume/ceph_volume/devices/lvm/strategies/bluestore.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/lvm/strategies/validators.py [new file with mode: 0644]

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 (file)
index 0000000..bb3d6be
--- /dev/null
@@ -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 (file)
index 0000000..aa618f5
--- /dev/null
@@ -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))