From eaf0ebfc859f0e8a4528d60c2655eae32c2b07c5 Mon Sep 17 00:00:00 2001 From: Dimitri Savineau Date: Mon, 16 Nov 2020 10:11:20 -0500 Subject: [PATCH] library: add ceph_mgr_module module This adds ceph_mgr_module ansible module for replacing the command module usage with the ceph mgr module enable/disable commands. Signed-off-by: Dimitri Savineau --- infrastructure-playbooks/cephadm-adopt.yml | 9 +- library/ceph_mgr_module.py | 126 ++++++++++++++ .../tasks/configure_dashboard.yml | 36 +++- roles/ceph-mgr/tasks/mgr_modules.yml | 16 +- tests/library/test_ceph_mgr_module.py | 162 ++++++++++++++++++ 5 files changed, 337 insertions(+), 12 deletions(-) create mode 100644 library/ceph_mgr_module.py create mode 100644 tests/library/test_ceph_mgr_module.py diff --git a/infrastructure-playbooks/cephadm-adopt.yml b/infrastructure-playbooks/cephadm-adopt.yml index 46b0f2f11..0f5ed650f 100644 --- a/infrastructure-playbooks/cephadm-adopt.yml +++ b/infrastructure-playbooks/cephadm-adopt.yml @@ -184,8 +184,13 @@ fsid: "{{ (current_fsid.stdout | from_json).fsid }}" - name: enable cephadm mgr module - command: "{{ container_exec_cmd | default('') }} ceph --cluster {{ cluster }} mgr module enable cephadm" - changed_when: false + ceph_mgr_module: + name: cephadm + cluster: "{{ cluster }}" + state: enable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" run_once: true delegate_to: '{{ groups[mon_group_name][0] }}' diff --git a/library/ceph_mgr_module.py b/library/ceph_mgr_module.py new file mode 100644 index 000000000..e82ae6e22 --- /dev/null +++ b/library/ceph_mgr_module.py @@ -0,0 +1,126 @@ +# 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_ceph_cmd, is_containerized +except ImportError: + from module_utils.ca_common import exit_module, generate_ceph_cmd, is_containerized +import datetime + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: ceph_mgr_module +short_description: Manage Ceph MGR module +version_added: "2.8" +description: + - Manage Ceph MGR module +options: + name: + description: + - name of the ceph MGR module. + required: true + cluster: + description: + - The ceph cluster name. + required: false + default: ceph + state: + description: + - If 'enable' is used, the module enables the MGR module. + If 'absent' is used, the module disables the MGR module. + required: false + choices: ['enable', 'disable'] + default: enable +author: + - Dimitri Savineau +''' + +EXAMPLES = ''' +- name: enable dashboard mgr module + ceph_mgr_module: + name: dashboard + state: enable + +- name: disable multiple mgr modules + ceph_mgr_module: + name: '{{ item }}' + state: disable + loop: + - 'dashboard' + - 'prometheus' +''' + +RETURN = '''# ''' + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + cluster=dict(type='str', required=False, default='ceph'), + state=dict(type='str', required=False, default='enable', choices=['enable', 'disable']), + ), + supports_check_mode=True, + ) + + name = module.params.get('name') + cluster = module.params.get('cluster') + state = module.params.get('state') + + startd = datetime.datetime.now() + + container_image = is_containerized() + + cmd = generate_ceph_cmd(['mgr', 'module'], [state, name], cluster=cluster, container_image=container_image) + + if module.check_mode: + exit_module( + module=module, + out='', + rc=0, + cmd=cmd, + err='', + startd=startd, + changed=False + ) + else: + rc, out, err = module.run_command(cmd) + if out == "module '{}' is already enabled".format(name): + changed = False + else: + changed = True + exit_module( + module=module, + out=out, + rc=rc, + cmd=cmd, + err=err, + startd=startd, + changed=changed + ) + + +if __name__ == '__main__': + main() diff --git a/roles/ceph-dashboard/tasks/configure_dashboard.yml b/roles/ceph-dashboard/tasks/configure_dashboard.yml index 73a28f5d8..13c7867fb 100644 --- a/roles/ceph-dashboard/tasks/configure_dashboard.yml +++ b/roles/ceph-dashboard/tasks/configure_dashboard.yml @@ -110,16 +110,26 @@ dashboard_backend: '{{ item }}' - name: disable mgr dashboard module (restart) - command: "{{ container_exec_cmd }} ceph --cluster {{ cluster }} mgr module disable dashboard" + ceph_mgr_module: + name: dashboard + cluster: "{{ cluster }}" + state: disable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" delegate_to: "{{ groups[mon_group_name][0] }}" run_once: true - changed_when: false - name: enable mgr dashboard module (restart) - command: "{{ container_exec_cmd }} ceph --cluster {{ cluster }} mgr module enable dashboard" + ceph_mgr_module: + name: dashboard + cluster: "{{ cluster }}" + state: enable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" delegate_to: "{{ groups[mon_group_name][0] }}" run_once: true - changed_when: false - name: create dashboard admin user ceph_dashboard_user: @@ -270,13 +280,23 @@ when: ip_version == 'ipv6' - name: disable mgr dashboard module (restart) - command: "{{ container_exec_cmd }} ceph --cluster {{ cluster }} mgr module disable dashboard" - changed_when: false + ceph_mgr_module: + name: dashboard + cluster: "{{ cluster }}" + state: disable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" delegate_to: "{{ groups[mon_group_name][0] }}" run_once: true - name: enable mgr dashboard module (restart) - command: "{{ container_exec_cmd }} ceph --cluster {{ cluster }} mgr module enable dashboard" - changed_when: false + ceph_mgr_module: + name: dashboard + cluster: "{{ cluster }}" + state: enable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" delegate_to: "{{ groups[mon_group_name][0] }}" run_once: true diff --git a/roles/ceph-mgr/tasks/mgr_modules.yml b/roles/ceph-mgr/tasks/mgr_modules.yml index 18cac848a..31ddf5ef8 100644 --- a/roles/ceph-mgr/tasks/mgr_modules.yml +++ b/roles/ceph-mgr/tasks/mgr_modules.yml @@ -32,13 +32,25 @@ _disabled_ceph_mgr_modules: "{% if _ceph_mgr_modules.disabled_modules | length == 0 %}[]{% elif _ceph_mgr_modules.disabled_modules[0] | type_debug != 'dict' %}{{ _ceph_mgr_modules['disabled_modules'] }}{% else %}{{ _ceph_mgr_modules['disabled_modules'] | map(attribute='name') | list }}{% endif %}" - name: disable ceph mgr enabled modules - command: "{{ hostvars[groups[mon_group_name][0]]['container_exec_cmd'] | default('') }} ceph --cluster {{ cluster }} mgr module disable {{ item }}" + ceph_mgr_module: + name: "{{ item }}" + cluster: "{{ cluster }}" + state: disable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" with_items: "{{ _ceph_mgr_modules.get('enabled_modules', []) }}" delegate_to: "{{ groups[mon_group_name][0] }}" when: item not in ceph_mgr_modules - name: add modules to ceph-mgr - command: "{{ hostvars[groups[mon_group_name][0]]['container_exec_cmd'] | default('') }} ceph --cluster {{ cluster }} mgr module enable {{ item }}" + ceph_mgr_module: + name: "{{ item }}" + cluster: "{{ cluster }}" + state: enable + environment: + CEPH_CONTAINER_IMAGE: "{{ ceph_docker_registry + '/' + ceph_docker_image + ':' + ceph_docker_image_tag if containerized_deployment | bool else None }}" + CEPH_CONTAINER_BINARY: "{{ container_binary }}" with_items: "{{ ceph_mgr_modules }}" delegate_to: "{{ groups[mon_group_name][0] }}" when: (item in _disabled_ceph_mgr_modules or _disabled_ceph_mgr_modules == []) diff --git a/tests/library/test_ceph_mgr_module.py b/tests/library/test_ceph_mgr_module.py new file mode 100644 index 000000000..f577c20e9 --- /dev/null +++ b/tests/library/test_ceph_mgr_module.py @@ -0,0 +1,162 @@ +from mock.mock import patch +import os +import pytest +import ca_test_common +import ceph_mgr_module + +fake_cluster = 'ceph' +fake_container_binary = 'podman' +fake_container_image = 'quay.ceph.io/ceph/daemon:latest' +fake_module = 'noup' +fake_user = 'client.admin' +fake_keyring = '/etc/ceph/{}.{}.keyring'.format(fake_cluster, fake_user) + + +class TestCephMgrModuleModule(object): + + @patch('ansible.module_utils.basic.AnsibleModule.fail_json') + def test_without_parameters(self, m_fail_json): + ca_test_common.set_module_args({}) + m_fail_json.side_effect = ca_test_common.fail_json + + with pytest.raises(ca_test_common.AnsibleFailJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert result['msg'] == 'missing required arguments: name' + + @patch('ansible.module_utils.basic.AnsibleModule.exit_json') + def test_with_check_mode(self, m_exit_json): + ca_test_common.set_module_args({ + 'name': fake_module, + '_ansible_check_mode': True + }) + m_exit_json.side_effect = ca_test_common.exit_json + + with pytest.raises(ca_test_common.AnsibleExitJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert not result['changed'] + assert result['cmd'] == ['ceph', '-n', fake_user, '-k', fake_keyring, '--cluster', fake_cluster, 'mgr', 'module', 'enable', fake_module] + assert result['rc'] == 0 + assert not result['stdout'] + assert not result['stderr'] + + @patch('ansible.module_utils.basic.AnsibleModule.exit_json') + @patch('ansible.module_utils.basic.AnsibleModule.run_command') + def test_with_failure(self, m_run_command, m_exit_json): + ca_test_common.set_module_args({ + 'name': fake_module + }) + m_exit_json.side_effect = ca_test_common.exit_json + stdout = '' + stderr = 'Error ENOENT: all mgr daemons do not support module \'{}\', pass --force to force enablement'.format(fake_module) + rc = 2 + m_run_command.return_value = rc, stdout, stderr + + with pytest.raises(ca_test_common.AnsibleExitJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert result['changed'] + assert result['cmd'] == ['ceph', '-n', fake_user, '-k', fake_keyring, '--cluster', fake_cluster, 'mgr', 'module', 'enable', fake_module] + assert result['rc'] == rc + assert result['stderr'] == stderr + + @patch('ansible.module_utils.basic.AnsibleModule.exit_json') + @patch('ansible.module_utils.basic.AnsibleModule.run_command') + def test_enable_module(self, m_run_command, m_exit_json): + ca_test_common.set_module_args({ + 'name': fake_module, + }) + m_exit_json.side_effect = ca_test_common.exit_json + stdout = '' + stderr = '' + rc = 0 + m_run_command.return_value = rc, stdout, stderr + + with pytest.raises(ca_test_common.AnsibleExitJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert result['changed'] + assert result['cmd'] == ['ceph', '-n', fake_user, '-k', fake_keyring, '--cluster', fake_cluster, 'mgr', 'module', 'enable', fake_module] + assert result['rc'] == rc + assert result['stderr'] == stderr + assert result['stdout'] == stdout + + @patch('ansible.module_utils.basic.AnsibleModule.exit_json') + @patch('ansible.module_utils.basic.AnsibleModule.run_command') + def test_already_enable_module(self, m_run_command, m_exit_json): + ca_test_common.set_module_args({ + 'name': fake_module, + }) + m_exit_json.side_effect = ca_test_common.exit_json + stdout = 'module \'{}\' is already enabled'.format(fake_module) + stderr = '' + rc = 0 + m_run_command.return_value = rc, stdout, stderr + + with pytest.raises(ca_test_common.AnsibleExitJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert not result['changed'] + assert result['cmd'] == ['ceph', '-n', fake_user, '-k', fake_keyring, '--cluster', fake_cluster, 'mgr', 'module', 'enable', fake_module] + assert result['rc'] == rc + assert result['stderr'] == stderr + assert result['stdout'] == stdout + + @patch('ansible.module_utils.basic.AnsibleModule.exit_json') + @patch('ansible.module_utils.basic.AnsibleModule.run_command') + def test_disable_module(self, m_run_command, m_exit_json): + ca_test_common.set_module_args({ + 'name': fake_module, + 'state': 'disable' + }) + m_exit_json.side_effect = ca_test_common.exit_json + stdout = '' + stderr = '' + rc = 0 + m_run_command.return_value = rc, stdout, stderr + + with pytest.raises(ca_test_common.AnsibleExitJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert result['changed'] + assert result['cmd'] == ['ceph', '-n', fake_user, '-k', fake_keyring, '--cluster', fake_cluster, 'mgr', 'module', 'disable', fake_module] + assert result['rc'] == rc + assert result['stderr'] == stderr + assert result['stdout'] == stdout + + @patch.dict(os.environ, {'CEPH_CONTAINER_BINARY': fake_container_binary}) + @patch.dict(os.environ, {'CEPH_CONTAINER_IMAGE': fake_container_image}) + @patch('ansible.module_utils.basic.AnsibleModule.exit_json') + @patch('ansible.module_utils.basic.AnsibleModule.run_command') + def test_with_container(self, m_run_command, m_exit_json): + ca_test_common.set_module_args({ + 'name': fake_module, + }) + m_exit_json.side_effect = ca_test_common.exit_json + stdout = '' + stderr = '{} is set'.format(fake_module) + rc = 0 + m_run_command.return_value = rc, stdout, stderr + + with pytest.raises(ca_test_common.AnsibleExitJson) as result: + ceph_mgr_module.main() + + result = result.value.args[0] + assert result['changed'] + assert result['cmd'] == [fake_container_binary, 'run', '--rm', '--net=host', + '-v', '/etc/ceph:/etc/ceph:z', + '-v', '/var/lib/ceph/:/var/lib/ceph/:z', + '-v', '/var/log/ceph/:/var/log/ceph/:z', + '--entrypoint=ceph', fake_container_image, + '-n', fake_user, '-k', fake_keyring, + '--cluster', fake_cluster, 'mgr', 'module', 'enable', fake_module] + assert result['rc'] == rc + assert result['stderr'] == stderr + assert result['stdout'] == stdout -- 2.39.5