# Copyright 2020, Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible.module_utils.basic import AnsibleModule try: from ansible.module_utils.ca_common import exit_module, \ generate_cmd, \ is_containerized, \ exec_command except ImportError: from module_utils.ca_common import exit_module, \ generate_cmd, \ is_containerized, \ exec_command import datetime import json ANSIBLE_METADATA = { 'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community' } DOCUMENTATION = ''' --- module: ceph_crush_rule short_description: Manage Ceph Crush Replicated/Erasure Rule version_added: "2.8" description: - Manage Ceph Crush rule(s) creation, deletion and updates. options: name: description: - name of the Ceph Crush rule. If state is 'info' - empty string can be provided as a value to get all crush rules required: true cluster: description: - The ceph cluster name. required: false default: ceph state: description: If 'present' is used, the module creates a rule if it doesn't exist or update it if it already exists. If 'absent' is used, the module will simply delete the rule. required: false choices: ['present', 'absent'] default: present rule_type: description: - The ceph CRUSH rule type. required: false choices: ['replicated', 'erasure'] required: false bucket_root: description: - The ceph bucket root for replicated rule. required: false bucket_type: description: - The ceph bucket type for replicated rule. required: false choices: ['osd', 'host', 'chassis', 'rack', 'row', 'pdu', 'pod', 'room', 'datacenter', 'zone', 'region', 'root'] device_class: description: - The ceph device class for replicated rule. required: false profile: description: - The ceph erasure profile for erasure rule. required: false author: - Dimitri Savineau ''' EXAMPLES = ''' - name: create a Ceph Crush replicated rule ceph_crush_rule: name: foo bucket_root: default bucket_type: host device_class: ssd rule_type: replicated - name: create a Ceph Crush erasure rule ceph_crush_rule: name: foo profile: bar rule_type: erasure - name: get a Ceph Crush rule information ceph_crush_rule: name: foo state: info - name: delete a Ceph Crush rule ceph_crush_rule: name: foo state: absent ''' RETURN = '''# ''' def create_rule(module, container_image=None): ''' Create a new crush replicated/erasure rule ''' cluster = module.params.get('cluster') name = module.params.get('name') rule_type = module.params.get('rule_type') bucket_root = module.params.get('bucket_root') bucket_type = module.params.get('bucket_type') device_class = module.params.get('device_class') profile = module.params.get('profile') if rule_type == 'replicated': args = ['create-replicated', name, bucket_root, bucket_type] if device_class: args.append(device_class) else: args = ['create-erasure', name] if profile: args.append(profile) cmd = generate_cmd(sub_cmd=['osd', 'crush', 'rule'], args=args, cluster=cluster, container_image=container_image) return cmd def get_rule(module, container_image=None): ''' Get existing crush rule ''' cluster = module.params.get('cluster') name = module.params.get('name') args = ['dump', name, '--format=json'] cmd = generate_cmd(sub_cmd=['osd', 'crush', 'rule'], args=args, cluster=cluster, container_image=container_image) return cmd def remove_rule(module, container_image=None): ''' Remove a crush rule ''' cluster = module.params.get('cluster') name = module.params.get('name') args = ['rm', name] cmd = generate_cmd(sub_cmd=['osd', 'crush', 'rule'], args=args, cluster=cluster, container_image=container_image) return cmd def main(): module = AnsibleModule( argument_spec=dict( name=dict(type='str', required=False), cluster=dict(type='str', required=False, default='ceph'), state=dict(type='str', required=False, choices=['present', 'absent'], default='present'), # noqa: E501 rule_type=dict(type='str', required=False, choices=['replicated', 'erasure']), # noqa: E501 bucket_root=dict(type='str', required=False), bucket_type=dict(type='str', required=False, choices=['osd', 'host', 'chassis', 'rack', 'row', 'pdu', 'pod', # noqa: E501 'room', 'datacenter', 'zone', 'region', 'root']), # noqa: E501 device_class=dict(type='str', required=False), profile=dict(type='str', required=False) ), supports_check_mode=True, required_if=[ ('state', 'present', ['rule_type']), ('state', 'present', ['name']), ('state', 'absent', ['name']), ('rule_type', 'replicated', ['bucket_root', 'bucket_type']), ('rule_type', 'erasure', ['profile']) ] ) # Gather module parameters in variables name = module.params.get('name') state = module.params.get('state') rule_type = module.params.get('rule_type') if module.check_mode: module.exit_json( changed=False, stdout='', stderr='', rc=0, start='', end='', delta='', ) startd = datetime.datetime.now() changed = False # will return either the image name or None container_image = is_containerized() rc, cmd, out, err = exec_command(module, get_rule(module, container_image=container_image)) # noqa: E501 if state == "present": if rc != 0: rc, cmd, out, err = exec_command(module, create_rule(module, container_image=container_image)) # noqa: E501 changed = True else: rule = json.loads(out) if (rule['type'] == 1 and rule_type == 'erasure') or (rule['type'] == 3 and rule_type == 'replicated'): # noqa: E501 module.fail_json(msg="Can not convert crush rule {} to {}".format(name, rule_type), changed=False, rc=1) # noqa: E501 elif state == "absent": if rc == 0: rc, cmd, out, err = exec_command(module, remove_rule(module, container_image=container_image)) # noqa: E501 changed = True else: rc = 0 out = "Crush Rule {} doesn't exist".format(name) else: pass exit_module(module=module, out=out, rc=rc, cmd=cmd, err=err, startd=startd, changed=changed) # noqa: E501 if __name__ == '__main__': main()