]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/ceph-volume/ceph_volume/devices/lvm/strategies/bluestore.py
update sources to 12.2.10
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / lvm / strategies / bluestore.py
index 7b6052607af3bdb8e7c8c4c8689cce1d541d25cd..92dc3a2e90a74dd8f62bb4d0ac60bc1a73771fa0 100644 (file)
@@ -1,11 +1,12 @@
 from __future__ import print_function
 import json
-from uuid import uuid4
-from ceph_volume.util import disk
+from ceph_volume.util import disk, prepare
 from ceph_volume.api import lvm
 from . import validators
 from ceph_volume.devices.lvm.create import Create
+from ceph_volume.devices.lvm.prepare import Prepare
 from ceph_volume.util import templates
+from ceph_volume.exceptions import SizeAllocationError
 
 
 class SingleType(object):
@@ -15,20 +16,38 @@ class SingleType(object):
 
     def __init__(self, devices, args):
         self.args = args
+        self.osds_per_device = args.osds_per_device
         self.devices = devices
+        # TODO: add --fast-devices and --slow-devices so these can be customized
         self.hdds = [device for device in devices if device.sys_api['rotational'] == '1']
         self.ssds = [device for device in devices if device.sys_api['rotational'] == '0']
-        self.computed = {'osds': [], 'vgs': []}
-        self.validate()
-        self.compute()
+        self.computed = {'osds': [], 'vgs': [], 'filtered_devices': args.filtered_devices}
+        if self.devices:
+            self.validate()
+            self.compute()
+        else:
+            self.computed["changed"] = False
+
+    @staticmethod
+    def type():
+        return "bluestore.SingleType"
+
+    @property
+    def total_osds(self):
+        if self.hdds:
+            return len(self.hdds) * self.osds_per_device
+        else:
+            return len(self.ssds) * self.osds_per_device
 
     def report_json(self):
         print(json.dumps(self.computed, indent=4, sort_keys=True))
 
     def report_pretty(self):
         string = ""
+        if self.args.filtered_devices:
+            string += templates.filtered_devices(self.args.filtered_devices)
         string += templates.total_osds.format(
-            total_osds=len(self.hdds) or len(self.ssds) * 2
+            total_osds=self.total_osds,
         )
         string += templates.osd_component_titles
 
@@ -49,7 +68,12 @@ class SingleType(object):
         met, raise an error if the provided devices would not work
         """
         # validate minimum size for all devices
-        validators.minimum_device_size(self.devices)
+        validators.minimum_device_size(
+            self.devices, osds_per_device=self.osds_per_device
+        )
+
+        # make sure that data devices do not have any LVs
+        validators.no_lvm_membership(self.hdds)
 
     def compute(self):
         """
@@ -57,30 +81,31 @@ class SingleType(object):
         a dictionary with the result
         """
         osds = self.computed['osds']
-        vgs = self.computed['vgs']
         for device in self.hdds:
-            vgs.append({'devices': [device.abspath], 'parts': 1})
-            osd = {'data': {}, 'block.db': {}}
-            osd['data']['path'] = device.abspath
-            osd['data']['size'] = device.sys_api['size']
-            osd['data']['parts'] = 1
-            osd['data']['percentage'] = 100
-            osd['data']['human_readable_size'] = str(disk.Size(b=device.sys_api['size']))
-            osds.append(osd)
+            for hdd in range(self.osds_per_device):
+                osd = {'data': {}, 'block.db': {}}
+                osd['data']['path'] = device.abspath
+                osd['data']['size'] = device.sys_api['size'] / self.osds_per_device
+                osd['data']['parts'] = self.osds_per_device
+                osd['data']['percentage'] = 100 / self.osds_per_device
+                osd['data']['human_readable_size'] = str(
+                    disk.Size(b=device.sys_api['size']) / self.osds_per_device
+                )
+                osds.append(osd)
 
         for device in self.ssds:
-            # TODO: creates 2 OSDs per device, make this configurable (env var?)
-            extents = lvm.sizing(device.sys_api['size'], parts=2)
-            vgs.append({'devices': [device.abspath], 'parts': 2})
-            for ssd in range(2):
+            extents = lvm.sizing(device.sys_api['size'], parts=self.osds_per_device)
+            for ssd in range(self.osds_per_device):
                 osd = {'data': {}, 'block.db': {}}
                 osd['data']['path'] = device.abspath
                 osd['data']['size'] = extents['sizes']
                 osd['data']['parts'] = extents['parts']
-                osd['data']['percentage'] = 50
+                osd['data']['percentage'] = 100 / self.osds_per_device
                 osd['data']['human_readable_size'] = str(disk.Size(b=extents['sizes']))
                 osds.append(osd)
 
+        self.computed['changed'] = len(osds) > 0
+
     def execute(self):
         """
         Create vgs/lvs from the incoming set of devices, assign their roles
