2018-02-19 17:13:06 +08:00
|
|
|
#!/usr/bin/python
|
|
|
|
|
|
|
|
# Copyright (c) 2018 Red Hat, Inc.
|
|
|
|
#
|
2020-09-06 10:17:02 +08:00
|
|
|
# GNU General Public License v3.0+
|
2018-02-19 17:13:06 +08:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
2020-09-06 10:17:02 +08:00
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
import datetime
|
2018-02-19 17:13:06 +08:00
|
|
|
|
|
|
|
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'] }}"
|
2019-05-14 20:51:32 +08:00
|
|
|
containerized: "{{ container_exec_cmd }}"
|
2018-02-19 17:13:06 +08:00
|
|
|
with_items: "{{ groups[osd_group_name] }}"
|
2019-05-22 16:02:42 +08:00
|
|
|
when: crush_rule_config | bool
|
2018-02-19 17:13:06 +08:00
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''# '''
|
|
|
|
|
|
|
|
|
|
|
|
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",
|
|
|
|
]
|
2020-09-06 10:17:02 +08:00
|
|
|
return sorted(location, key=lambda crush: crush_bucket_types.index(crush[0])) # noqa E501
|
2018-02-19 17:13:06 +08:00
|
|
|
except ValueError as error:
|
2020-09-06 10:17:02 +08:00
|
|
|
fatal("{} is not a valid CRUSH bucket, valid bucket types are {}".format(error.args[0].split()[0], crush_bucket_types), module) # noqa E501
|
2018-02-19 17:13:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2020-09-06 10:17:02 +08:00
|
|
|
cmd_list.append(generate_cmd(cluster, "add-bucket", bucket_name, bucket_type, containerized)) # noqa E501
|
2018-02-19 17:13:06 +08:00
|
|
|
if previous_bucket:
|
|
|
|
# ceph osd crush move monrack root=maroot
|
2020-09-06 10:17:02 +08:00
|
|
|
cmd_list.append(generate_cmd(cluster, "move", previous_bucket, "%s=%s" % (bucket_type, bucket_name), containerized)) # noqa E501
|
2018-02-19 17:13:06 +08:00
|
|
|
previous_bucket = item[1]
|
|
|
|
return cmd_list
|
|
|
|
|
|
|
|
|
|
|
|
def exec_commands(module, cmd_list):
|
|
|
|
'''
|
|
|
|
Creates Ceph commands
|
|
|
|
'''
|
|
|
|
for cmd in cmd_list:
|
2019-03-20 01:23:56 +08:00
|
|
|
rc, out, err = module.run_command(cmd)
|
2018-02-19 17:13:06 +08:00
|
|
|
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
|
2020-09-06 10:17:02 +08:00
|
|
|
rc, cmd, out, err = exec_commands(module, create_and_move_buckets_list(cluster, location, containerized)) # noqa E501
|
2018-02-19 17:13:06 +08:00
|
|
|
|
|
|
|
endd = datetime.datetime.now()
|
|
|
|
delta = endd - startd
|
|
|
|
|
|
|
|
result = dict(
|
|
|
|
cmd=cmd,
|
|
|
|
start=str(startd),
|
|
|
|
end=str(endd),
|
|
|
|
delta=str(delta),
|
|
|
|
rc=rc,
|
2019-03-20 01:23:56 +08:00
|
|
|
stdout=out.rstrip("\r\n"),
|
|
|
|
stderr=err.rstrip("\r\n"),
|
2018-02-19 17:13:06 +08:00
|
|
|
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()
|