diff --git a/plugins/module_utils/cluster_utils.py b/plugins/module_utils/cluster_utils.py index 203d4230..bf3cae1f 100644 --- a/plugins/module_utils/cluster_utils.py +++ b/plugins/module_utils/cluster_utils.py @@ -17,7 +17,7 @@ """ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( - _parse_output, + normalize_output, ) from cm_client import ApiCluster @@ -42,5 +42,5 @@ def parse_cluster_result(cluster: ApiCluster) -> dict: # Retrieve full_version as version output = dict(version=cluster.full_version) - output.update(_parse_output(cluster.to_dict(), CLUSTER_OUTPUT)) + output.update(normalize_output(cluster.to_dict(), CLUSTER_OUTPUT)) return output diff --git a/plugins/module_utils/cm_utils.py b/plugins/module_utils/cm_utils.py index a1667986..c892b229 100644 --- a/plugins/module_utils/cm_utils.py +++ b/plugins/module_utils/cm_utils.py @@ -35,8 +35,6 @@ ApiClient, ApiCommand, ApiConfigList, - ApiRole, - ApiRoleConfigGroup, Configuration, ) from cm_client.rest import ApiException, RESTClientObject @@ -47,34 +45,8 @@ __credits__ = ["frisch@cloudera.com"] __maintainer__ = ["wmudge@cloudera.com"] -ROLE_OUTPUT = [ - "commission_state", - "config_staleness_status", - "ha_status", - "health_checks", - "health_summary", - # "host_ref", - "maintenance_mode", - "maintenance_owners", - "name", - # "role_config_group_ref", - "role_state", - # "service_ref", - "tags", - "type", - "zoo_keeper_server_mode", -] - -ROLE_CONFIG_GROUP = [ - "name", - "role_type", - "base", - "display_name", - # "service_ref", -] - - -def _parse_output(entity: dict, filter: list) -> dict: + +def normalize_output(entity: dict, filter: list) -> dict: output = {} for k in filter: if k == "tags": @@ -85,24 +57,6 @@ def _parse_output(entity: dict, filter: list) -> dict: return output -def parse_role_result(role: ApiRole) -> dict: - # Retrieve only the host_id, role_config_group, and service identifiers - output = dict( - host_id=role.host_ref.host_id, - role_config_group_name=role.role_config_group_ref.role_config_group_name, - service_name=role.service_ref.service_name, - ) - output.update(_parse_output(role.to_dict(), ROLE_OUTPUT)) - return output - - -def parse_role_config_group_result(role_config_group: ApiRoleConfigGroup) -> dict: - # Retrieve only the service identifier - output = dict(service_name=role_config_group.service_ref.service_name) - output.update(_parse_output(role_config_group.to_dict(), ROLE_CONFIG_GROUP)) - return output - - def normalize_values(add: dict) -> dict: """Normalize parameter values. Strings have whitespace trimmed, integers are converted to strings, and Boolean values are converted their string representation diff --git a/plugins/module_utils/data_context_utils.py b/plugins/module_utils/data_context_utils.py index 4b3f54f7..be4f7c57 100644 --- a/plugins/module_utils/data_context_utils.py +++ b/plugins/module_utils/data_context_utils.py @@ -17,7 +17,7 @@ """ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( - _parse_output, + normalize_output, ) from cm_client import ApiDataContextList @@ -38,9 +38,9 @@ ] -def _parse_output(data: dict, keys: list) -> dict: +def normalize_output(data: dict, keys: list) -> dict: return {key: data[key] for key in keys if key in data} def parse_data_context_result(data_contexts: ApiDataContextList) -> list: - return [_parse_output(item, DATA_CONTEXT_OUTPUT) for item in data_contexts.items] + return [normalize_output(item, DATA_CONTEXT_OUTPUT) for item in data_contexts.items] diff --git a/plugins/module_utils/parcel_utils.py b/plugins/module_utils/parcel_utils.py index 88d13793..38a50c5a 100644 --- a/plugins/module_utils/parcel_utils.py +++ b/plugins/module_utils/parcel_utils.py @@ -23,7 +23,7 @@ from cm_client import ApiParcel, ParcelResourceApi from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( - _parse_output, + normalize_output, ) @@ -138,5 +138,5 @@ def activate(self): def parse_parcel_result(parcel: ApiParcel) -> dict: # Retrieve only the cluster identifier output = dict(cluster_name=parcel.cluster_ref.cluster_name) - output.update(_parse_output(parcel.to_dict(), PARCEL)) + output.update(normalize_output(parcel.to_dict(), PARCEL)) return output diff --git a/plugins/module_utils/role_config_group_utils.py b/plugins/module_utils/role_config_group_utils.py new file mode 100644 index 00000000..b17e8160 --- /dev/null +++ b/plugins/module_utils/role_config_group_utils.py @@ -0,0 +1,35 @@ +# Copyright 2024 Cloudera, 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 +# +# https://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 ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( + normalize_output, +) + +from cm_client import ApiRoleConfigGroup + + +ROLE_CONFIG_GROUP = [ + "name", + "role_type", + "base", + "display_name", + # "service_ref", +] + + +def parse_role_config_group_result(role_config_group: ApiRoleConfigGroup) -> dict: + # Retrieve only the service identifier + output = dict(service_name=role_config_group.service_ref.service_name) + output.update(normalize_output(role_config_group.to_dict(), ROLE_CONFIG_GROUP)) + return output diff --git a/plugins/module_utils/role_utils.py b/plugins/module_utils/role_utils.py new file mode 100644 index 00000000..55bb463b --- /dev/null +++ b/plugins/module_utils/role_utils.py @@ -0,0 +1,48 @@ +# Copyright 2024 Cloudera, 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 +# +# https://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 ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( + normalize_output, +) + +from cm_client import ApiRole + +ROLE_OUTPUT = [ + "commission_state", + "config_staleness_status", + "ha_status", + "health_checks", + "health_summary", + # "host_ref", + "maintenance_mode", + "maintenance_owners", + "name", + # "role_config_group_ref", + "role_state", + # "service_ref", + "tags", + "type", + "zoo_keeper_server_mode", +] + + +def parse_role_result(role: ApiRole) -> dict: + # Retrieve only the host_id, role_config_group, and service identifiers + output = dict( + host_id=role.host_ref.host_id, + role_config_group_name=role.role_config_group_ref.role_config_group_name, + service_name=role.service_ref.service_name, + ) + output.update(normalize_output(role.to_dict(), ROLE_OUTPUT)) + return output diff --git a/plugins/module_utils/service_utils.py b/plugins/module_utils/service_utils.py index c11a2d79..9e65bff3 100644 --- a/plugins/module_utils/service_utils.py +++ b/plugins/module_utils/service_utils.py @@ -17,7 +17,7 @@ """ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( - _parse_output, + normalize_output, resolve_parameter_updates, ) @@ -47,10 +47,15 @@ def parse_service_result(service: ApiService) -> dict: # Retrieve only the cluster_name output = dict(cluster_name=service.cluster_ref.cluster_name) - output.update(_parse_output(service.to_dict(), SERVICE_OUTPUT)) + output.update(normalize_output(service.to_dict(), SERVICE_OUTPUT)) return output +def parse_cm_service_result(service: ApiService) -> dict: + # Ignore cluster_name + return normalize_output(service.to_dict(), SERVICE_OUTPUT) + + class ServiceConfigUpdates(object): def __init__(self, existing: ApiServiceConfig, updates: dict, purge: bool) -> None: current = {r.name: r.value for r in existing.items} diff --git a/plugins/modules/cm_service.py b/plugins/modules/cm_service.py index 921981cf..6528169d 100644 --- a/plugins/modules/cm_service.py +++ b/plugins/modules/cm_service.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + # Copyright 2024 Cloudera, Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,24 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( - ClouderaManagerModule, -) - -from cm_client.rest import ApiException -from cm_client import MgmtRolesResourceApi -from cm_client import MgmtServiceResourceApi -from cm_client import MgmtRoleCommandsResourceApi -from cm_client import HostsResourceApi - -ANSIBLE_METADATA = { - "metadata_version": "1.1", - "status": ["preview"], - "supported_by": "community", -} - DOCUMENTATION = r""" ---- module: cm_service short_description: Manage Cloudera Manager service roles description: @@ -68,7 +54,6 @@ """ EXAMPLES = r""" ---- - name: Start Cloudera Manager service roles cloudera.cluster.cm_version: host: "10.10.10.10" @@ -114,8 +99,7 @@ """ RETURN = r""" ---- -cloudera_manager: +service: description: List of Cloudera Manager roles type: dict contains: @@ -185,241 +169,325 @@ returned: optional """ +import json + +from cm_client import ( + HostsResourceApi, + MgmtRolesResourceApi, + MgmtRoleConfigGroupsResourceApi, + MgmtRoleCommandsResourceApi, + MgmtServiceResourceApi, +) +from cm_client.rest import ApiException + +from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( + ClouderaManagerMutableModule, +) +from ansible_collections.cloudera.cluster.plugins.module_utils.service_utils import ( + ServiceConfigUpdates, + parse_cm_service_result, +) +from ansible_collections.cloudera.cluster.plugins.module_utils.role_utils import ( + parse_role_result, +) +from ansible_collections.cloudera.cluster.plugins.module_utils.role_config_group_utils import ( + parse_role_config_group_result, +) + -class ClouderaService(ClouderaManagerModule): +class ClouderaManagerService(ClouderaManagerMutableModule): def __init__(self, module): - super(ClouderaService, self).__init__(module) + super(ClouderaManagerService, self).__init__(module) - self.role = self.get_param("role") + # Set the parameters + self.params = self.get_param("parameters") + self.roles = self.get_param("roles") self.state = self.get_param("state") self.purge = self.get_param("purge") + self.view = self.get_param("view") + + # Initialize the return value + self.changed = False + self.cm_service = {} + + if self.module._diff: + self.diff = dict(before=dict(), after=dict()) + else: + self.diff = {} + + # Execute the logic self.process() - @ClouderaManagerModule.handle_process + @ClouderaManagerMutableModule.handle_process def process(self): - try: - api_instance = MgmtServiceResourceApi(self.api_client) - role_api_instance = MgmtRolesResourceApi(self.api_client) - role_cmd_api_instance = MgmtRoleCommandsResourceApi(self.api_client) - mgmt_service_api_instance = MgmtServiceResourceApi(self.api_client) - host_api_instance = HostsResourceApi(self.api_client) - - get_host_infomation = host_api_instance.read_hosts().to_dict() - for item in get_host_infomation["items"]: - if self.host == item["hostname"]: - host_id = item["host_id"] - - if not self.purge: - available_roles_info = role_api_instance.read_roles().to_dict() - existing_roles = [] - for item in available_roles_info["items"]: - existing_roles.append(item["type"]) - - if self.state in ["present"]: - not_existing_roles = [] - for role in self.role: - if role not in existing_roles: - not_existing_roles.append(role) - if not_existing_roles: - body = { - "items": [ - {"type": role, "hostRef": {"hostId": host_id}} - for role in not_existing_roles - ] - } - role_api_instance.create_roles(body=body) - self.cm_service_output = role_api_instance.read_roles().to_dict() - self.changed = True - elif self.state in ["absent"]: - roles_to_remove = [ - role for role in self.role if role in existing_roles - ] - roles_to_remove_extended_info = [] - for role in roles_to_remove: - for item in available_roles_info["items"]: - if role == item["type"]: - roles_to_remove_extended_info.append(item["name"]) - if not roles_to_remove_extended_info: - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() - ) - self.changed = False - else: - for role in roles_to_remove_extended_info: - role_api_instance.delete_role(role_name=role) - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() + service_api = MgmtServiceResourceApi(self.api_client) + role_api = MgmtRolesResourceApi(self.api_client) + role_cmd_api = MgmtRoleCommandsResourceApi(self.api_client) + rcg_api = MgmtRoleConfigGroupsResourceApi(self.api_client) + host_api = HostsResourceApi(self.api_client) + + # Manage service-wide configurations + if self.params or self.purge: + try: + existing_params = service_api.read_service_config() + except ApiException as ex: + if ex.status == 404: + self.module.fail_json(msg=json.loads(ex.body)["message"]) + else: + raise ex + + service_wide = ServiceConfigUpdates( + existing_params, self.params, self.purge + ) + + if service_wide.changed: + self.changed = True + + if self.module._diff: + self.diff["before"].update(params=service_wide.diff["before"]) + self.diff["after"].update(params=service_wide.diff["after"]) + + if not self.module.check_mode: + service_api.update_service_config( + message=self.message, body=service_wide.config + ) + + # Manage roles + if self.roles: + try: + # Get a list of all host and find itself + # This is hardcoded, so needs to be broken into host + # assignment per-role + hosts = host_api.read_hosts() + for h in hosts.items(): + if self.host == h.hostname: + host_id = h.host_id + + # CHECK MODE + if not self.purge: + available_roles_info = role_api.read_roles().to_dict() + existing_roles = [] + for item in available_roles_info["items"]: + existing_roles.append(item["type"]) + + if self.state in ["present"]: + not_existing_roles = [] + for role in self.roles: + if role not in existing_roles: + not_existing_roles.append(role) + if not_existing_roles: + body = { + "items": [ + {"type": role, "hostRef": {"hostId": host_id}} + for role in not_existing_roles + ] + } + role_api.create_roles(body=body) + self.cm_service = parse_cm_service_result( + service_api.read_service() ) self.changed = True - elif self.state in ["started"]: - - matching_roles = [] - new_roles = [] - for role in self.role: - if role in existing_roles: - matching_roles.append(role) + elif self.state in ["absent"]: + roles_to_remove = [ + role for role in self.roles if role in existing_roles + ] + roles_to_remove_extended_info = [] + for role in roles_to_remove: + for item in available_roles_info["items"]: + if role == item["type"]: + roles_to_remove_extended_info.append(item["name"]) + if not roles_to_remove_extended_info: + self.cm_service = role_api.read_roles().to_dict() + self.changed = False + else: + for role in roles_to_remove_extended_info: + role_api.delete_role(role_name=role) + self.cm_service = role_api.read_roles().to_dict() + self.changed = True + + elif self.state in ["started"]: + + matching_roles = [] + new_roles = [] + for role in self.roles: + if role in existing_roles: + matching_roles.append(role) + else: + new_roles.append(role) + + new_roles_to_start = [] + if new_roles: + body = { + "items": [ + {"type": role, "hostRef": {"hostId": host_id}} + for role in new_roles + ] + } + newly_added_roles = role_api.create_roles( + body=body + ).to_dict() + + for role in newly_added_roles["items"]: + new_roles_to_start.append(role["name"]) + body = {"items": new_roles_to_start} + + existing_roles_state = [] + for role in matching_roles: + for item in available_roles_info["items"]: + if role == item["type"]: + existing_roles_state.append( + { + "type": item["type"], + "role_state": item["role_state"].lower(), + "name": item["name"], + } + ) + + existing_roles_to_start = [] + for role in existing_roles_state: + if role["role_state"] == "stopped": + existing_roles_to_start.append(role["name"]) + + all_roles_to_start = ( + new_roles_to_start + existing_roles_to_start + ) + body = {"items": all_roles_to_start} + + if all_roles_to_start: + start_roles_request = role_cmd_api.start_command( + body=body + ).to_dict() + command_id = start_roles_request["items"][0]["id"] + self.wait_for_command_state( + command_id=command_id, polling_interval=5 + ) + self.cm_service = role_api.read_roles().to_dict() + self.changed = True + else: + self.cm_service = role_api.read_roles().to_dict() + self.changed = False + + elif self.state in ["stopped"]: + matching_roles = [] + for role in self.roles: + if role in existing_roles: + matching_roles.append(role) + + matching_roles_state = [] + for role in matching_roles: + for item in available_roles_info["items"]: + if role == item["type"]: + matching_roles_state.append( + { + "type": item["type"], + "role_state": item["role_state"].lower(), + "name": item["name"], + } + ) + + roles_to_stop = [] + for role in matching_roles_state: + if role["role_state"] == "started": + roles_to_stop.append(role["name"]) + body = {"items": roles_to_stop} + + if roles_to_stop: + role_cmd_api.stop_command(body=body) + self.cm_service = role_api.read_roles().to_dict() + self.changed = True else: - new_roles.append(role) - - new_roles_to_start = [] - if new_roles: - body = { - "items": [ - {"type": role, "hostRef": {"hostId": host_id}} - for role in new_roles - ] - } - newly_added_roles = role_api_instance.create_roles( - body=body - ).to_dict() - - for role in newly_added_roles["items"]: - new_roles_to_start.append(role["name"]) - body = {"items": new_roles_to_start} - - existing_roles_state = [] - for role in matching_roles: - for item in available_roles_info["items"]: - if role == item["type"]: - existing_roles_state.append( - { - "type": item["type"], - "role_state": item["role_state"].lower(), - "name": item["name"], - } - ) - - existing_roles_to_start = [] - for role in existing_roles_state: - if role["role_state"] == "stopped": - existing_roles_to_start.append(role["name"]) - - all_roles_to_start = new_roles_to_start + existing_roles_to_start - body = {"items": all_roles_to_start} - - if all_roles_to_start: - start_roles_request = role_cmd_api_instance.start_command( - body=body - ).to_dict() - command_id = start_roles_request["items"][0]["id"] + self.cm_service = role_api.read_roles().to_dict() + self.changed = False + + elif self.state in ["restarted"]: + matching_roles = [] + for role in self.roles: + if role in existing_roles: + matching_roles.append(role) + + matching_roles_state = [] + for role in matching_roles: + for item in available_roles_info["items"]: + if role == item["type"]: + matching_roles_state.append( + { + "type": item["type"], + "role_state": item["role_state"].lower(), + "name": item["name"], + } + ) + + roles_to_restart = [] + for role in matching_roles_state: + roles_to_restart.append(role["name"]) + body = {"items": roles_to_restart} + + if roles_to_restart: + role_cmd_api.restart_command(body=body) + self.cm_service = role_api.read_roles().to_dict() + self.changed = True + + if self.purge: + service_api.delete_cms() + body = {"roles": [{"type": role} for role in self.roles]} + service_api.setup_cms(body=body) + self.cm_service = role_api.read_roles().to_dict() + + if self.state in ["started"]: + start_roles_request = service_api.start_command().to_dict() + command_id = start_roles_request["id"] self.wait_for_command_state( command_id=command_id, polling_interval=5 ) - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() + self.cm_service = role_api.read_roles().to_dict() + self.changed = True + except ApiException as e: + if e.status == 404 or 400: + roles_dict = {"roles": [{"type": role} for role in self.roles]} + service_api.setup_cms(body=roles_dict) + + if self.state in ["started"]: + start_roles_request = service_api.start_command().to_dict() + command_id = start_roles_request["id"] + self.wait_for_command_state( + command_id=command_id, polling_interval=5 ) - self.changed = True + self.cm_service = role_api.read_roles().to_dict() else: - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() - ) - self.changed = False - - elif self.state in ["stopped"]: - matching_roles = [] - for role in self.role: - if role in existing_roles: - matching_roles.append(role) - - matching_roles_state = [] - for role in matching_roles: - for item in available_roles_info["items"]: - if role == item["type"]: - matching_roles_state.append( - { - "type": item["type"], - "role_state": item["role_state"].lower(), - "name": item["name"], - } - ) - - roles_to_stop = [] - for role in matching_roles_state: - if role["role_state"] == "started": - roles_to_stop.append(role["name"]) - body = {"items": roles_to_stop} - - if roles_to_stop: - role_cmd_api_instance.stop_command(body=body) - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() - ) - self.changed = True - else: - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() - ) - self.changed = False - - elif self.state in ["restarted"]: - matching_roles = [] - for role in self.role: - if role in existing_roles: - matching_roles.append(role) - - matching_roles_state = [] - for role in matching_roles: - for item in available_roles_info["items"]: - if role == item["type"]: - matching_roles_state.append( - { - "type": item["type"], - "role_state": item["role_state"].lower(), - "name": item["name"], - } - ) - - roles_to_restart = [] - for role in matching_roles_state: - roles_to_restart.append(role["name"]) - body = {"items": roles_to_restart} - - if roles_to_restart: - role_cmd_api_instance.restart_command(body=body) - self.cm_service_output = ( - role_api_instance.read_roles().to_dict() - ) - self.changed = True - - if self.purge: - mgmt_service_api_instance.delete_cms() - body = {"roles": [{"type": role} for role in self.role]} - mgmt_service_api_instance.setup_cms(body=body) - self.cm_service_output = role_api_instance.read_roles().to_dict() - - if self.state in ["started"]: - start_roles_request = api_instance.start_command().to_dict() - command_id = start_roles_request["id"] - self.wait_for_command_state( - command_id=command_id, polling_interval=5 - ) - self.cm_service_output = role_api_instance.read_roles().to_dict() - self.changed = True - - except ApiException as e: - if e.status == 404 or 400: - roles_dict = {"roles": [{"type": role} for role in self.role]} - api_instance.setup_cms(body=roles_dict) + self.cm_service = role_api.read_roles().to_dict() + self.changed = True - if self.state in ["started"]: - start_roles_request = api_instance.start_command().to_dict() - command_id = start_roles_request["id"] - self.wait_for_command_state( - command_id=command_id, polling_interval=5 - ) - self.cm_service_output = role_api_instance.read_roles().to_dict() - else: - self.cm_service_output = role_api_instance.read_roles().to_dict() - self.changed = True + # Read and generate payload for Cloudera Manager Service + self.cm_service = parse_cm_service_result(service_api.read_service()) + self.cm_service.update( + config=[ + c.to_dict() + for c in service_api.read_service_config(view=self.view).items + ] + ) + self.cm_service.update( + roles=[parse_role_result(r) for r in role_api.read_roles().items] + ) + self.cm_service.update( + role_config_groups=[ + parse_role_config_group_result(rcg) + for rcg in rcg_api.read_role_config_groups().items + ] + ) def main(): - module = ClouderaManagerModule.ansible_module( + module = ClouderaManagerMutableModule.ansible_module( argument_spec=dict( - role=dict(required=True, type="list"), - purge=dict(required=False, type="bool", default=False), + parameters=dict(type="dict", aliases=["params"]), + roles=dict(type="list"), + purge=dict(type="bool", default=False), + view=dict( + default="summary", + choices=["summary", "full"], + ), state=dict( type="str", default="started", @@ -429,13 +497,13 @@ def main(): supports_check_mode=False, ) - result = ClouderaService(module) + result = ClouderaManagerService(module) changed = result.changed output = dict( changed=changed, - cloudera_manager=result.cm_service_output, + service=result.cm_service, ) if result.debug: diff --git a/plugins/modules/service_role.py b/plugins/modules/service_role.py index f9bed4a1..d92ca533 100644 --- a/plugins/modules/service_role.py +++ b/plugins/modules/service_role.py @@ -16,9 +16,11 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerMutableModule, - parse_role_result, resolve_tag_updates, ) +from ansible_collections.cloudera.cluster.plugins.module_utils.role_utils import ( + parse_role_result, +) from cm_client import ( ApiEntityTag, @@ -34,6 +36,7 @@ ) from cm_client.rest import ApiException + ANSIBLE_METADATA = { "metadata_version": "1.1", "status": ["preview"], diff --git a/plugins/modules/service_role_config_group.py b/plugins/modules/service_role_config_group.py index 5d1f4449..b54ffeef 100644 --- a/plugins/modules/service_role_config_group.py +++ b/plugins/modules/service_role_config_group.py @@ -16,6 +16,9 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerMutableModule, +) + +from ansible_collections.cloudera.cluster.plugins.module_utils.role_config_group_utils import ( parse_role_config_group_result, ) diff --git a/plugins/modules/service_role_config_group_config_info.py b/plugins/modules/service_role_config_group_config_info.py index fc127dc8..ba25a6cb 100644 --- a/plugins/modules/service_role_config_group_config_info.py +++ b/plugins/modules/service_role_config_group_config_info.py @@ -16,7 +16,6 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerModule, - parse_role_config_group_result, ) from cm_client import ( @@ -26,6 +25,7 @@ ) from cm_client.rest import ApiException + ANSIBLE_METADATA = { "metadata_version": "1.1", "status": ["preview"], diff --git a/plugins/modules/service_role_config_group_info.py b/plugins/modules/service_role_config_group_info.py index 46e95af4..cc71314b 100644 --- a/plugins/modules/service_role_config_group_info.py +++ b/plugins/modules/service_role_config_group_info.py @@ -16,6 +16,9 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerModule, +) + +from ansible_collections.cloudera.cluster.plugins.module_utils.role_config_group_utils import ( parse_role_config_group_result, ) @@ -26,6 +29,7 @@ ) from cm_client.rest import ApiException + ANSIBLE_METADATA = { "metadata_version": "1.1", "status": ["preview"], diff --git a/plugins/modules/service_role_info.py b/plugins/modules/service_role_info.py index c0e1f63f..9581a8bb 100644 --- a/plugins/modules/service_role_info.py +++ b/plugins/modules/service_role_info.py @@ -18,12 +18,16 @@ from ansible_collections.cloudera.cluster.plugins.module_utils.cm_utils import ( ClouderaManagerModule, +) + +from ansible_collections.cloudera.cluster.plugins.module_utils.role_utils import ( parse_role_result, ) from cm_client import ClustersResourceApi, RolesResourceApi, ServicesResourceApi from cm_client.rest import ApiException + ANSIBLE_METADATA = { "metadata_version": "1.1", "status": ["preview"], diff --git a/pyproject.toml b/pyproject.toml index a36945c5..e46f1c79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ filterwarnings = [ "ignore:'crypt' is deprecated and slated for removal in Python 3.13:DeprecationWarning", ] markers = [ - "prepare: Prepare Cloudera Manager and resources for tests", + "service_config: Prepare service-wide configurations for tests", ] [build-system] diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 5fb502e1..268192db 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -295,7 +295,7 @@ def cms_service_config(cm_api_client, cms, request): for k, v in marker.args[0].items(): try: api.update_service_config( - message=f"{request.node.name}::set", + message=f"{Path(request.node.parent.name).stem}::{request.node.name}::set", body=ApiServiceConfig(items=[ApiConfig(name=k, value=v)]), ) except ApiException as ae: @@ -321,6 +321,6 @@ def cms_service_config(cm_api_client, cms, request): ) api.update_service_config( - message=f"{request.node.name}::reset", + message=f"{Path(request.node.parent.name).stem}::{request.node.name}::reset", body=ApiServiceConfig(items=reconciled), ) diff --git a/tests/unit/plugins/modules/cm_service/test_cm_service.py b/tests/unit/plugins/modules/cm_service/test_cm_service.py index 5614fe61..af679312 100644 --- a/tests/unit/plugins/modules/cm_service/test_cm_service.py +++ b/tests/unit/plugins/modules/cm_service/test_cm_service.py @@ -17,7 +17,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import os + import logging import pytest @@ -30,23 +30,144 @@ LOG = logging.getLogger(__name__) -def test_pytest_cm_service(module_args): +def test_minimal(conn, module_args, cms): + module_args(conn) + + with pytest.raises(AnsibleExitJson): + cm_service.main() + + +@pytest.mark.service_config(dict(log_event_retry_frequency=10)) +def test_set_parameters(conn, module_args, cms_service_config): + module_args( + { + **conn, + "parameters": dict(mgmt_emit_sensitive_data_in_stderr=True), + "message": "test_cm_service::test_set_parameters", + # _ansible_check_mode=True, + # _ansible_diff=True, + } + ) + + expected = dict( + mgmt_emit_sensitive_data_in_stderr="True", log_event_retry_frequency="10" + ) + + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == True + assert ( + expected.items() + <= {c["name"]: c["value"] for c in e.value.service["config"]}.items() + ) + + # Idempotency + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == False + assert ( + expected.items() + <= {c["name"]: c["value"] for c in e.value.service["config"]}.items() + ) + + +@pytest.mark.service_config( + dict(mgmt_emit_sensitive_data_in_stderr=True, log_event_retry_frequency=10) +) +def test_unset_parameters(conn, module_args, cms_service_config): + module_args( + { + **conn, + "parameters": dict(mgmt_emit_sensitive_data_in_stderr=None), + "message": "test_cm_service::test_unset_parameters", + } + ) + + expected = dict(log_event_retry_frequency="10") + + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == True + assert ( + expected.items() + <= {c["name"]: c["value"] for c in e.value.service["config"]}.items() + ) + + # Idempotency + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == False + assert ( + expected.items() + <= {c["name"]: c["value"] for c in e.value.service["config"]}.items() + ) + + +@pytest.mark.service_config( + dict(mgmt_emit_sensitive_data_in_stderr=True, log_event_retry_frequency=10) +) +def test_set_parameters_with_purge(conn, module_args, cms_service_config): + module_args( + { + **conn, + "parameters": dict(mgmt_emit_sensitive_data_in_stderr=True), + "purge": True, + "message": "test_cm_service::test_set_parameters_with_purge", + # _ansible_check_mode=True, + # _ansible_diff=True, + } + ) + + expected = dict(mgmt_emit_sensitive_data_in_stderr="True") + + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == True + assert ( + expected.items() + <= {c["name"]: c["value"] for c in e.value.service["config"]}.items() + ) + + # Idempotency + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == False + assert ( + expected.items() + <= {c["name"]: c["value"] for c in e.value.service["config"]}.items() + ) + + +@pytest.mark.service_config( + dict(mgmt_emit_sensitive_data_in_stderr=True, log_event_retry_frequency=10) +) +def test_purge_all_parameters(conn, module_args, cms_service_config): module_args( { - "username": os.getenv("CM_USERNAME"), - "password": os.getenv("CM_PASSWORD"), - "host": os.getenv("CM_HOST"), - "port": "7180", - "verify_tls": "no", - "debug": "yes", - "state": "started", - "role": ["SERVICEMONITOR", "HOSTMONITOR", "EVENTSERVER", "ALERTPUBLISHER"], + **conn, + "parameters": dict(), + "purge": True, + "message": "test_cm_service::test_purge_all_parameters", + # _ansible_check_mode=True, + # _ansible_diff=True, } ) - # with pytest.raises(AnsibleFailJson, match=r"boom") as e: with pytest.raises(AnsibleExitJson) as e: cm_service.main() - # LOG.info(str(e.value)) - LOG.info(str(e.value.cloudera_manager)) + assert e.value.changed == True + assert len(e.value.service["config"]) == 0 + + # Idempotency + with pytest.raises(AnsibleExitJson) as e: + cm_service.main() + + assert e.value.changed == False + assert len(e.value.service["config"]) == 0 diff --git a/tests/unit/plugins/modules/cm_service_config/test_cm_service_config.py b/tests/unit/plugins/modules/cm_service_config/test_cm_service_config.py index 9e208227..ad54716b 100644 --- a/tests/unit/plugins/modules/cm_service_config/test_cm_service_config.py +++ b/tests/unit/plugins/modules/cm_service_config/test_cm_service_config.py @@ -38,10 +38,12 @@ def test_missing_required(conn, module_args): def test_present_invalid_parameter(conn, module_args): - conn.update( - parameters=dict(example="Example"), + module_args( + { + **conn, + "parameters": dict(example="Example"), + } ) - module_args(conn) with pytest.raises( AnsibleFailJson, match="Unknown configuration attribute 'example'"