From 8e451eca448cbc0ba4872a9b6cdbf7eaabf9097f Mon Sep 17 00:00:00 2001 From: Mingjun Zhang <54682183+mingjunzhang2019@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:01:53 -0700 Subject: [PATCH] Add diff and check modes for route_maps and prefix_lists modules (#331) * Add diff and check modes for route_maps and prefix_lists modules * Add fragment file * Change GitHub workflow version to fix sanilty check error * Merge with main * Address review comments and fix sanity errors * Add diff sample text for doc --- ...iff-modes-for-route_maps_prefix_lists.yaml | 3 ++ .../sonic/config/prefix_lists/prefix_lists.py | 44 +++++++++++++++++++ .../sonic/config/route_maps/route_maps.py | 40 +++++++++++++++++ .../config/static_routes/static_routes.py | 8 ++-- plugins/modules/sonic_prefix_lists.py | 37 ++++++++++++++++ plugins/modules/sonic_route_maps.py | 9 ++++ tests/sanity/ignore-3.10.txt | 1 + tests/sanity/ignore-3.11.txt | 1 + tests/sanity/ignore-3.12.txt | 1 + 9 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml create mode 100644 tests/sanity/ignore-3.10.txt create mode 100644 tests/sanity/ignore-3.11.txt create mode 100644 tests/sanity/ignore-3.12.txt diff --git a/changelogs/fragments/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml b/changelogs/fragments/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml new file mode 100644 index 000000000..23aa15fea --- /dev/null +++ b/changelogs/fragments/331-playbook-check-diff-modes-for-route_maps_prefix_lists.yaml @@ -0,0 +1,3 @@ +minor_changes: + - sonic_route_maps - Add playbook check and diff modes support for route_maps module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/331). + - sonic_prefix_lists - Add playbook check and diff modes support for prefix_lists module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/331). diff --git a/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py b/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py index 879d24d80..2b037381b 100644 --- a/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py +++ b/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py @@ -30,6 +30,7 @@ import ( remove_empties_from_list, update_states, + remove_empties_from_list, ) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( @@ -37,6 +38,12 @@ edit_config ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + __DELETE_CONFIG_IF_NO_SUBCONFIG, + get_new_config, + get_formatted_config_diff +) + # from ansible.module_utils.connection import ConnectionError TEST_KEYS = [ @@ -44,6 +51,11 @@ {"prefixes": {"ge": "", "le": "", "prefix": "", "sequence": ""}} ] +TEST_KEYS_generate_config = [ + {"config": {"afi": "", "name": "", '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}}, + {"prefixes": {"ge": "", "le": "", "prefix": "", "sequence": "", '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}} +] + DELETE = "delete" PATCH = "patch" @@ -117,6 +129,23 @@ def execute_module(self): if result['changed']: result['after'] = changed_prefix_lists_facts + new_config = changed_prefix_lists_facts + old_config = existing_prefix_lists_facts + if self._module.check_mode: + result.pop('after', None) + new_config = get_new_config(commands, existing_prefix_lists_facts, + TEST_KEYS_generate_config) + new_config = self.post_process_generated_config(new_config) + result['after(generated)'] = new_config + + if self._module._diff: + new_config = remove_empties_from_list(new_config) + old_config = remove_empties_from_list(old_config) + self.sort_lists_in_config(new_config) + self.sort_lists_in_config(old_config) + result['diff'] = get_formatted_config_diff(old_config, + new_config, + self._module._verbosity) result['warnings'] = warnings return result @@ -597,6 +626,21 @@ def set_ipaddress_net_attrs(self, prefix_val, conf_afi): prefix_net['prefixlen'] = int(prefix_val.split("/")[1]) return prefix_net + def sort_lists_in_config(self, config): + if config: + config.sort(key=lambda x: x['name']) + for cfg in config: + if 'prefixes' in cfg and cfg['prefixes']: + cfg['prefixes'].sort(key=lambda x: (x['sequence'], x['action'], x['prefix'])) + + def post_process_generated_config(self, configs): + confs = remove_empties_from_list(configs) + if confs: + for conf in confs[:]: + if not conf.get('prefixes', None): + confs.remove(conf) + return confs + @staticmethod def _convert_config_list_to_dict(config_list): config_dict = {} diff --git a/plugins/module_utils/network/sonic/config/route_maps/route_maps.py b/plugins/module_utils/network/sonic/config/route_maps/route_maps.py index 0b40c30f2..380797a56 100644 --- a/plugins/module_utils/network/sonic/config/route_maps/route_maps.py +++ b/plugins/module_utils/network/sonic/config/route_maps/route_maps.py @@ -35,6 +35,11 @@ get_normalize_interface_name, check_required ) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import ( + __DELETE_CONFIG_IF_NO_SUBCONFIG, + get_new_config, + get_formatted_config_diff +) from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( to_request, edit_config @@ -45,6 +50,10 @@ {"config": {"map_name": "", "sequence_num": ""}} ] +TEST_KEYS_generate_config = [ + {"config": {"map_name": "", "sequence_num": "", '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}} +] + DELETE = "delete" PATCH = "patch" @@ -125,6 +134,21 @@ def execute_module(self): if result['changed']: result['after'] = changed_route_maps_facts + new_config = changed_route_maps_facts + old_config = existing_route_maps_facts + if self._module.check_mode: + result.pop('after', None) + new_config = get_new_config(commands, existing_route_maps_facts, + TEST_KEYS_generate_config) + new_config = self.post_process_generated_config(new_config) + result['after(generated)'] = new_config + + if self._module._diff: + self.sort_lists_in_config(new_config) + self.sort_lists_in_config(old_config) + result['diff'] = get_formatted_config_diff(old_config, + new_config, + self._module._verbosity) result['warnings'] = warnings return result @@ -2352,3 +2376,19 @@ def validate_and_normalize_config(self, input_config_list): route_map['match']['peer']['interface'] = updated_intf_name return updated_config_list + + def sort_lists_in_config(self, config): + if config: + config.sort(key=lambda x: (x['map_name'], + x.get('sequence_num') if x.get('sequence_num') is not None else 0)) + + def post_process_generated_config(self, configs): + confs = remove_empties_from_list(configs) + if confs: + for conf in confs[:]: + rm_match = conf.get('match', None) + rm_set = conf.get('set', None) + rm_call = conf.get('call', None) + if not rm_match and not rm_set and not rm_call: + confs.remove(conf) + return confs diff --git a/plugins/module_utils/network/sonic/config/static_routes/static_routes.py b/plugins/module_utils/network/sonic/config/static_routes/static_routes.py index c3d62d852..63dab8c47 100644 --- a/plugins/module_utils/network/sonic/config/static_routes/static_routes.py +++ b/plugins/module_utils/network/sonic/config/static_routes/static_routes.py @@ -498,10 +498,10 @@ def sort_lists_in_config(self, config): cfg['static_list'].sort(key=self.get_prefix) for rt in cfg['static_list']: if 'next_hops' in rt and rt['next_hops']: - rt['next_hops'].sort(key=lambda x: (x['index'].get('blackhole', None) is not None, - x['index'].get('interface', None) is not None, - x['index'].get('nexthop_vrf', None) is not None, - x['index'].get('next_hop', None) is not None)) + rt['next_hops'].sort(key=lambda x: (x['index'].get('blackhole') if x['index'].get('blackhole') is not None else False, + x['index'].get('interface') if x['index'].get('interface') is not None else '', + x['index'].get('nexthop_vrf') if x['index'].get('nexthop_vrf') is not None else '', + x['index'].get('next_hop') if x['index'].get('next_hop') is not None else '')) def get_vrf_name(self, vrf_name): return vrf_name.get('vrf_name') diff --git a/plugins/modules/sonic_prefix_lists.py b/plugins/modules/sonic_prefix_lists.py index b3389b6ad..fa37adc0a 100644 --- a/plugins/modules/sonic_prefix_lists.py +++ b/plugins/modules/sonic_prefix_lists.py @@ -34,6 +34,9 @@ module: sonic_prefix_lists version_added: "2.0.0" author: Kerry Meyer (@kerry-meyer) +notes: + - Supports C(check_mode). + - Supports D(diff_mode). short_description: prefix list configuration handling for SONiC description: - This module provides configuration management for prefix list parameters on devices running SONiC. @@ -492,6 +495,40 @@ # } # ], """ +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after(generated): + description: The generated configuration model invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +diff: + description: The difference between 'before' and 'after' (or 'after(generated)'). + returned: when D(diff_mode) + type: list + sample: > + The difference shows several lines of context around the lines that differ. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" from ansible.module_utils.basic import AnsibleModule diff --git a/plugins/modules/sonic_route_maps.py b/plugins/modules/sonic_route_maps.py index 01327572e..2bc10d45b 100644 --- a/plugins/modules/sonic_route_maps.py +++ b/plugins/modules/sonic_route_maps.py @@ -35,6 +35,8 @@ module: sonic_route_maps version_added: "2.1.0" author: "Kerry Meyer (@kerry-meyer)" +notes: + - Supports C(check_mode). short_description: route map configuration handling for SONiC description: - This module provides configuration management for route map parameters on devices running SONiC. @@ -1576,6 +1578,13 @@ sample: > The configuration returned will always be in the same format as the parameters above. +after(generated): + description: The generated configuration model invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. commands: description: The set of commands pushed to the remote device. returned: always diff --git a/tests/sanity/ignore-3.10.txt b/tests/sanity/ignore-3.10.txt new file mode 100644 index 000000000..c2cf4ded5 --- /dev/null +++ b/tests/sanity/ignore-3.10.txt @@ -0,0 +1 @@ +plugins/action/sonic.py action-plugin-docs #action plugin for base class diff --git a/tests/sanity/ignore-3.11.txt b/tests/sanity/ignore-3.11.txt new file mode 100644 index 000000000..c2cf4ded5 --- /dev/null +++ b/tests/sanity/ignore-3.11.txt @@ -0,0 +1 @@ +plugins/action/sonic.py action-plugin-docs #action plugin for base class diff --git a/tests/sanity/ignore-3.12.txt b/tests/sanity/ignore-3.12.txt new file mode 100644 index 000000000..c2cf4ded5 --- /dev/null +++ b/tests/sanity/ignore-3.12.txt @@ -0,0 +1 @@ +plugins/action/sonic.py action-plugin-docs #action plugin for base class