Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
modules: Add _info modules
Browse files Browse the repository at this point in the history
Validate modules test fails with:

Argument state includes get, list or info as a choice.
Functionality should be in an _info or (if further conditions apply) _facts module.

on modules ceph_crush_rule and ceph_key

With Ansible 2.8 and onwards best practice requires that modules
gathering info to be put in a separate module named _info[1]

[1]https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#creating-an-info-or-a-facts-module

Signed-off-by: Teoman ONAY <tonay@ibm.com>
asm0deuz committed Dec 5, 2024
1 parent 3814c2e commit 46ddd04
Showing 21 changed files with 421 additions and 69 deletions.
4 changes: 2 additions & 2 deletions infrastructure-playbooks/ceph-keys.yml
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@
with_items: "{{ keys_to_delete }}"

- name: Info ceph key(s)
ceph_key:
ceph_key_info:
name: "{{ item }}"
state: info
cluster: "{{ cluster }}"
@@ -60,7 +60,7 @@
with_items: "{{ keys_to_info }}"

- name: List ceph key(s)
ceph_key:
ceph_key_info:
state: list
cluster: "{{ cluster }}"
containerized: "{{ container_exec_cmd | default(False) }}"
2 changes: 1 addition & 1 deletion infrastructure-playbooks/cephadm-adopt.yml
Original file line number Diff line number Diff line change
@@ -382,7 +382,7 @@
inventory_hostname in groups.get(rbdmirror_group_name, [])

- name: Get the client.admin keyring
ceph_key:
ceph_key_info:
name: client.admin
cluster: "{{ cluster }}"
output_format: plain
6 changes: 2 additions & 4 deletions library/ceph_crush_rule.py
Original file line number Diff line number Diff line change
@@ -59,10 +59,8 @@
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.
If 'info' is used, the module will return all details about the
existing rule (json formatted).
required: false
choices: ['present', 'absent', 'info']
choices: ['present', 'absent']
default: present
rule_type:
description:
@@ -192,7 +190,7 @@ def main():
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', 'info'], default='present'), # noqa: E501
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
119 changes: 119 additions & 0 deletions library/ceph_crush_rule_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# 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


ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: ceph_crush_rule_info
short_description: Lists Ceph Crush Replicated/Erasure Rules
version_added: "2.8"
description:
- Retrieces Ceph Crush rule(s).
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
author:
- Teoman ONAY <@asm0deuz>
'''

EXAMPLES = '''
- name: get a Ceph Crush rule information
ceph_crush_rule_info:
name: foo
'''

RETURN = '''# '''


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 main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=False),
cluster=dict(type='str', required=False, default='ceph'),
),
supports_check_mode=True,
)

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

exit_module(module=module, out=out, rc=rc, cmd=cmd, err=err, startd=startd, changed=changed) # noqa: E501


if __name__ == '__main__':
main()
44 changes: 9 additions & 35 deletions library/ceph_key.py
Original file line number Diff line number Diff line change
@@ -18,14 +18,14 @@
from ansible.module_utils.basic import AnsibleModule
try:
from ansible.module_utils.ca_common import generate_cmd, \
is_containerized, \
container_exec, \
fatal
is_containerized, \
container_exec, \
fatal
except ImportError:
from module_utils.ca_common import generate_cmd, \
is_containerized, \
container_exec, \
fatal
is_containerized, \
container_exec, \
fatal
import datetime
import json
import os
@@ -85,11 +85,9 @@
If 'absent' is used, the module will simply delete the keyring.
If 'list' is used, the module will list all the keys and will
return a json output.
If 'info' is used, the module will return in a json format the
description of a given keyring.
If 'generate_secret' is used, the module will simply output a cephx keyring.
required: false
choices: ['present', 'update', 'absent', 'list', 'info', 'fetch_initial_keys', 'generate_secret']
choices: ['present', 'update', 'absent', 'fetch_initial_keys', 'generate_secret']
default: present
caps:
description:
@@ -182,22 +180,6 @@
name: "my_key"
state: absent
- name: info cephx key
ceph_key:
name: "my_key""
state: info
- name: info cephx admin key (plain)
ceph_key:
name: client.admin
output_format: plain
state: info
register: client_admin_key
- name: list cephx keys
ceph_key:
state: list
- name: fetch cephx keys
ceph_key:
state: fetch_initial_keys
@@ -487,7 +469,7 @@ def run_module():
cluster=dict(type='str', required=False, default='ceph'),
name=dict(type='str', required=False),
state=dict(type='str', required=False, default='present', choices=['present', 'update', 'absent', # noqa: E501
'list', 'info', 'fetch_initial_keys', 'generate_secret']), # noqa: E501
'fetch_initial_keys', 'generate_secret']), # noqa: E501
caps=dict(type='dict', required=False, default=None),
secret=dict(type='str', required=False, default=None, no_log=True),
import_key=dict(type='bool', required=False, default=True),
@@ -518,7 +500,7 @@ def run_module():
output_format = module.params.get('output_format')

