ceph-ansible/library/ceph_crush.py

212 lines
5.1 KiB
Python
Raw Normal View History

#!/usr/bin/python
#
# Copyright (c) 2018 Red Hat, Inc.
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: ceph_crush
author: Sebastien Han <seb@redhat.com>
short_description: Create Ceph CRUSH hierarchy
version_added: "2.6"
description:
- By using the hostvar variable 'osd_crush_location'
ceph_crush creates buckets and places them in the right CRUSH hierarchy
options:
cluster:
description:
- The ceph cluster name.
required: false
default: ceph
location:
description:
- osd_crush_location dict from the inventory file. It contains
the placement of each host in the CRUSH map.
required: true
containerized:
description:
- Weither or not this is a containerized cluster. The value is
assigned or not depending on how the playbook runs.
required: false
default: None
'''
EXAMPLES = '''
- name: configure crush hierarchy
ceph_crush:
cluster: "{{ cluster }}"
location: "{{ hostvars[item]['osd_crush_location'] }}"
containerized: "{{ docker_exec_cmd }}"
with_items: "{{ groups[osd_group_name] }}"
when: crush_rule_config
'''
RETURN = '''# '''
from ansible.module_utils.basic import AnsibleModule
import datetime
def fatal(message, module):
'''
Report a fatal error and exit
'''
if module:
module.fail_json(msg=message, rc=1)
else:
raise(Exception(message))
def generate_cmd(cluster, subcommand, bucket, bucket_type, containerized=None):
'''
Generate command line to execute
'''
cmd = [
'ceph',
'--cluster',
cluster,
'osd',
'crush',
subcommand,
bucket,
bucket_type,
]
if containerized:
cmd = containerized.split() + cmd
return cmd
def sort_osd_crush_location(location, module):
'''
Sort location tuple
'''
if len(location) < 2:
fatal("You must specify at least 2 buckets.", module)
if not any(item for item in location if item[0] == "host"):
fatal("You must specify a 'host' bucket.", module)
try:
crush_bucket_types = [
"host",
"chassis",
"rack",
"row",
"pdu",
"pod",
"room",
"datacenter",
"region",
"root",
]
return sorted(location, key=lambda crush: crush_bucket_types.index(crush[0]))
except ValueError as error:
fatal("{} is not a valid CRUSH bucket, valid bucket types are {}".format(error.args[0].split()[0], crush_bucket_types), module)
def create_and_move_buckets_list(cluster, location, containerized=None):
'''
Creates Ceph CRUSH buckets and arrange the hierarchy
'''
previous_bucket = None
cmd_list = []
for item in location:
bucket_type, bucket_name = item
# ceph osd crush add-bucket maroot root
cmd_list.append(generate_cmd(cluster, "add-bucket", bucket_name, bucket_type, containerized))
if previous_bucket:
# ceph osd crush move monrack root=maroot
cmd_list.append(generate_cmd(cluster, "move", previous_bucket, "%s=%s" % (bucket_type, bucket_name), containerized))
previous_bucket = item[1]
return cmd_list
def exec_commands(module, cmd_list):
'''
Creates Ceph commands
'''
for cmd in cmd_list:
rc, out, err = module.run_command(cmd)
return rc, cmd, out, err
def run_module():
module_args = dict(
cluster=dict(type='str', required=False, default='ceph'),
location=dict(type='dict', required=True),
containerized=dict(type='str', required=True, default=None),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
cluster = module.params['cluster']
location_dict = module.params['location']
location = sort_osd_crush_location(tuple(location_dict.items()), module)
containerized = module.params['containerized']
result = dict(
changed=False,
stdout='',
stderr='',
rc='',
start='',
end='',
delta='',
)
if module.check_mode:
return result
startd = datetime.datetime.now()
# run the Ceph command to add buckets
rc, cmd, out, err = exec_commands(module, create_and_move_buckets_list(cluster, location, containerized))
endd = datetime.datetime.now()
delta = endd - startd
result = dict(
cmd=cmd,
start=str(startd),
end=str(endd),
delta=str(delta),
rc=rc,
stdout=out.rstrip("\r\n"),
stderr=err.rstrip("\r\n"),
changed=True,
)
if rc != 0:
module.fail_json(msg='non-zero return code', **result)
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()