]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume lvm.strategies create mixed type filestore
authorAlfredo Deza <adeza@redhat.com>
Thu, 23 Aug 2018 13:53:37 +0000 (09:53 -0400)
committerAndrew Schoen <aschoen@redhat.com>
Wed, 29 Aug 2018 19:03:26 +0000 (14:03 -0500)
Signed-off-by: Alfredo Deza <adeza@redhat.com>
(cherry picked from commit e4902ced1bb26a7f55639c747e6aed7a9fd97b05)

src/ceph-volume/ceph_volume/devices/lvm/strategies/filestore.py

index 56372c6efb6d344261b5ab0b8608b99b5f8e1a53..8b99a52942ecaf37580f7c9de22b16d79c165064 100644 (file)
@@ -16,8 +16,8 @@ class SingleType(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.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()
@@ -96,11 +96,14 @@ class SingleType(object):
             vg = lvm.create_vg(osd['data']['path'])
             osd_vgs.append(vg)
 
+        journal_size = prepare.get_journal_size()
+
         # create the lvs from the vgs captured in the beginning
         for vg in osd_vgs:
             # this is called again, getting us the LVM formatted string
-            journal_size = prepare.get_journal_size()
-            journal_lv = lvm.create_lv('osd-journal', vg.name, size=journal_size)
+            journal_lv = lvm.create_lv(
+                'osd-journal', vg.name, size=journal_size, uuid_name=True
+            )
             # no extents or size means it will use 100%FREE
             data_lv = lvm.create_lv('osd-data', vg.name)
 
@@ -118,4 +121,198 @@ class SingleType(object):
 
 
 class MixedType(object):
-    pass
+    """
+    Supports HDDs with SSDs, journals will be placed on SSDs, while HDDs will
+    be used fully for data.
+
+    If an existing common VG is detected on SSDs, it will be extended if blank
+    SSDs are used, otherwise it will be used directly.
+    """
+
+    def __init__(self, devices, args):
+        self.args = args
+        self.devices = devices
+        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': [], 'vg': None}
+        self.blank_ssds = []
+        self.journals_needed = len(self.hdds)
+        self.journal_size = prepare.get_journal_size(lv_format=False)
+        self.system_vgs = lvm.VolumeGroups()
+        self.validate()
+        self.compute()
+
+    def report_json(self):
+        print(json.dumps(self.computed, indent=4, sort_keys=True))
+
+    def report_pretty(self):
+        string = ""
+        string += templates.total_osds.format(
+            total_osds=len(self.hdds) or len(self.ssds) * 2
+        )
+
+        string += templates.ssd_volume_group.format(
+            target='journal',
+            total_lv_size=str(self.total_available_journal_space),
+            total_lvs=self.journals_needed,
+            block_db_devices=', '.join([d.path for d in self.ssds]),
+            lv_size=str(self.journal_size),
+            total_osds=self.journals_needed
+        )
+
+        string += templates.osd_component_titles
+
+        for osd in self.computed['osds']:
+            string += templates.osd_header
+            string += templates.osd_component.format(
+                _type='[data]',
+                path=osd['data']['path'],
+                size=osd['data']['human_readable_size'],
+                percent=osd['data']['percentage'],
+            )
+            string += templates.osd_component.format(
+                _type='[journal]',
+                path=osd['journal']['path'],
+                size=osd['journal']['human_readable_size'],
+                percent=osd['journal']['percentage'],
+            )
+
+        print(string)
+
+    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):
+        """
+        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)
+
+        # 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 journals 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_journal_space = self.total_blank_ssd_size + common_vg_size
+
+        try:
+            self.vg_extents = lvm.sizing(
+                self.total_available_journal_space.b, size=self.journal_size.b
+            )
+        # FIXME with real exception catching from sizing that happens when the
+        # journal space is not enough
+        except Exception:
+            self.vg_extents = {'parts': 0, 'percentages': 0, 'sizes': 0}
+
+        # validate that number of journals possible are enough for number of
+        # OSDs proposed
+        total_journals_possible = self.total_available_journal_space / self.journal_size
+        if len(self.hdds) > total_journals_possible:
+            msg = "Not enough %s journals (%s) can be created for %s OSDs" % (
+                self.journal_size, total_journals_possible, len(self.hdds)
+            )
+            raise RuntimeError(msg)
+
+    def compute(self):
+        """
+        Go through the rules needed to properly size the lvs, return
+        a dictionary with the result
+        """
+        osds = self.computed['osds']
+
+        vg_free = int(self.total_available_journal_space.gb)
+        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': self.blank_ssds,
+                'parts': self.journals_needed,
+                'percentages': self.vg_extents['percentages'],
+                'sizes': self.journal_size.b,
+                'size': int(self.total_blank_ssd_size.b),
+                'human_readable_sizes': str(self.journal_size),
+                'human_readable_size': str(self.total_available_journal_space),
+            }
+            vg_name = 'lv/vg'
+        else:
+            vg_name = self.common_vg.name
+
+        for device in self.hdds:
+            device_size = disk.Size(b=device.sys_api['size'])
+            data_size = device_size - self.journal_size
+            osd = {'data': {}, 'journal': {}}
+            osd['data']['path'] = device.path
+            osd['data']['size'] = data_size.b
+            osd['data']['percentage'] = 100
+            osd['data']['human_readable_size'] = str(device_size)
+            osd['journal']['path'] = 'vg: %s' % vg_name
+            osd['journal']['size'] = self.journal_size.b
+            osd['journal']['percentage'] = int(self.journal_size.gb * 100 / vg_free)
+            osd['journal']['human_readable_size'] = str(self.journal_size)
+            osds.append(osd)
+
+    def execute(self):
+        """
+        Create vgs/lvs from the incoming set of devices, assign their roles
+        (data, journal) and offload the OSD creation to ``lvm create``
+        """
+        ssd_paths = [d.abspath for d in self.blank_ssds]
+
+        # no common vg is found, create one with all the blank SSDs
+        if not self.common_vg:
+            journal_vg = lvm.create_vg(ssd_paths, name_prefix='ceph-journals')
+        # a vg exists that can be extended
+        elif self.common_vg and ssd_paths:
+            journal_vg = lvm.extend_vg(self.common_vg, ssd_paths)
+        # one common vg with nothing else to extend can be used directly
+        else:
+            journal_vg = self.common_vg
+
+        journal_size = prepare.get_journal_size(lv_format=True)
+
+        for osd in self.computed['osds']:
+            data_vg = lvm.create_vg(osd['data']['path'], name_prefix='ceph-data')
+            # no extents or size means it will use 100%FREE
+            data_lv = lvm.create_lv('osd-data', data_vg.name)
+            journal_lv = lvm.create_lv(
+                'osd-journal', journal_vg.name, size=journal_size, uuid_name=True
+            )
+
+            command = ['--filestore', '--data']
+            command.append('%s/%s' % (data_vg.name, data_lv.name))
+            command.extend(['--journal', '%s/%s' % (journal_vg.name, journal_lv.name)])
+            if self.args.dmcrypt:
+                command.append('--dmcrypt')
+            if self.args.no_systemd:
+                command.append('--no-systemd')
+            if self.args.crush_device_class:
+                command.extend(['--crush-device-class', self.args.crush_device_class])
+
+            Create(command).main()