@@ -110,7 +135,10 @@ class SingleType(object):
                 if self.args.crush_device_class:
                     command.extend(['--crush-device-class', self.args.crush_device_class])
 
-                Create(command).main()
+                if self.args.prepare:
+                    Prepare(command).main()
+                else:
+                    Create(command).main()
 
 
 class MixedType(object):
@@ -118,34 +146,51 @@ class MixedType(object):
     def __init__(self, devices, args):
         self.args = args
         self.devices = devices
+        self.osds_per_device = args.osds_per_device
+        # TODO: add --fast-devices and --slow-devices so these can be customized
         self.hdds = [device for device in devices if device.sys_api['rotational'] == '1']
         self.ssds = [device for device in devices if device.sys_api['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()
+        self.computed = {'osds': [], 'filtered_devices': args.filtered_devices}
+        self.block_db_size = self.get_block_size()
+        self.system_vgs = lvm.VolumeGroups()
+        self.dbs_needed = len(self.hdds) * self.osds_per_device
+        if self.devices:
+            self.validate()
+            self.compute()
+        else:
+            self.computed["changed"] = False
+
+    @staticmethod
+    def type():
+        return "bluestore.MixedType"
 
     def report_json(self):
         print(json.dumps(self.computed, indent=4, sort_keys=True))
 
+    def get_block_size(self):
+        if self.args.block_db_size:
+            return disk.Size(b=self.args.block_db_size)
+        else:
+            return prepare.get_block_db_size(lv_format=False) or disk.Size(b=0)
+
     def report_pretty(self):
-        vg_extents = lvm.sizing(self.total_ssd_size.b, parts=self.db_lvs)
+        vg_extents = lvm.sizing(self.total_available_db_space.b, parts=self.dbs_needed)
         db_size = str(disk.Size(b=(vg_extents['sizes'])))
 
         string = ""
+        if self.args.filtered_devices:
+            string += templates.filtered_devices(self.args.filtered_devices)
         string += templates.total_osds.format(
-            total_osds=len(self.hdds)
+            total_osds=len(self.hdds) * self.osds_per_device
         )
 
         string += templates.ssd_volume_group.format(
             target='block.db',
-            total_lv_size=str(self.total_ssd_size),
-            total_lvs=vg_extents['parts'],
+            total_lv_size=str(self.total_available_db_space),
+            total_lvs=vg_extents['parts'] * self.osds_per_device,
             block_lv_size=db_size,
             block_db_devices=', '.join([ssd.abspath for ssd in self.ssds]),
-            lv_size=str(disk.Size(b=(vg_extents['sizes']))),
+            lv_size=self.block_db_size or str(disk.Size(b=(vg_extents['sizes']))),
             total_osds=len(self.hdds)
         )
 
@@ -160,7 +205,7 @@ class MixedType(object):
 
             string += templates.osd_component.format(
                 _type='[block.db]',
-                path='(volume-group/lv)',
+                path=osd['block.db']['path'],
                 size=osd['block.db']['human_readable_size'],
                 percent=osd['block.db']['percentage'])
 
@@ -168,27 +213,44 @@ class MixedType(object):
 
     def compute(self):
         osds = self.computed['osds']
+
+        # unconfigured block db size will be 0, so set it back to using as much
+        # as possible from looking at extents
+        if self.block_db_size.b == 0:
+            self.block_db_size = disk.Size(b=self.vg_extents['sizes'])
+
+        if not self.common_vg:
+            # there isn't a common vg, so a new one must be created with all
+            # the blank SSDs
+            self.computed['vg'] = {
+                'devices': ", ".join([ssd.abspath for ssd in self.blank_ssds]),
+                'parts': self.dbs_needed,
+                'percentages': self.vg_extents['percentages'],
+                'sizes': self.block_db_size.b.as_int(),
+                'size': self.total_blank_ssd_size.b.as_int(),
+                'human_readable_sizes': str(self.block_db_size),
+                'human_readable_size': str(self.total_available_db_space),
+            }
+            vg_name = 'vg/lv'
+        else:
+            vg_name = self.common_vg.name
+
         for device in self.hdds:
-            osd = {'data': {}, 'block.db': {}}
-            osd['data']['path'] = device.abspath
-            osd['data']['size'] = device.sys_api['size']
-            osd['data']['percentage'] = 100
-            osd['data']['human_readable_size'] = str(disk.Size(b=(device.sys_api['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.abspath 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),
-        }]
+            for hdd in range(self.osds_per_device):
+                osd = {'data': {}, 'block.db': {}}
+                osd['data']['path'] = device.abspath
+                osd['data']['size'] = device.sys_api['size'] / self.osds_per_device
+                osd['data']['percentage'] = 100 / self.osds_per_device
+                osd['data']['human_readable_size'] = str(
+                    disk.Size(b=(device.sys_api['size'])) / self.osds_per_device
+                )
+                osd['block.db']['path'] = 'vg: %s' % vg_name
+                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['changed'] = len(osds) > 0
 
     def execute(self):
         """
@@ -196,18 +258,50 @@ class MixedType(object):
         (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')
+        blank_ssd_paths = [d.abspath for d in self.blank_ssds]
+        data_vgs = dict([(osd['data']['path'], None) for osd in self.computed['osds']])
+
+        # no common vg is found, create one with all the blank SSDs
+        if not self.common_vg:
+            db_vg = lvm.create_vg(blank_ssd_paths, name_prefix='ceph-block-dbs')
+
+        # if a common vg exists then extend it with any blank ssds
+        elif self.common_vg and blank_ssd_paths:
+            db_vg = lvm.extend_vg(self.common_vg, blank_ssd_paths)
+
+        # one common vg with nothing else to extend can be used directly,
+        # either this is one device with one vg, or multiple devices with the
+        # same vg
+        else:
+            db_vg = self.common_vg
+
+        # since we are falling back to a block_db_size that might be "as large
+        # as possible" we can't fully rely on LV format coming from the helper
+        # function that looks up this value
+        block_db_size = "%sG" % self.block_db_size.gb.as_int()
+
+        # create 1 vg per data device first, mapping them to the device path,
+        # when the lv gets created later, it can create as many as needed (or
+        # even just 1)
+        for osd in self.computed['osds']:
+            vg = data_vgs.get(osd['data']['path'])
+            if not vg:
+                vg = lvm.create_vg(osd['data']['path'], name_prefix='ceph-block')
+                data_vgs[osd['data']['path']] = vg
 
-        # create the data lvs, and create the OSD with the matching block.db lvs from before
+        # create the data lvs, and create the OSD with an lv from the common
+        # block.db vg from before
         for osd in self.computed['osds']:
-            vg = lvm.create_vg(osd['data']['path'])
-            data_lv = lvm.create_lv('osd-data-%s' % str(uuid4()), vg.name)
-            db_lv = db_lvs.pop()
+            data_path = osd['data']['path']
+            data_lv_size = disk.Size(b=osd['data']['size']).gb.as_int()
+            data_vg = data_vgs[data_path]
+            data_lv_extents = data_vg.sizing(size=data_lv_size)['extents']
+            data_lv = lvm.create_lv(
+                'osd-block', data_vg.name, extents=data_lv_extents, uuid_name=True
+            )
+            db_lv = lvm.create_lv(
+                'osd-block-db', db_vg.name, size=block_db_size, uuid_name=True
+            )
             command = [
                 '--bluestore',
                 '--data', "%s/%s" % (data_lv.vg_name, data_lv.name),
@@ -220,7 +314,21 @@ class MixedType(object):
             if self.args.crush_device_class:
                 command.extend(['--crush-device-class', self.args.crush_device_class])
 
-            Create(command).main()
+            if self.args.prepare:
+                Prepare(command).main()
+            else:
+                Create(command).main()
+
+    def get_common_vg(self):
+        # find all the vgs associated with the current device
+        for ssd in self.ssds:
+            for pv in ssd.pvs_api:
+                vg = self.system_vgs.get(vg_name=pv.vg_name)
+                if not vg:
+                    continue
+                # this should give us just one VG, it would've been caught by
+                # the validator otherwise
+                return vg
 
     def validate(self):
         """
@@ -229,20 +337,65 @@ class MixedType(object):
         those LVs would be large enough to accommodate a block.db
         """
         # validate minimum size for all devices
-        validators.minimum_device_size(self.devices)
+        validators.minimum_device_size(self.devices, osds_per_device=self.osds_per_device)
+
+        # make sure that data devices do not have any LVs
+        validators.no_lvm_membership(self.hdds)
+
+        # do not allow non-common VG to continue
+        validators.has_common_vg(self.ssds)
+
+        # find the common VG to calculate how much is available
+        self.common_vg = self.get_common_vg()
+
+        # find how many block.db LVs are possible from the common VG
+        if self.common_vg:
+            common_vg_size = disk.Size(gb=self.common_vg.free)
+        else:
+            common_vg_size = disk.Size(gb=0)
+
+        # non-VG SSDs
+        self.vg_ssds = set([d for d in self.ssds if d.is_lvm_member])
+        self.blank_ssds = set(self.ssds).difference(self.vg_ssds)
+        self.total_blank_ssd_size = disk.Size(b=0)
+        for blank_ssd in self.blank_ssds:
+            self.total_blank_ssd_size += disk.Size(b=blank_ssd.sys_api['size'])
+
+        self.total_available_db_space = self.total_blank_ssd_size + common_vg_size
+
+        # If not configured, we default to 0, which is really "use as much as
+        # possible" captured by the `else` condition
+        if self.block_db_size.gb > 0:
+            try:
+                self.vg_extents = lvm.sizing(
+                    self.total_available_db_space.b, size=self.block_db_size.b * self.osds_per_device
+                )
+            except SizeAllocationError:
+                msg = "Not enough space in fast devices (%s) to create %s x %s block.db LV"
+                raise RuntimeError(
+                    msg % (self.total_available_db_space, self.osds_per_device, self.block_db_size)
+                )
+        else:
+            self.vg_extents = lvm.sizing(
+                self.total_available_db_space.b, parts=self.dbs_needed
+            )
 
-        # 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.sys_api['size'])
+        # validate that number of block.db LVs possible are enough for number of
+        # OSDs proposed
+        if self.total_available_db_space.b == 0:
+            msg = "No space left in fast devices to create block.db LVs"
+            raise RuntimeError(msg)
+
+        # bluestore_block_db_size was unset, so we must set this to whatever
+        # size we get by dividing the total available space for block.db LVs
+        # into the number of block.db LVs needed (i.e. "as large as possible")
+        if self.block_db_size.b == 0:
+            self.block_db_size = self.total_available_db_space / self.dbs_needed
 
-        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)
+        total_dbs_possible = self.total_available_db_space / self.block_db_size
 
-        # 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)
+        if self.dbs_needed > total_dbs_possible:
+            msg = "Not enough space (%s) to create %s x %s block.db LVs" % (
+                self.total_available_db_space, self.dbs_needed, self.block_db_size,
+            )
+            raise RuntimeError(msg)