From f6fd034e7ee167ea31a382c30432972aeb7adba5 Mon Sep 17 00:00:00 2001 From: Teoman ONAY Date: Wed, 24 Apr 2024 21:32:39 +0200 Subject: [PATCH] ceph_orch_spec: Add ceph orch apply spec feature Add new module ceph_orch_spec which applies ceph spec files. This feature was needed to bind extra mount points to the RGW container (/etc/pki/ca-trust/). Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2262133 Signed-off-by: Teoman ONAY --- infrastructure-playbooks/cephadm-adopt.yml | 24 ++- library/ceph_orch_apply.py | 166 +++++++++++++++++++++ module_utils/ca_common.py | 28 ++++ tests/requirements.txt | 1 + 4 files changed, 211 insertions(+), 8 deletions(-) create mode 100644 library/ceph_orch_apply.py diff --git a/infrastructure-playbooks/cephadm-adopt.yml b/infrastructure-playbooks/cephadm-adopt.yml index 3922e0790..30f2b6f6b 100644 --- a/infrastructure-playbooks/cephadm-adopt.yml +++ b/infrastructure-playbooks/cephadm-adopt.yml @@ -931,14 +931,22 @@ - radosgw_address_block != 'subnet' - name: Update the placement of radosgw hosts - ansible.builtin.command: > - {{ cephadm_cmd }} shell -k /etc/ceph/{{ cluster }}.client.admin.keyring --fsid {{ fsid }} -- - ceph orch apply rgw {{ ansible_facts['hostname'] }} - --placement='count-per-host:{{ radosgw_num_instances }} {{ ansible_facts['nodename'] }}' - {{ rgw_subnet if rgw_subnet is defined else '' }} - --port={{ radosgw_frontend_port }} - {{ '--ssl' if radosgw_frontend_ssl_certificate else '' }} - changed_when: false + ceph_orch_apply: + fsid: "{{ fsid }}" + spec: | + service_type: rgw + service_id: {{ ansible_facts['hostname'] }} + placement: + count_per_host: {{ radosgw_num_instances }} + hosts: + - {{ ansible_facts['nodename'] }} + {{ "networks: rgw_subnet" if rgw_subnet is defined }} + spec: + rgw_frontend_port: {{ radosgw_frontend_port }} + {{ "ssl: true" if radosgw_frontend_ssl_certificate }} + extra_container_args: + - -v + - /etc/pki/ca-trust:/etc/pki/ca-trust:ro delegate_to: "{{ groups[mon_group_name][0] }}" environment: CEPHADM_IMAGE: '{{ ceph_docker_registry }}/{{ ceph_docker_image }}:{{ ceph_docker_image_tag }}' diff --git a/library/ceph_orch_apply.py b/library/ceph_orch_apply.py new file mode 100644 index 000000000..b163bbd09 --- /dev/null +++ b/library/ceph_orch_apply.py @@ -0,0 +1,166 @@ +# Copyright Red Hat +# SPDX-License-Identifier: Apache-2.0 +# Author: Guillaume Abrioux +# +# 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 +from typing import List, Tuple, Dict +__metaclass__ = type + +import datetime +import yaml + +from ansible.module_utils.basic import AnsibleModule # type: ignore +try: + from ansible.module_utils.ca_common import exit_module, build_base_cmd_orch # type: ignore +except ImportError: + from module_utils.ca_common import exit_module, build_base_cmd_orch + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: ceph_orch_apply +short_description: apply service spec +version_added: "2.9" +description: + - apply a service spec +options: + fsid: + description: + - the fsid of the Ceph cluster to interact with. + required: false + image: + description: + - The Ceph container image to use. + required: false + spec: + description: + - The service spec to apply + required: true +author: + - Guillaume Abrioux +''' + +EXAMPLES = ''' +- name: apply osd spec + ceph_orch_apply: + spec: | + service_type: osd + service_id: osd + placement: + label: osds + spec: + data_devices: + all: true +''' + + +def parse_spec(spec: str) -> Dict: + """ parse spec string to yaml """ + yaml_spec = yaml.safe_load(spec) + return yaml_spec + + +def retrieve_current_spec(module: AnsibleModule, expected_spec: Dict) -> Dict: + """ retrieve current config of the service """ + service: str = expected_spec["service_type"] + cmd = build_base_cmd_orch(module) + cmd.extend(['ls', service, '--format=yaml']) + out = module.run_command(cmd) + if isinstance(out, str): + # if there is no existing service, cephadm returns the string 'No services reported' + return {} + else: + return yaml.safe_load(out[1]) + + +def apply_spec(module: "AnsibleModule", + data: str) -> Tuple[int, List[str], str, str]: + cmd = build_base_cmd_orch(module) + cmd.extend(['apply', '-i', '-']) + rc, out, err = module.run_command(cmd, data=data) + + if rc: + raise RuntimeError(err) + + return rc, cmd, out, err + + +def run_module() -> None: + + module_args = dict( + spec=dict(type='str', required=True), + fsid=dict(type='str', required=False), + docker=dict(type=bool, + required=False, + default=False), + image=dict(type='str', required=False) + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True + ) + + startd = datetime.datetime.now() + spec = module.params.get('spec') + + if module.check_mode: + exit_module( + module=module, + out='', + rc=0, + cmd=[], + err='', + startd=startd, + changed=False + ) + + # Idempotency check + expected = parse_spec(module.params.get('spec')) + current_spec = retrieve_current_spec(module, expected) + + if not current_spec or current_spec != expected: + rc, cmd, out, err = apply_spec(module, spec) + changed = True + else: + rc = 0 + cmd = [] + out = '' + err = '' + changed = False + + exit_module( + module=module, + out=out, + rc=rc, + cmd=cmd, + err=err, + startd=startd, + changed=changed + ) + + +def main() -> None: + run_module() + + +if __name__ == '__main__': + main() diff --git a/module_utils/ca_common.py b/module_utils/ca_common.py index 32c0cbdbe..cfbf55a43 100644 --- a/module_utils/ca_common.py +++ b/module_utils/ca_common.py @@ -1,5 +1,7 @@ import os import datetime +from typing import List +from ansible.module_utils.basic import AnsibleModule def generate_cmd(cmd='ceph', @@ -94,6 +96,32 @@ def exec_command(module, cmd, stdin=None, check_rc=False): return rc, cmd, out, err +def build_base_cmd(module: "AnsibleModule") -> List[str]: + cmd = ['cephadm'] + docker = module.params.get('docker') + image = module.params.get('image') + fsid = module.params.get('fsid') + + if docker: + cmd.append('--docker') + if image: + cmd.extend(['--image', image]) + + cmd.append('shell') + + if fsid: + cmd.extend(['--fsid', fsid]) + + return cmd + + +def build_base_cmd_orch(module: "AnsibleModule") -> List[str]: + cmd = build_base_cmd(module) + cmd.extend(['ceph', 'orch']) + + return cmd + + def exit_module(module, out, rc, cmd, err, startd, changed=False, diff=dict(before="", after="")): # noqa: E501 endd = datetime.datetime.now() delta = endd - startd diff --git a/tests/requirements.txt b/tests/requirements.txt index b24770e4f..2ddd5e275 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -8,3 +8,4 @@ mock jmespath pytest-rerunfailures pytest-cov +setuptools