# Can't use required_if with 'name' for some reason...
if state in ['present', 'absent', 'update', 'info'] and not name:
if state in ['present', 'absent', 'update'] and not name:
fatal(f'"state" is "{state}" but "name" is not defined.', module)

changed = False
@@ -626,14 +608,6 @@ def run_module():
else:
rc = 0

elif state == "info":
rc, cmd, out, err = exec_commands(
module, info_key(cluster, name, user, user_key_path, output_format, container_image)) # noqa: E501

elif state == "list":
rc, cmd, out, err = exec_commands(
module, list_keys(cluster, user, user_key_path, container_image))

elif state == "fetch_initial_keys":
hostname = socket.gethostname().split('.', 1)[0]
user = "mon."
265 changes: 265 additions & 0 deletions library/ceph_key_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# Copyright 2018, 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 generate_cmd, \
is_containerized, \
fatal
except ImportError:
from module_utils.ca_common import generate_cmd, \
is_containerized, \
fatal
import datetime
import os


ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: ceph_key_info
author: Teoman ONAY <@asM0deuz>
short_description: Manage Cephx key(s)
version_added: "2.6"
description:
- Manage CephX creation, deletion and updates.
It can also list and get information about keyring(s).
options:
cluster:
description:
- The ceph cluster name.
required: false
default: ceph
name:
description:
- name of the CephX key
required: true
user:
description:
- entity used to perform operation.
It corresponds to the -n option (--name)
required: false
user_key:
description:
- the path to the keyring corresponding to the
user being used.
It corresponds to the -k option (--keyring)
state:
description:
If 'list' is used, the module will list all the keys and will
return a json output.
If 'info' is used, the module will return in a json format the
description of a given keyring.
required: false
choices: ['list', 'info']
default: list
output_format:
description:
- The key output format when retrieving the information of an
entity.
required: false
default: json
'''

EXAMPLES = '''
- name: info cephx key
ceph_key:
name: "my_key""
state: info
- name: info cephx admin key (plain)
ceph_key:
name: client.admin
output_format: plain
state: info
register: client_admin_key
- name: list cephx keys
ceph_key:
state: list
'''

RETURN = '''# '''


CEPH_INITIAL_KEYS = ['client.admin', 'client.bootstrap-mds', 'client.bootstrap-mgr', # noqa: E501
'client.bootstrap-osd', 'client.bootstrap-rbd', 'client.bootstrap-rbd-mirror', 'client.bootstrap-rgw'] # noqa: E501


def info_key(cluster, name, user, user_key, output_format, container_image=None): # noqa: E501
'''
Get information about a CephX key
'''

cmd_list = []

args = [
'get',
name,
'-f',
output_format,
]

cmd_list.append(generate_cmd(sub_cmd=['auth'],
args=args,
cluster=cluster,
user=user,
user_key=user_key,
container_image=container_image))

return cmd_list


def list_keys(cluster, user, user_key, container_image=None):
'''
List all CephX keys
'''

cmd_list = []

args = [
'ls',
'-f',
'json',
]

cmd_list.append(generate_cmd(sub_cmd=['auth'],
args=args,
cluster=cluster,
user=user,
user_key=user_key,
container_image=container_image))

return cmd_list


def exec_commands(module, cmd_list):
'''
Execute command(s)
'''

for cmd in cmd_list:
rc, out, err = module.run_command(cmd)
if rc != 0:
return rc, cmd, out, err

return rc, cmd, out, err


def run_module():
module_args = dict(
cluster=dict(type='str', required=False, default='ceph'),
name=dict(type='str', required=False),
state=dict(type='str', required=False, default='info', choices=['list', 'info']), # noqa: E501
user=dict(type='str', required=False, default='client.admin'),
user_key=dict(type='str', required=False, default=None),
output_format=dict(type='str', required=False, default='json', choices=['json', 'plain', 'xml', 'yaml']) # noqa: E501
)

module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
add_file_common_args=True,
)

