Skip to content

Commit

Permalink
ceph-volume lvm.strategies add bluestore with validators
Browse files Browse the repository at this point in the history
Signed-off-by: Alfredo Deza <adeza@redhat.com>
(cherry picked from commit 9f13951)
  • Loading branch information
Alfredo Deza authored and andrewschoen committed Aug 28, 2018
1 parent 414836b commit f96cb9c
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
265 changes: 265 additions & 0 deletions 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)
13 changes: 13 additions & 0 deletions 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))

0 comments on commit f96cb9c

Please sign in to comment.