ceph-ansible/library/ceph_volume.py

389 lines
11 KiB
Python

#!/usr/bin/python
import datetime
import json
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: ceph_volume
short_description: Create ceph OSDs with ceph-volume
description:
- Using the ceph-volume utility available in Ceph this module
can be used to create ceph OSDs that are backed by logical volumes.
- Only available in ceph versions luminous or greater.
options:
cluster:
description:
- The ceph cluster name.
required: false
default: ceph
objectstore:
description:
- The objectstore of the OSD, either filestore or bluestore
- Required if state is 'present'
required: false
choices: ['bluestore', 'filestore']
state:
description:
- The objectstore of the OSD, either filestore or bluestore
required: true
choices: ['present', 'absent']
default: present
data:
description:
- The logical volume name or device to use for the OSD data.
required: true
data_vg:
description:
- If data is a lv, this must be the name of the volume group it belongs to.
required: false
journal:
description:
- The logical volume name or partition to use as a filestore journal.
- Only applicable if objectstore is 'filestore'.
required: false
journal_vg:
description:
- If journal is a lv, this must be the name of the volume group it belongs to.
- Only applicable if objectstore is 'filestore'.
required: false
db:
description:
- A partition or logical volume name to use for block.db.
- Only applicable if objectstore is 'bluestore'.
required: false
db_vg:
description:
- If db is a lv, this must be the name of the volume group it belongs to.
- Only applicable if objectstore is 'bluestore'.
required: false
wal:
description:
- A partition or logical volume name to use for block.wal.
- Only applicable if objectstore is 'bluestore'.
required: false
wal_vg:
description:
- If wal is a lv, this must be the name of the volume group it belongs to.
- Only applicable if objectstore is 'bluestore'.
required: false
crush_device_class:
description:
- Will set the crush device class for the OSD.
required: false
dmcrypt:
description:
- If set to True the OSD will be encrypted with dmcrypt.
required: false
zap:
description:
- If set to True the devices used to create the OSD will be zapped with 'ceph-volume lvm zap'.
- Only applicable if state is 'absent'.
required: false
author:
- Andrew Schoen (@andrewschoen)
'''
EXAMPLES = '''
- name: set up a filestore osd with an lv data and a journal partition
ceph_volume:
objectstore: filestore
data: data-lv
data_vg: data-vg
journal: /dev/sdc1
- name: set up a bluestore osd with a raw device for data
ceph_volume:
objectstore: bluestore
data: /dev/sdc
- name: set up a bluestore osd with an lv for data and partitions for block.wal and block.db
ceph_volume:
objectstore: bluestore
data: data-lv
data_vg: data-vg
db: /dev/sdc1
wal: /dev/sdc2
'''
from ansible.module_utils.basic import AnsibleModule
def get_data(data, data_vg):
if data_vg:
data = "{0}/{1}".format(data_vg, data)
return data
def get_journal(journal, journal_vg):
if journal_vg:
journal = "{0}/{1}".format(journal_vg, journal)
return journal
def get_db(db, db_vg):
if db_vg:
db = "{0}/{1}".format(db_vg, db)
return db
def get_wal(wal, wal_vg):
if wal_vg:
wal = "{0}/{1}".format(wal_vg, wal)
return wal
def create_osd(module):
cluster = module.params['cluster']
objectstore = module.params.get('objectstore')
data = module.params['data']
data_vg = module.params.get('data_vg', None)
journal = module.params.get('journal', None)
journal_vg = module.params.get('journal_vg', None)
db = module.params.get('db', None)
db_vg = module.params.get('db_vg', None)
wal = module.params.get('wal', None)
wal_vg = module.params.get('wal_vg', None)
crush_device_class = module.params.get('crush_device_class', None)
dmcrypt = module.params['dmcrypt']
if not objectstore:
module.fail_json(msg="The objectstore param is required if state is 'present'. Choices are 'bluestore' or 'filestore'.", changed=False)
cmd = [
'ceph-volume',
'--cluster',
cluster,
'lvm',
'create',
'--%s' % objectstore,
'--data',
]
data = get_data(data, data_vg)
cmd.append(data)
if journal:
journal = get_journal(journal, journal_vg)
cmd.extend(["--journal", journal])
if db:
db = get_db(db, db_vg)
cmd.extend(["--block.db", db])
if wal:
wal = get_wal(wal, wal_vg)
cmd.extend(["--block.wal", wal])
if crush_device_class:
cmd.extend(["--crush-device-class", crush_device_class])
if dmcrypt:
cmd.append("--dmcrypt")
result = dict(
changed=False,
cmd=cmd,
stdout='',
stderr='',
rc='',
start='',
end='',
delta='',
)
if module.check_mode:
return result
# check to see if osd already exists
# FIXME: this does not work when data is a raw device
# support for 'lvm list' and raw devices was added with https://github.com/ceph/ceph/pull/20620 but
# has not made it to a luminous release as of 12.2.4
rc, out, err = module.run_command(["ceph-volume", "lvm", "list", data], encoding=None)
if rc == 0:
result["stdout"] = "skipped, since {0} is already used for an osd".format(data)
result['rc'] = 0
module.exit_json(**result)
startd = datetime.datetime.now()
rc, out, err = module.run_command(cmd, encoding=None)
endd = datetime.datetime.now()
delta = endd - startd
result = dict(
cmd=cmd,
stdout=out.rstrip(b"\r\n"),
stderr=err.rstrip(b"\r\n"),
rc=rc,
start=str(startd),
end=str(endd),
delta=str(delta),
changed=True,
)
if rc != 0:
module.fail_json(msg='non-zero return code', **result)
module.exit_json(**result)
def remove_osd(module):
"""
If the OSD exists, it will 'ceph osd destroy' the
OSD. If zap is set to True then the devices, lvs and
partitions used to create the OSD will be zapped with
'ceph-volume lvm zap' even if the OSD using the data
device is not still active.
"""
data = module.params['data']
data_vg = module.params.get('data_vg', None)
journal = module.params.get('journal', None)
journal_vg = module.params.get('journal_vg', None)
db = module.params.get('db', None)
db_vg = module.params.get('db_vg', None)
wal = module.params.get('wal', None)
wal_vg = module.params.get('wal_vg', None)
zap = module.params.get('zap', False)
base_zap_cmd = [
'ceph-volume',
'lvm',
'zap',
# for simplicity always --destroy. It will be needed
# for raw devices and will noop for lvs.
'--destroy',
]
commands = []
data = get_data(data, data_vg)
# check to see if osd already exists
# FIXME: this does not work when data is a raw device
# support for 'lvm list' and raw devices was added with https://github.com/ceph/ceph/pull/20620 but
# has not made it to a luminous release as of 12.2.4
rc, out, err = module.run_command(["ceph-volume", "lvm", "list", "--format=json", data], encoding=None)
if rc != 0:
# OSD exists and we need to get it's osd_id and run 'ceph osd destroy'
osd_json = json.loads(out)
# the first key should be the osd_id
osd_id = osd_json.keys()[0]
destroy_cmd = [
'ceph',
'osd',
'destroy',
osd_id,
'--yes-i-really-mean-it',
]
commands.append(destroy_cmd)
if zap:
# zap data device
commands.append(base_zap_cmd + [data])
if journal:
journal = get_journal(journal, journal_vg)
commands.append(base_zap_cmd + [journal])
if db:
db = get_db(db, db_vg)
commands.append(base_zap_cmd + [db])
if wal:
wal = get_wal(wal, wal_vg)
commands.append(base_zap_cmd + [wal])
if not commands:
# There was no OSD to destroy and nothing to zap
result = dict(
changed=False,
stdout='There was no OSD to destroy and zap was set to False.',
rc=1,
)
else:
result = dict(
changed=True,
rc=1,
)
command_results = []
for cmd in commands:
startd = datetime.datetime.now()
rc, out, err = module.run_command(cmd, encoding=None)
endd = datetime.datetime.now()
delta = endd - startd
cmd_result = dict(
cmd=cmd,
stdout=out.rstrip(b"\r\n"),
stderr=err.rstrip(b"\r\n"),
rc=rc,
start=str(startd),
end=str(endd),
delta=str(delta),
)
if rc != 0:
module.fail_json(msg='non-zero return code', **cmd_result)
command_results.append(cmd_result)
result["commands"] = command_results
module.exit_json(**result)
def run_module():
module_args = dict(
cluster=dict(type='str', required=False, default='ceph'),
objectstore=dict(type='str', required=False, choices=['bluestore', 'filestore']),
state=dict(type='str', required=False, choices=['present', 'absent'], default='present'),
data=dict(type='str', required=True),
data_vg=dict(type='str', required=False),
journal=dict(type='str', required=False),
journal_vg=dict(type='str', required=False),
db=dict(type='str', required=False),
db_vg=dict(type='str', required=False),
wal=dict(type='str', required=False),
wal_vg=dict(type='str', required=False),
crush_device_class=dict(type='str', required=False),
dmcrypt=dict(type='bool', required=False, default=False),
zap=dict(type='bool', required=False, default=False),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
state = module.params['state']
if state == "present":
create_osd(module)
elif state == "absent":
remove_osd(module)
module.fail_json(msg='State must either be "present" or "absent".', changed=False, rc=1)
def main():
run_module()
if __name__ == '__main__':
main()