# Gather module parameters in variables
state = module.params['state']
name = module.params.get('name')
cluster = module.params.get('cluster')
user = module.params.get('user')
user_key = module.params.get('user_key')
output_format = module.params.get('output_format')

# Can't use required_if with 'name' for some reason...
if state in ['info'] and not name:
fatal(f'"state" is "{state}" but "name" is not defined.', module)

changed = False
cmd = ''
rc = 0
out = ''
err = ''

result = dict(
changed=changed,
stdout='',
stderr='',
rc=0,
start='',
end='',
delta='',
)

if module.check_mode:
module.exit_json(**result)

startd = datetime.datetime.now()

# will return either the image name or None
container_image = is_containerized()

if not user_key:
user_key_filename = '{}.{}.keyring'.format(cluster, user)
user_key_dir = '/etc/ceph'
user_key_path = os.path.join(user_key_dir, user_key_filename)
else:
user_key_path = user_key

if state == "info":
rc, cmd, out, err = exec_commands(
module, info_key(cluster, name, user, user_key_path, output_format, container_image)) # noqa: E501

elif state == "list":
rc, cmd, out, err = exec_commands(
module, list_keys(cluster, user, user_key_path, container_image))

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=changed,
)

if rc != 0:
module.fail_json(msg='non-zero return code', **result)

module.exit_json(**result)


def main():
run_module()


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion roles/ceph-client/tasks/pre_requisite.yml
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
- copy_admin_key | bool
block:
- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: client.admin
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-crash/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@
no_log: "{{ no_log_on_ceph_key_tasks }}"

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: client.crash
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-exporter/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
no_log: "{{ no_log_on_ceph_key_tasks }}"

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: client.ceph-exporter
cluster: "{{ cluster }}"
output_format: plain
3 changes: 1 addition & 2 deletions roles/ceph-facts/tasks/get_def_crush_rule_name.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
---
- name: Get current default crush rule details
ceph_crush_rule:
ceph_crush_rule_info:
cluster: "{{ cluster }}"
state: info
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 }}"
2 changes: 1 addition & 1 deletion roles/ceph-mds/tasks/common.yml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
- /var/lib/ceph/mds/{{ cluster }}-{{ ansible_facts['hostname'] }}

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-mgr/tasks/common.yml
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@
- { 'name': "mgr.{{ ansible_facts['hostname'] }}", 'path': "/var/lib/ceph/mgr/{{ cluster }}-{{ ansible_facts['hostname'] }}/keyring", 'copy_key': true }

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-nfs/tasks/pre_requisite_container.yml
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@
- item.create | default(False) | bool

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-nfs/tasks/pre_requisite_non_container.yml
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@
- groups.get(mon_group_name, []) | length > 0
block:
- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-osd/tasks/common.yml
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
- /var/lib/ceph/osd/

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
output_format: plain
3 changes: 1 addition & 2 deletions roles/ceph-osd/tasks/crush_rules.yml
Original file line number Diff line number Diff line change
@@ -52,10 +52,9 @@
run_once: true

- name: Get id for new default crush rule
ceph_crush_rule:
ceph_crush_rule_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
state: info
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 }}"
4 changes: 2 additions & 2 deletions roles/ceph-rbd-mirror/tasks/configure_mirroring.yml
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
- cephx | bool
block:
- name: Get client.bootstrap-rbd-mirror from ceph monitor
ceph_key:
ceph_key_info:
name: client.bootstrap-rbd-mirror
cluster: "{{ cluster }}"
output_format: plain
@@ -62,7 +62,7 @@
secret: "{{ ceph_rbd_mirror_local_user_secret | default('') }}" }

- name: Get client.rbd-mirror keyring from ceph monitor
ceph_key:
ceph_key_info:
name: "client.rbd-mirror.{{ ansible_facts['hostname'] }}"
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-rgw/tasks/common.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "{{ item.name }}"
cluster: "{{ cluster }}"
output_format: plain
2 changes: 1 addition & 1 deletion roles/ceph-rgw/tasks/pre_requisite.yml
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@
when: cephx | bool

- name: Get keys from monitors
ceph_key:
ceph_key_info:
name: "client.rgw.{{ rgw_zone }}.{{ ansible_facts['hostname'] }}.{{ item.instance_name }}"
cluster: "{{ cluster }}"
output_format: plain
13 changes: 5 additions & 8 deletions tests/library/test_ceph_crush_rule.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import pytest
import ca_test_common
import ceph_crush_rule
import ceph_crush_rule_info

fake_cluster = 'ceph'
fake_container_binary = 'podman'
@@ -339,7 +340,6 @@ def test_remove_existing_rule(self, m_run_command, m_exit_json):
def test_get_non_existing_rule(self, m_run_command, m_exit_json):
ca_test_common.set_module_args({
'name': fake_name,
'state': 'info'
})
m_exit_json.side_effect = ca_test_common.exit_json
rc = 2
@@ -348,7 +348,7 @@ def test_get_non_existing_rule(self, m_run_command, m_exit_json):
m_run_command.return_value = rc, stdout, stderr

with pytest.raises(ca_test_common.AnsibleExitJson) as result:
ceph_crush_rule.main()
ceph_crush_rule_info.main()

result = result.value.args[0]
assert not result['changed']
@@ -364,7 +364,6 @@ def test_get_non_existing_rule(self, m_run_command, m_exit_json):
def test_get_existing_rule(self, m_run_command, m_exit_json):
ca_test_common.set_module_args({
'name': fake_name,
'state': 'info'
})
m_exit_json.side_effect = ca_test_common.exit_json
rc = 0
@@ -373,7 +372,7 @@ def test_get_existing_rule(self, m_run_command, m_exit_json):
m_run_command.return_value = rc, stdout, stderr

with pytest.raises(ca_test_common.AnsibleExitJson) as result:
ceph_crush_rule.main()
ceph_crush_rule_info.main()

result = result.value.args[0]
assert not result['changed']
@@ -389,7 +388,6 @@ def test_get_existing_rule(self, m_run_command, m_exit_json):
def test_get_all_rules(self, m_run_command, m_exit_json):
ca_test_common.set_module_args({
'name': str(),
'state': 'info'
})
m_exit_json.side_effect = ca_test_common.exit_json
rc = 0
@@ -398,7 +396,7 @@ def test_get_all_rules(self, m_run_command, m_exit_json):
m_run_command.return_value = rc, stdout, stderr

with pytest.raises(ca_test_common.AnsibleExitJson) as result:
ceph_crush_rule.main()
ceph_crush_rule_info.main()

result = result.value.args[0]
assert not result['changed']
@@ -416,7 +414,6 @@ def test_get_all_rules(self, m_run_command, m_exit_json):
def test_with_container(self, m_run_command, m_exit_json):
ca_test_common.set_module_args({
'name': fake_name,
'state': 'info'
})
m_exit_json.side_effect = ca_test_common.exit_json
rc = 0
@@ -425,7 +422,7 @@ def test_with_container(self, m_run_command, m_exit_json):
m_run_command.return_value = rc, stdout, stderr

with pytest.raises(ca_test_common.AnsibleExitJson) as result:
ceph_crush_rule.main()
ceph_crush_rule_info.main()

result = result.value.args[0]
assert not result['changed']
7 changes: 4 additions & 3 deletions tests/library/test_ceph_key.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import pytest
import ca_test_common
import ceph_key
import ceph_key_info


@mock.patch.dict(os.environ, {'CEPH_CONTAINER_BINARY': 'docker'})
@@ -537,7 +538,7 @@ def test_build_key_path_bootstrap_osd(self):
assert result == expected_result

@mock.patch('ansible.module_utils.basic.AnsibleModule.exit_json')
@mock.patch('ceph_key.exec_commands')
@mock.patch('ceph_key_info.exec_commands')
@pytest.mark.parametrize('output_format', ['json', 'plain', 'xml', 'yaml'])
def test_state_info(self, m_exec_commands, m_exit_json, output_format):
ca_test_common.set_module_args({"state": "info",
@@ -551,7 +552,7 @@ def test_state_info(self, m_exec_commands, m_exit_json, output_format):
'exported keyring for client.admin')

with pytest.raises(ca_test_common.AnsibleExitJson) as result:
ceph_key.run_module()
ceph_key_info.run_module()

result = result.value.args[0]
assert not result['changed']
@@ -570,7 +571,7 @@ def test_state_info_invalid_format(self, m_fail_json):
m_fail_json.side_effect = ca_test_common.fail_json

with pytest.raises(ca_test_common.AnsibleFailJson) as result:
ceph_key.run_module()
ceph_key_info.run_module()

result = result.value.args[0]
assert result['msg'] == 'value of output_format must be one of: json, plain, xml, yaml, got: {}'.format(invalid_format)

0 comments on commit 46ddd04

Please sign in to comment.