From 8a5e5d2186c9bd7b7787d733e801ac49ebbef6d2 Mon Sep 17 00:00:00 2001 From: ArunSaravananBalachandran Date: Tue, 24 Dec 2024 20:57:21 +0530 Subject: [PATCH 1/4] Add Dell Enterprise SONiC 'ipv6_router_advertisement' module --- .../network/sonic/argspec/facts/facts.py | 1 + .../ipv6_router_advertisement/__init__.py | 0 .../ipv6_router_advertisement.py | 102 +++ .../ipv6_router_advertisement/__init__.py | 0 .../ipv6_router_advertisement.py | 588 ++++++++++++ .../module_utils/network/sonic/facts/facts.py | 4 + .../ipv6_router_advertisement/__init__.py | 0 .../ipv6_router_advertisement.py | 223 +++++ plugins/modules/sonic_facts.py | 1 + .../sonic_ipv6_router_advertisement.py | 732 +++++++++++++++ .../defaults/main.yml | 265 ++++++ .../meta/main.yml | 5 + .../tasks/cleanup_tests.yaml | 18 + .../tasks/main.yml | 10 + .../tasks/preparation_tests.yaml | 25 + .../tasks/tasks_template.yaml | 24 + tests/regression/test.yaml | 1 + .../sonic_ipv6_router_advertisement.yaml | 843 ++++++++++++++++++ .../test_sonic_ipv6_router_advertisement.py | 96 ++ 19 files changed, 2938 insertions(+) create mode 100644 plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/__init__.py create mode 100644 plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/ipv6_router_advertisement.py create mode 100644 plugins/module_utils/network/sonic/config/ipv6_router_advertisement/__init__.py create mode 100644 plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py create mode 100644 plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/__init__.py create mode 100644 plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py create mode 100644 plugins/modules/sonic_ipv6_router_advertisement.py create mode 100644 tests/regression/roles/sonic_ipv6_router_advertisement/defaults/main.yml create mode 100644 tests/regression/roles/sonic_ipv6_router_advertisement/meta/main.yml create mode 100644 tests/regression/roles/sonic_ipv6_router_advertisement/tasks/cleanup_tests.yaml create mode 100644 tests/regression/roles/sonic_ipv6_router_advertisement/tasks/main.yml create mode 100644 tests/regression/roles/sonic_ipv6_router_advertisement/tasks/preparation_tests.yaml create mode 100644 tests/regression/roles/sonic_ipv6_router_advertisement/tasks/tasks_template.yaml create mode 100644 tests/unit/modules/network/sonic/fixtures/sonic_ipv6_router_advertisement.yaml create mode 100644 tests/unit/modules/network/sonic/test_sonic_ipv6_router_advertisement.py diff --git a/plugins/module_utils/network/sonic/argspec/facts/facts.py b/plugins/module_utils/network/sonic/argspec/facts/facts.py index ecb0c3763..0938a0db3 100644 --- a/plugins/module_utils/network/sonic/argspec/facts/facts.py +++ b/plugins/module_utils/network/sonic/argspec/facts/facts.py @@ -82,6 +82,7 @@ def __init__(self, **kwargs): 'mgmt_servers', 'ospf_area', 'ssh', + 'ipv6_router_advertisement' ] argument_spec = { diff --git a/plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/__init__.py b/plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/ipv6_router_advertisement.py b/plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/ipv6_router_advertisement.py new file mode 100644 index 000000000..0a3f67a6d --- /dev/null +++ b/plugins/module_utils/network/sonic/argspec/ipv6_router_advertisement/ipv6_router_advertisement.py @@ -0,0 +1,102 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The arg spec for the sonic_ipv6_router_advertisement module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Ipv6_router_advertisementArgs(object): # pylint: disable=R0903 + """The arg spec for the sonic_ipv6_router_advertisement module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'adv_interval_option': {'type': 'bool'}, + 'dnssl': { + 'elements': 'dict', + 'options': { + 'dnssl_name': {'required': True, 'type': 'str'}, + 'valid_lifetime': {'type': 'int'} + }, + 'type': 'list' + }, + 'home_agent_config': {'type': 'bool'}, + 'home_agent_lifetime': {'type': 'int'}, + 'home_agent_preference': {'type': 'int'}, + 'managed_config': {'type': 'bool'}, + 'min_ra_interval': {'type': 'int'}, + 'min_ra_interval_msec': {'type': 'int'}, + 'mtu': {'type': 'int'}, + 'name': {'required': True, 'type': 'str'}, + 'other_config': {'type': 'bool'}, + 'ra_fast_retrans': {'type': 'bool'}, + 'ra_hop_limit': {'type': 'int'}, + 'ra_interval': {'type': 'int'}, + 'ra_interval_msec': {'type': 'int'}, + 'ra_lifetime': {'type': 'int'}, + 'ra_prefixes': { + 'elements': 'dict', + 'options': { + 'no_autoconfig': {'type': 'bool'}, + 'off_link': {'type': 'bool'}, + 'preferred_lifetime': {'type': 'int'}, + 'prefix': {'required': True, 'type': 'str'}, + 'router_address': {'type': 'bool'}, + 'valid_lifetime': {'type': 'int'} + }, + 'type': 'list' + }, + 'ra_retrans_interval': {'type': 'int'}, + 'rdnss': { + 'elements': 'dict', + 'options': { + 'address': {'required': True, 'type': 'str'}, + 'valid_lifetime': {'type': 'int'} + }, + 'type': 'list' + }, + 'reachable_time': {'type': 'int'}, + 'router_preference': { + 'choices': ['low', 'medium', 'high'], + 'type': 'str' + }, + 'suppress': {'type': 'bool'} + }, + 'type': 'list' + }, + 'state': { + 'choices': ['merged', 'deleted', 'replaced', 'overridden'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/__init__.py b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py new file mode 100644 index 000000000..ce93e4c60 --- /dev/null +++ b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py @@ -0,0 +1,588 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic_ipv6_router_advertisement class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + get_diff, + remove_empties_from_list, + update_states, + normalize_interface_name +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + 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 +) + +DELETE = 'DELETE' +PATCH = 'PATCH' + +TEST_KEYS = [ + {'dnssl': {'dnssl_name': ''}}, + {'ra_prefixes': {'prefix': ''}}, + {'rdnss': {'address'}} +] +TEST_KEYS_formatted_diff = [ + {'config': {'name': ''}}, + {'dnssl': {'dnssl_name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}}, + {'ra_prefixes': {'prefix': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}}, + {'rdnss': {'address': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}} +] + +DEFAULT_RTADV_VALUES = { + 'suppress': True, + 'ra_fast_retrans': True, + 'adv_interval_option': False, + 'home_agent_config': False, + 'managed_config': False, + 'other_config': False +} +DEFAULT_PREFIX_VALUES = { + 'off_link': False, + 'no_autoconfig': False, + 'router_address': False +} + +OPTION_TO_PAYLOAD_MAP = { + 'adv_interval_option': 'openconfig-interfaces-ext:adv-interval-option', + 'dnssl': 'openconfig-interfaces-ext:dns-search-names', + 'home_agent_config': 'openconfig-interfaces-ext:home-agent-config', + 'home_agent_lifetime': 'openconfig-interfaces-ext:home-agent-lifetime', + 'home_agent_preference': 'openconfig-interfaces-ext:home-agent-preference', + 'managed_config': 'openconfig-interfaces-ext:managed-config', + 'min_ra_interval': 'openconfig-interfaces-ext:min-ra-interval', + 'min_ra_interval_msec': 'openconfig-interfaces-ext:min-ra-interval-msec', + 'mtu': 'openconfig-interfaces-ext:mtu', + 'other_config': 'openconfig-interfaces-ext:other-config', + 'ra_fast_retrans': 'openconfig-interfaces-ext:ra-fast-retrans', + 'ra_hop_limit': 'openconfig-interfaces-ext:ra-hop-limit', + 'ra_interval': 'interval', + 'ra_interval_msec': 'openconfig-interfaces-ext:ra-interval-msec', + 'ra_lifetime': 'lifetime', + 'ra_prefixes': 'openconfig-interfaces-ext:ra-prefixes', + 'ra_retrans_interval': 'openconfig-interfaces-ext:ra-retrans-interval', + 'rdnss': 'openconfig-interfaces-ext:rdnss-addresses', + 'reachable_time': 'openconfig-interfaces-ext:reachable-time', + 'router_preference': 'openconfig-interfaces-ext:router-preference', + 'suppress': 'suppress' +} + + +class Ipv6_router_advertisement(ConfigBase): + """ + The sonic_ipv6_router_advertisement class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ipv6_router_advertisement', + ] + + non_vlan_rtadv_path = ('data/openconfig-interfaces:interfaces/interface={intf_name}' + '/subinterfaces/subinterface={sub_intf}/openconfig-if-ip:ipv6/router-advertisement') + vlan_rtadv_path = 'data/openconfig-interfaces:interfaces/interface={vlan_name}/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement' + rtadv_config_path = '{rtadv_path}/config' + + option_to_key_map = { + 'dnssl': 'dnssl_name', + 'ra_prefixes': 'prefix', + 'rdnss': 'address' + } + option_to_table_map = { + 'dnssl': 'dns-search-name', + 'ra_prefixes': 'ra-prefix', + 'rdnss': 'rdnss-address' + } + + def __init__(self, module): + super(Ipv6_router_advertisement, self).__init__(module) + + def get_ipv6_router_advertisement_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + ipv6_router_advertisement_facts = facts['ansible_network_resources'].get('ipv6_router_advertisement') + if not ipv6_router_advertisement_facts: + return [] + return ipv6_router_advertisement_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = [] + + existing_ipv6_router_advertisement_facts = self.get_ipv6_router_advertisement_facts() + commands, requests = self.set_config(existing_ipv6_router_advertisement_facts) + if commands: + if not self._module.check_mode: + try: + edit_config(self._module, to_request(self._module, requests)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + result['changed'] = True + + result['before'] = existing_ipv6_router_advertisement_facts + old_config = existing_ipv6_router_advertisement_facts + if self._module.check_mode: + new_config = self.get_new_config(commands, old_config) + self.sort_lists_in_config(new_config) + result['after(generated)'] = new_config + else: + new_config = self.get_ipv6_router_advertisement_facts() + if result['changed']: + result['after'] = new_config + + if self._module._diff: + self.sort_lists_in_config(old_config) + if not self._module.check_mode: + self.sort_lists_in_config(new_config) + result['diff'] = get_formatted_config_diff(old_config, new_config, self._module._verbosity) + + result['commands'] = commands + result['warnings'] = warnings + return result + + def set_config(self, existing_ipv6_router_advertisement_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self.validate_and_normalize_want(self._module.params['config']) + have = existing_ipv6_router_advertisement_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params['state'] + if state in ('replaced', 'overridden'): + commands, requests = self._state_replaced_overridden(want, have, state) + elif state == 'deleted': + commands, requests = self._state_deleted(want, have) + elif state == 'merged': + commands, requests = self._state_merged(want, have) + return commands, requests + + def _state_replaced_overridden(self, want, have, state): + """ The command generator when state is replaced/overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands, del_commands = [], [] + requests, del_requests = [], [] + + if have: + for have_conf in have: + intf_name = have_conf['name'] + have_conf = self.remove_defaults(have_conf) + if len(have_conf.keys()) == 1: + continue + + conf = next((item for item in want if item['name'] == intf_name), {}) + # Delete all interface rtadv config if not specified in 'overridden', or + # if only interface name is specified + if not conf: + if state == 'overridden': + del_commands.append({'name': have_conf['name']}) + del_requests.extend(self.get_delete_ipv6_rtadv_requests(have_conf, have_conf, True)) + elif len(conf.keys()) == 1: + del_commands.append({'name': have_conf['name']}) + del_requests.extend(self.get_delete_ipv6_rtadv_requests(have_conf, have_conf, True)) + else: + del_command = {} + for option, value in have_conf.items(): + if option == 'name': + continue + + if option not in conf: + del_command[option] = value + elif option in ('dnssl', 'ra_prefixes', 'rdnss'): + del_opt_command = [] + opt_key = self.option_to_key_map[option] + for have_item in value: + item = next((ele for ele in conf[option] if ele[opt_key] == have_item[opt_key]), {}) + if not item or item != have_item: + del_opt_command.append({opt_key: have_item[opt_key]}) + + if del_opt_command: + del_command[option] = del_opt_command + + if del_command: + del_command['name'] = conf['name'] + del_commands.append(del_command) + del_requests.extend(self.get_delete_ipv6_rtadv_requests(del_command, have_conf)) + + if del_commands: + commands = update_states(del_commands, 'deleted') + new_have = self.get_new_config(commands, have) + requests = del_requests + else: + new_have = have + + add_commands = self.get_diff(want, new_have) + if add_commands: + commands.extend(update_states(add_commands, state)) + requests.extend(self.get_modify_ipv6_rtadv_requests(add_commands)) + + return commands, requests + + def _state_merged(self, want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = self.get_diff(want, have) + requests = self.get_modify_ipv6_rtadv_requests(commands) + if commands and len(requests) > 0: + commands = update_states(commands, 'merged') + else: + commands = [] + + return commands, requests + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands, requests = [], [] + if not have: + return commands, requests + elif not want: + for have_conf in have: + commands.append({'name': have_conf['name']}) + requests.extend(self.get_delete_ipv6_rtadv_requests(have_conf, have_conf, True)) + else: + for conf in want: + intf_name = conf['name'] + have_conf = next((item for item in have if item['name'] == intf_name), None) + if not have_conf: + continue + + have_conf = self.remove_defaults(have_conf) + # If only default config is present, nothing to delete + if len(have_conf.keys()) == 1: + continue + + # Delete all L3 config if only interface name is specified + if len(conf.keys()) == 1: + commands.append(conf) + requests.extend(self.get_delete_ipv6_rtadv_requests(conf, have_conf, True)) + continue + + command = {} + for option, value in conf.items(): + if option == 'name': + continue + + if option in ('dnssl', 'ra_prefixes', 'rdnss'): + if have_conf.get(option): + opt_command = [] + opt_key = self.option_to_key_map[option] + for item in value: + have_item = next((ele for ele in have_conf[option] if ele[opt_key] == item[opt_key]), None) + if have_item: + opt_command.append({opt_key: item[opt_key]}) + if opt_command: + command[option] = opt_command + else: + if value == have_conf.get(option): + command[option] = value + + if command: + command['name'] = conf['name'] + commands.append(command) + requests.extend(self.get_delete_ipv6_rtadv_requests(command, have_conf)) + + if commands: + commands = update_states(commands, 'deleted') + return commands, requests + + def get_delete_ipv6_rtadv_requests(self, command, have_conf, delete_all=False): + requests = [] + rtadv_path = self.get_rtadv_path(command['name']) + if delete_all or len(command.keys()) == 1: + requests.append({'path': rtadv_path, 'method': DELETE}) + else: + rtadv_config_path = self.rtadv_config_path.format(rtadv_path=rtadv_path) + + # Delete in the order of: ra-lifetime -> ra-interval -> min-ra-interval + for option in ('ra_lifetime', 'ra_interval_msec', 'ra_interval', 'min_ra_interval_msec', 'min_ra_interval'): + if option in command: + requests.append({'path': rtadv_config_path + '/' + OPTION_TO_PAYLOAD_MAP[option], 'method': DELETE}) + + for option in command: + if option in ('name', 'min_ra_interval', 'min_ra_interval_msec', 'ra_interval', 'ra_interval_msec', 'ra_lifetime'): + continue + + if option in ('dnssl', 'ra_prefixes', 'rdnss'): + if len(command[option]) == len(have_conf[option]): + requests.append({'path': rtadv_path + '/' + OPTION_TO_PAYLOAD_MAP[option], 'method': DELETE}) + else: + opt_key = self.option_to_key_map[option] + url = '{0}/{1}/{2}='.format(rtadv_path, OPTION_TO_PAYLOAD_MAP[option], self.option_to_table_map[option]) + for item in command[option]: + requests.append({'path': url + item[opt_key].replace('/', '%2f'), 'method': DELETE}) + else: + requests.append({'path': rtadv_config_path + '/' + OPTION_TO_PAYLOAD_MAP[option], 'method': DELETE}) + + return requests + + def get_modify_ipv6_rtadv_requests(self, commands): + requests = [] + + for command in commands: + name = command['name'] + rtadv_path = self.get_rtadv_path(name) + rtadv_config_path = self.rtadv_config_path.format(rtadv_path=rtadv_path) + + # Configure in the order of: min-ra-interval -> ra-interval -> ra-lifetime + for option in ('min_ra_interval', 'min_ra_interval_msec', 'ra_interval', 'ra_interval_msec', 'ra_lifetime'): + if option in command: + requests.append({ + 'path': rtadv_config_path + '/' + OPTION_TO_PAYLOAD_MAP[option], + 'method': PATCH, + 'data': {OPTION_TO_PAYLOAD_MAP[option]: command[option]} + }) + + rtadv_payload = {} + rtadv_config_payload = {} + for option, value in command.items(): + if option in ('name', 'min_ra_interval', 'min_ra_interval_msec', 'ra_interval', 'ra_interval_msec', 'ra_lifetime'): + continue + + # For default value, send DELETE request + if option in DEFAULT_RTADV_VALUES and value == DEFAULT_RTADV_VALUES[option]: + url = rtadv_config_path + '/' + OPTION_TO_PAYLOAD_MAP[option] + requests.append({'path': url, 'method': DELETE}) + else: + if option == 'dnssl': + dnssl_payload = self.get_ipv6_rtadv_dnssl_payload(value) + if dnssl_payload: + rtadv_payload[OPTION_TO_PAYLOAD_MAP[option]] = dnssl_payload + elif option == 'ra_prefixes': + ra_prefixes_payload = self.get_ipv6_rtadv_ra_prefixes_payload(value) + if ra_prefixes_payload: + rtadv_payload[OPTION_TO_PAYLOAD_MAP[option]] = ra_prefixes_payload + elif option == 'rdnss': + rdnss_payload = self.get_ipv6_rtadv_rdnss_payload(value) + if rdnss_payload: + rtadv_payload[OPTION_TO_PAYLOAD_MAP[option]] = rdnss_payload + elif option == 'router_preference': + rtadv_config_payload[OPTION_TO_PAYLOAD_MAP[option]] = 'openconfig-interfaces-ext:' + value.upper() + else: + rtadv_config_payload[OPTION_TO_PAYLOAD_MAP[option]] = value + + if rtadv_config_payload: + rtadv_payload['config'] = rtadv_config_payload + if rtadv_payload: + requests.append({'path': rtadv_path, 'method': PATCH, 'data': {'openconfig-if-ip:router-advertisement': rtadv_payload}}) + + return requests + + @staticmethod + def get_ipv6_rtadv_dnssl_payload(dnssl_list): + payload = [] + for dnssl in dnssl_list: + config_payload = {'dnssl-name': dnssl['dnssl_name']} + if 'valid_lifetime' in dnssl: + config_payload['valid-lifetime'] = dnssl['valid_lifetime'] + payload.append({'dnssl-name': dnssl['dnssl_name'], 'config': config_payload}) + + if payload: + return {'dns-search-name': payload} + + return None + + @staticmethod + def get_ipv6_rtadv_ra_prefixes_payload(prefix_list): + payload = [] + for prefix in prefix_list: + config_payload = {'prefix': prefix['prefix']} + for option in ('no_autoconfig', 'off_link', 'preferred_lifetime', 'router_address', 'valid_lifetime'): + if option in prefix: + config_payload[option.replace('_', '-')] = prefix[option] + payload.append({'prefix': prefix['prefix'], 'config': config_payload}) + + if payload: + return {'ra-prefix': payload} + + return None + + @staticmethod + def get_ipv6_rtadv_rdnss_payload(rdnss_list): + payload = [] + for rdnss in rdnss_list: + config_payload = {'address': rdnss['address']} + if 'valid_lifetime' in rdnss: + config_payload['valid-lifetime'] = rdnss['valid_lifetime'] + payload.append({'address': rdnss['address'], 'config': config_payload}) + + if payload: + return {'rdnss-address': payload} + + return None + + def get_rtadv_path(self, intf_name): + if intf_name.startswith('Vlan'): + return self.vlan_rtadv_path.format(vlan_name=intf_name) + else: + sub_intf = 0 + if '.' in intf_name: + intf_name, sub_intf = intf_name.split('.') + return self.non_vlan_rtadv_path.format(intf_name=intf_name, sub_intf=sub_intf) + + def get_diff(self, want, have): + updated_want = [] + for conf in want: + have_conf = next((item for item in have if item['name'] == conf['name']), {}) + conf = self.remove_defaults(have_conf, conf, False) + if len(conf.keys()) > 1: + updated_want.append(conf) + + return get_diff(updated_want, have, TEST_KEYS) + + def get_new_config(self, commands, have): + """Returns generated configuration based on commands and + existing configuration""" + state = self._module.params['state'] + new_conf = get_new_config(commands, have, TEST_KEYS_formatted_diff) + generated_conf = [] + default_conf = DEFAULT_RTADV_VALUES.copy() + for conf in new_conf: + # Remove empty lists + for option in ('dnssl', 'ra_prefixes', 'rdnss'): + if option in conf and not conf[option]: + del conf[option] + + # Set default values + for option, def_value in DEFAULT_RTADV_VALUES.items(): + conf.setdefault(option, def_value) + + if conf.get('ra_prefixes'): + for prefix in conf['ra_prefixes']: + for option, def_value in DEFAULT_PREFIX_VALUES.items(): + prefix.setdefault(option, def_value) + + # Return config if non-default values are present + default_conf['name'] = conf['name'] + if conf != default_conf: + generated_conf.append(conf) + + return generated_conf + + @staticmethod + def remove_defaults(have_conf, conf=None, delete_op=True): + if delete_op: + # For delete operation, the default values in have_conf are removed + updated_conf = have_conf.copy() + for option, def_value in DEFAULT_RTADV_VALUES.items(): + if updated_conf.get(option) == def_value: + del updated_conf[option] + + if updated_conf.get('ra_prefixes'): + prefixes = updated_conf['ra_prefixes'] + for i in range(len(prefixes)): + updated_prefix = prefixes[i].copy() + for option, def_value in DEFAULT_PREFIX_VALUES.items(): + if updated_prefix.get(option) == def_value: + del updated_prefix[option] + + prefixes[i] = updated_prefix + else: + # For merge operation, the default values in conf are removed + # if that option is not present in have_conf + updated_conf = conf + if conf: + updated_conf = conf.copy() + have_conf = have_conf if have_conf else {} + for option, def_value in DEFAULT_RTADV_VALUES.items(): + if option not in have_conf and conf.get(option) == def_value: + del updated_conf[option] + + if updated_conf.get('ra_prefixes'): + prefixes = updated_conf['ra_prefixes'] + have_prefixes = have_conf.get('ra_prefixes', []) + for i in range(len(prefixes)): + updated_prefix = prefixes[i].copy() + have_prefix = next((item for item in have_prefixes if item['prefix'] == updated_prefix['prefix']), {}) + for option, def_value in DEFAULT_PREFIX_VALUES.items(): + if option not in have_prefix and updated_prefix.get(option) == def_value: + del updated_prefix[option] + + prefixes[i] = updated_prefix + + return updated_conf + + def validate_and_normalize_want(self, want): + state = self._module.params['state'] + if not want: + if state != 'deleted': + self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state)) + return [] + else: + updated_want = remove_empties_from_list(want) + normalize_interface_name(updated_want, self._module) + return updated_want + + @staticmethod + def sort_lists_in_config(config): + if config: + config.sort(key=lambda x: x['name']) + for cfg in config: + if cfg.get('dnssl'): + cfg['dnssl'].sort(key=lambda x: x['dnssl_name']) + if cfg.get('ra_prefixes'): + cfg['ra_prefixes'].sort(key=lambda x: x['prefix']) + if cfg.get('rdnss'): + cfg['rdnss'].sort(key=lambda x: x['address']) diff --git a/plugins/module_utils/network/sonic/facts/facts.py b/plugins/module_utils/network/sonic/facts/facts.py index 73e12acf8..a584a5779 100644 --- a/plugins/module_utils/network/sonic/facts/facts.py +++ b/plugins/module_utils/network/sonic/facts/facts.py @@ -79,6 +79,9 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mgmt_servers.mgmt_servers import Mgmt_serversFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ospf_area.ospf_area import Ospf_areaFacts from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ssh.ssh import SshFacts +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ipv6_router_advertisement.ipv6_router_advertisement import ( + Ipv6_router_advertisementFacts +) FACT_LEGACY_SUBSETS = {} @@ -144,6 +147,7 @@ mgmt_servers=Mgmt_serversFacts, ospf_area=Ospf_areaFacts, ssh=SshFacts, + ipv6_router_advertisement=Ipv6_router_advertisementFacts ) diff --git a/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/__init__.py b/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py b/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py new file mode 100644 index 000000000..d0d7c339b --- /dev/null +++ b/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py @@ -0,0 +1,223 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The sonic ipv6_router_advertisement fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import ( + remove_empties_from_list +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ipv6_router_advertisement.ipv6_router_advertisement import ( + Ipv6_router_advertisementArgs +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import ( + to_request, + edit_config +) +from ansible.module_utils.connection import ConnectionError + +DEFAULT_VALUES = { + 'suppress': True, + 'ra_fast_retrans': True, + 'adv_interval_option': False, + 'home_agent_config': False, + 'managed_config': False, + 'other_config': False +} + +OPTION_TO_PAYLOAD_MAP = { + 'adv_interval_option': 'openconfig-interfaces-ext:adv-interval-option', + 'dnssl': 'openconfig-interfaces-ext:dns-search-names', + 'home_agent_config': 'openconfig-interfaces-ext:home-agent-config', + 'home_agent_lifetime': 'openconfig-interfaces-ext:home-agent-lifetime', + 'home_agent_preference': 'openconfig-interfaces-ext:home-agent-preference', + 'managed_config': 'openconfig-interfaces-ext:managed-config', + 'min_ra_interval': 'openconfig-interfaces-ext:min-ra-interval', + 'min_ra_interval_msec': 'openconfig-interfaces-ext:min-ra-interval-msec', + 'mtu': 'openconfig-interfaces-ext:mtu', + 'other_config': 'openconfig-interfaces-ext:other-config', + 'ra_fast_retrans': 'openconfig-interfaces-ext:ra-fast-retrans', + 'ra_hop_limit': 'openconfig-interfaces-ext:ra-hop-limit', + 'ra_interval': 'interval', + 'ra_interval_msec': 'openconfig-interfaces-ext:ra-interval-msec', + 'ra_lifetime': 'lifetime', + 'ra_prefixes': 'openconfig-interfaces-ext:ra-prefixes', + 'ra_retrans_interval': 'openconfig-interfaces-ext:ra-retrans-interval', + 'rdnss': 'openconfig-interfaces-ext:rdnss-addresses', + 'reachable_time': 'openconfig-interfaces-ext:reachable-time', + 'router_preference': 'openconfig-interfaces-ext:router-preference', + 'suppress': 'suppress' +} + + +class Ipv6_router_advertisementFacts(object): + """ The sonic ipv6_router_advertisement fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Ipv6_router_advertisementArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for ipv6_router_advertisement + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if connection: # just for linting purposes, remove + pass + if not data: + objs = self.get_ipv6_router_advertisement() + + ansible_facts['ansible_network_resources'].pop('ipv6_router_advertisement', None) + facts = {} + if objs: + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['ipv6_router_advertisement'] = remove_empties_from_list(params['config']) + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def get_ipv6_router_advertisement(self): + url = 'data/openconfig-interfaces:interfaces/interface' + method = 'GET' + request = [{'path': url, 'method': method}] + + try: + response = edit_config(self._module, to_request(self._module, request)) + except ConnectionError as exc: + self._module.fail_json(msg=str(exc), code=exc.code) + + interfaces = [] + if 'openconfig-interfaces:interface' in response[0][1]: + interfaces = response[0][1].get('openconfig-interfaces:interface', []) + + ipv6_rtadv_configs = [] + for interface in interfaces: + intf_name = interface['name'] + if intf_name.startswith('Vlan'): + ipv6_conf = interface.get('openconfig-vlan:routed-vlan', {}).get('openconfig-if-ip:ipv6') + ipv6_rtadv_config = self.render_config(ipv6_conf, intf_name, is_vlan=True) + if ipv6_rtadv_config: + ipv6_rtadv_configs.append(ipv6_rtadv_config) + elif not (intf_name == 'eth0' or intf_name.startswith('Management') or '.' in intf_name or '|' in intf_name): + if interface.get('subinterfaces', {}).get('subinterface'): + for sub_intf in interface['subinterfaces']['subinterface']: + if sub_intf.get('index', 0) != 0: + intf_name = interface['name'] + '.' + str(sub_intf['index']) + else: + intf_name = interface['name'] + ipv6_rtadv_config = self.render_config(sub_intf.get('openconfig-if-ip:ipv6'), intf_name) + if ipv6_rtadv_config: + ipv6_rtadv_configs.append(ipv6_rtadv_config) + + return ipv6_rtadv_configs + + def render_config(self, ipv6_conf, intf_name, is_vlan=False): + ipv6_rtadv = {} + if not ipv6_conf: + return ipv6_rtadv + + rtadv = ipv6_conf.get('router-advertisement') + if rtadv: + if rtadv.get('config'): + rtadv_config = rtadv['config'] + for option, field in OPTION_TO_PAYLOAD_MAP.items(): + if field in rtadv_config: + if option == 'router_preference': + ipv6_rtadv[option] = rtadv_config[field].split('openconfig-interfaces-ext:')[-1].lower() + else: + ipv6_rtadv[option] = rtadv_config[field] + + if rtadv.get(OPTION_TO_PAYLOAD_MAP['dnssl']): + self.parse_and_update_dnssl_conf(ipv6_rtadv, rtadv[OPTION_TO_PAYLOAD_MAP['dnssl']]) + if rtadv.get(OPTION_TO_PAYLOAD_MAP['ra_prefixes']): + self.parse_and_update_ra_prefixes_conf(ipv6_rtadv, rtadv[OPTION_TO_PAYLOAD_MAP['ra_prefixes']]) + if rtadv.get(OPTION_TO_PAYLOAD_MAP['rdnss']): + self.parse_and_update_rdnss_conf(ipv6_rtadv, rtadv[OPTION_TO_PAYLOAD_MAP['rdnss']]) + + # Fill in default values + for option, def_value in DEFAULT_VALUES.items(): + ipv6_rtadv.setdefault(option, def_value) + + if ipv6_rtadv: + ipv6_rtadv['name'] = intf_name + + return ipv6_rtadv + + @staticmethod + def parse_and_update_dnssl_conf(ipv6_rtadv, dnssl_conf): + if dnssl_conf: + parsed_dnssl_list = [] + for item in dnssl_conf.get('dns-search-name', []): + parsed_conf = {} + if item.get('dnssl-name'): + parsed_conf['dnssl_name'] = item['dnssl-name'] + if item.get('config') and 'valid-lifetime' in item['config']: + parsed_conf['valid_lifetime'] = item['config']['valid-lifetime'] + parsed_dnssl_list.append(parsed_conf) + + if parsed_dnssl_list: + ipv6_rtadv['dnssl'] = parsed_dnssl_list + + @staticmethod + def parse_and_update_ra_prefixes_conf(ipv6_rtadv, ra_prefixes_conf): + if ra_prefixes_conf: + parsed_ra_prefixes = [] + for item in ra_prefixes_conf.get('ra-prefix', []): + parsed_conf = {} + if item.get('prefix'): + parsed_conf = { + 'prefix': item['prefix'], + 'no_autoconfig': False, + 'off_link': False, + 'router_address': False + } + if item.get('config'): + for option in ('no-autoconfig', 'off-link', 'preferred-lifetime', 'router-address', 'valid-lifetime'): + if option in item['config']: + parsed_conf[option.replace('-', '_')] = item['config'][option] + parsed_ra_prefixes.append(parsed_conf) + + if parsed_ra_prefixes: + ipv6_rtadv['ra_prefixes'] = parsed_ra_prefixes + + @staticmethod + def parse_and_update_rdnss_conf(ipv6_rtadv, rdnss_conf): + if rdnss_conf: + parsed_rdnss_list = [] + for item in rdnss_conf.get('rdnss-address', []): + parsed_conf = {} + if item.get('address'): + parsed_conf['address'] = item['address'] + if item.get('config') and 'valid-lifetime' in item['config']: + parsed_conf['valid_lifetime'] = item['config']['valid-lifetime'] + parsed_rdnss_list.append(parsed_conf) + + if parsed_rdnss_list: + ipv6_rtadv['rdnss'] = parsed_rdnss_list diff --git a/plugins/modules/sonic_facts.py b/plugins/modules/sonic_facts.py index bd5b282c0..967e8a74f 100644 --- a/plugins/modules/sonic_facts.py +++ b/plugins/modules/sonic_facts.py @@ -57,6 +57,7 @@ - interfaces - l2_interfaces - l3_interfaces + - ipv6_router_advertisement - lag_interfaces - bgp - bgp_af diff --git a/plugins/modules/sonic_ipv6_router_advertisement.py b/plugins/modules/sonic_ipv6_router_advertisement.py new file mode 100644 index 000000000..c62e1fe30 --- /dev/null +++ b/plugins/modules/sonic_ipv6_router_advertisement.py @@ -0,0 +1,732 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for sonic_ipv6_router_advertisement +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: sonic_ipv6_router_advertisement +version_added: '3.1.0' +short_description: Manage interface-specific IPv6 Router Advertisement configurations on SONiC +description: + - This module provides configuration management of interface-specific + IPv6 Router Advertisement parameters for devices running SONiC. + - This functionality is referred to as 'ipv6 nd' in Enterprise SONiC CLI. +author: 'Arun Saravanan Balachandran (@ArunSaravananBalachandran)' +options: + config: + description: + - Specifies interface-specific IPv6 Router Advertisement configurations. + type: list + elements: dict + suboptions: + name: + description: + - Full name of the interface. + type: str + required: true + adv_interval_option: + description: + - Include Advertisement Interval option in Router Advertisement. + type: bool + home_agent_config: + description: + - Set 'Home Agent' flag in Router Advertisement. + type: bool + home_agent_lifetime: + description: + - Specifies the Home Agent lifetime in seconds when I(home_agent_config=True). + - The range is from 0 to 65520. + type: int + home_agent_preference: + description: + - Specifies the Home Agent preference when I(home_agent_config=True). + - The range is from 0 to 65535. + type: int + managed_config: + description: + - Set 'Managed Address Configuration' flag in Router Advertisement. + type: bool + mtu: + description: + - Specifies the MTU (in bytes) to be advertised. + - The range is from 0 to 65535. + type: int + other_config: + description: + - Set 'Other Configuration' flag in Router Advertisement. + type: bool + ra_fast_retrans: + description: + - Enable faster transmissions of RA packets. + type: bool + ra_hop_limit: + description: + - Specifies the Hop limit to be advertised. + - The range is from 0 to 255. + type: int + ra_interval: + description: + - Specifies the maximum Router Advertisement interval in seconds. + - The range is from 1 to 1800. + type: int + ra_interval_msec: + description: + - Specifies the maximum Router Advertisement interval in milliseconds. + - The range is from 70 to 1800000. + type: int + min_ra_interval: + description: + - Specifies the minimum Router Advertisement interval in seconds. + - The range is from 1 to 1350. + type: int + min_ra_interval_msec: + description: + - Specifies the minimum Router Advertisement interval in milliseconds. + - The range is from 30 to 1350000. + type: int + ra_lifetime: + description: + - Specifies the Router Lifetime in seconds. + - The range is from 0 to 9000. + type: int + ra_retrans_interval: + description: + - Specifies the Retransmission Interval in milliseconds. + - The range is from 0 to 4294967295. + type: int + reachable_time: + description: + - Specifies the Reachable Time in milliseconds. + - The range is from 0 to 3600000. + type: int + router_preference: + description: + - Specifies the default router preference. + type: str + choices: + - low + - medium + - high + suppress: + description: + - Enable suppression of Router Advertisement. + type: bool + dnssl: + description: + - Specifies the DNS search list to advertise. + - If I(state=deleted), options other than I(dnssl_name) are not considered. + type: list + elements: dict + suboptions: + dnssl_name: + description: + - Domain Name suffix to be advertised. + type: str + required: true + valid_lifetime: + description: + - Specifies the valid lifetime in seconds. + - The range if from 0 to 4294967295. + - Value of 4294967295 represents infinite valid lifetime. + type: int + ra_prefixes: + description: + - Specifies the IPv6 prefixes to be included in Router Advertisement. + - If I(state=deleted), options other than I(prefix) are not considered. + type: list + elements: dict + suboptions: + prefix: + description: + - IPv6 prefix to be advertised. + type: str + required: true + valid_lifetime: + description: + - Specifies the valid lifetime in seconds. + - The range if from 0 to 4294967295. + - Value of 4294967295 represents infinite valid lifetime. + type: int + preferred_lifetime: + description: + - Specifies the preferred lifetime in seconds. + - The range if from 0 to 4294967295. + - Value of 4294967295 represents infinite preferred lifetime. + type: int + no_autoconfig: + description: + - Indicate the prefix cannot be used for IPv6 autoconfiguration. + type: bool + off_link: + description: + - Indicate the prefix cannot be used for on-link determination. + type: bool + router_address: + description: + - Set 'Router Address' flag. + type: bool + rdnss: + description: + - Specifies the Recursive DNS server addresses to advertise. + - If I(state=deleted), options other than I(address) are not considered. + type: list + elements: dict + suboptions: + address: + description: + - Recursive DNS server address to be advertised. + type: str + required: true + valid_lifetime: + description: + - Specifies the valid lifetime in seconds. + - The range if from 0 to 4294967295. + - Value of 4294967295 represents infinite valid lifetime. + type: int + state: + description: + - The state of the configuration after module completion. + - C(merged) - Merges provided interface-specific PIM configuration with on-device configuration. + - C(replaced) - Replaces on-device PIM configuration of the specified interfaces with provided configuration. + - C(overridden) - Overrides all on-device interface-specific PIM configurations with the provided configuration. + - C(deleted) - Deletes on-device interface-specific PIM configuration. + type: str + choices: + - merged + - deleted + - replaced + - overridden + default: merged +""" +EXAMPLES = """ +# Using merged +# +# Before State: +# ------------- +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ! + +- name: Add IPv6 Router Advertisement configurations + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: + - name: 'Eth1/1' + suppress: false + router_preference: high + ra_interval: 180 + min_ra_interval: 60 + ra_lifetime: 360 + ra_retrans_interval: 30000 + ra_hop_limit: 10 + dnssl: + - dnssl_name: 'test.com' + valid_lifetime: 3600 + rdnss: + - address: 100::100 + - address: 100::200 + - name: 'Eth1/2' + adv_interval_option: true + ra_fast_retrans: false + reachable_time: 7200000 + ra_prefixes: + - prefix: 1000:0:0:2000::/64 + valid_lifetime: 86400 + preferred_lifetime: 86400 + off_link: true + no_autoconfig: true + state: merged + +# After State: +# ------------ +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd ra-retrans-interval 30000 +# ipv6 nd router-preference high +# ipv6 nd dnssl test.com 3600 +# ipv6 nd rdnss 100::100 +# ipv6 nd rdnss 100::200 +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# no ipv6 nd ra-fast-retrans +# ipv6 nd adv-interval-option +# ipv6 nd reachable-time 1200000 +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ipv6 nd prefix 1000:0:0:2000::/64 86400 86400 off-link no-autoconfig +# ! + + +# Using deleted +# +# Before State: +# ------------- +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd ra-retrans-interval 30000 +# ipv6 nd router-preference high +# ipv6 nd dnssl test.com 3600 +# ipv6 nd dnssl test2.com 7200 +# ipv6 nd rdnss 100::100 3600 +# ipv6 nd rdnss 100::200 7200 +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# no ipv6 nd ra-fast-retrans +# ipv6 nd adv-interval-option +# ipv6 nd min-ra-interval msec 45500 +# ipv6 nd reachable-time 1200000 +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ipv6 nd prefix 1000:0:0:2000::/64 86400 86400 off-link no-autoconfig +# ! + +- name: Delete IPv6 Router Advertisement configurations + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: + - name: 'Eth1/1' + ra_hop_limit: 10 + router_preference: high + dnssl: + - dnssl_name: test2.com + rdnss: + - address: 100::200 + - name: 'Eth1/2' + adv_interval_option: true + ra_fast_retrans: false + ra_prefixes: + - prefix: 1000:0:0:2000::/64 + state: deleted + +# After State: +# ------------ +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd ra-retrans-interval 30000 +# ipv6 nd dnssl test.com 3600 +# ipv6 nd rdnss 100::100 3600 +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# shutdown +# no ipv6 nd suppress-ra +# ipv6 nd min ra-interval msec 45500 +# ipv6 nd reachable-time 1200000 +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ! + + +# Using deleted +# +# Before State: +# ------------- +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd ra-retrans-interval 30000 +# ipv6 nd dnssl test.com 3600 +# ipv6 nd rdnss 100::100 3600 +# ipv6 nd rdnss 100::200 7200 +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd adv-interval-option +# ipv6 nd router-preference low +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ! + +- name: Delete all IPv6 Router Advertisement configurations for interface Eth1/1 + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: + - name: 'Eth1/1' + state: deleted + +# After State: +# ------------ +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd adv-interval-option +# ipv6 nd router-preference low +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ! + + +# Using deleted +# +# Before State: +# ------------- +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd ra-retrans-interval 30000 +# ipv6 nd dnssl test.com 3600 +# ipv6 nd rdnss 100::100 3600 +# ipv6 nd rdnss 100::200 7200 +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd adv-interval-option +# ipv6 nd router-preference low +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ! + +- name: Delete all IPv6 Router Advertisement configurations + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: + state: deleted + +# After State: +# ------------ +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ! + + +# Using replaced +# +# Before State: +# ------------- +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd router-preference high +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# no ipv6 nd ra-fast-retrans +# ipv6 nd adv-interval-option +# ipv6 nd min-ra-interval msec 45500 +# ipv6 nd ra-hop-limit 10 +# ipv6 nd reachable-time 1200000 +# ipv6 nd prefix 1000:0:0:1000::/64 86400 86400 off-link no-autoconfig +# ipv6 nd prefix 1000:0:0:2000::/64 86400 86400 off-link no-autoconfig +# ! + +- name: Replace IPv6 Router Advertisement configurations for interface Eth1/2 + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: + - name: 'Eth1/2' + suppress: false + ra_interval: 300 + router_preference: high + ra_prefixes: + - prefix: 2000:0:0:1000::/64 + valid_lifetime: 3600 + preferred_lifetime: 3600 + router_address: true + state: replaced + +# After State: +# ------------ +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd router-preference high +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-interval 300 +# ipv6 nd router-preference high +# ipv6 nd prefix 2000:0:0:1000::/64 3600 3600 router-address +# ! + + +# Using overridden +# +# Before State: +# ------------- +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-hop-limit 10 +# ipv6 nd ra-interval 180 +# ipv6 nd min-ra-interval 60 +# ipv6 nd ra-lifetime 360 +# ipv6 nd router-preference high +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd ra-interval 300 +# ipv6 nd router-preference high +# ipv6 nd prefix 2000:0:0:1000::/64 3600 3600 router-address +# ! +# interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ! + +- name: Override all IPv6 Router Advertisement configurations + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: + - name: 'Eth1/1' + suppress: false + home_agent_config: true + home_agent_lifetime: 7200 + home_agent_preference: 100 + - name: 'Eth1/3' + suppress: false + managed_config: true + other_config: true + ra_retrans_interval: 30000 + state: overridden + +# After State: +# ------------ +# +# sonic# show running-configuration interface +# ! +# interface Eth1/1 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd home-agent-config-flag +# ipv6 nd home-agent-lifetime 7200 +# ipv6 nd home-agent-preference 100 +# ! +# interface Eth1/2 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# ! +# interface Eth1/3 +# mtu 9100 +# speed 400000 +# fec RS +# no shutdown +# no ipv6 nd suppress-ra +# ipv6 nd managed-config-flag +# ipv6 nd other-config-flag +# ipv6 nd ra-retrans-interval 30000 +# ! +""" +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 module invocation. + returned: when C(check_mode) + type: list + sample: > + The configuration returned will always be in the same format + as the parameters above. +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 +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ipv6_router_advertisement.ipv6_router_advertisement import ( + Ipv6_router_advertisementArgs +) +from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ipv6_router_advertisement.ipv6_router_advertisement import ( + Ipv6_router_advertisement +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Ipv6_router_advertisementArgs.argument_spec, + supports_check_mode=True) + + result = Ipv6_router_advertisement(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/regression/roles/sonic_ipv6_router_advertisement/defaults/main.yml b/tests/regression/roles/sonic_ipv6_router_advertisement/defaults/main.yml new file mode 100644 index 000000000..a0ad63cf0 --- /dev/null +++ b/tests/regression/roles/sonic_ipv6_router_advertisement/defaults/main.yml @@ -0,0 +1,265 @@ +--- +ansible_connection: httpapi + +sonic_ipv6_router_advertisement_task_input: + lag_interfaces: + - name: 'Portchannel 100' + - name: 'Portchannel 101' + vlans: + - vlan_id: 100 + - vlan_id: 101 + +sonic_ipv6_router_advertisement_tests: + - name: test_case_01 + description: Add IPv6 Router Advertisement configuration + state: merged + input: + - name: '{{ interface1 }}' + suppress: false + ra_interval: 450 + min_ra_interval: 300 + ra_retrans_interval: 100 + ra_lifetime: 600 + reachable_time: 600 + ra_prefixes: + - prefix: 1000:2000::/64 + valid_lifetime: 2400 + preferred_lifetime: 1800 + - prefix: 1000:3000::/64 + + - name: test_case_02 + description: Update IPv6 Router Advertisement configuration + state: merged + input: + - name: '{{ interface1 }}' + router_preference: 'high' + ra_fast_retrans: false + ra_prefixes: + - prefix: 1000:4000::/64 + off_link: true + no_autoconfig: true + rdnss: + - address: 100::200 + - address: 100::300 + - name: '{{ interface2 }}' + suppress: false + ra_hop_limit: 10 + other_config: true + managed_config: true + dnssl: + - dnssl_name: 'test.com' + - dnssl_name: 'test2.com' + valid_lifetime: 300 + rdnss: + - address: 100::200 + - address: 100::300 + - name: 'Portchannel 100' + suppress: false + dnssl: + - dnssl_name: 'test.com' + - dnssl_name: 'test2.com' + valid_lifetime: 300 + - name: 'Vlan100' + ra_interval_msec: 200500 + min_ra_interval_msec: 100500 + home_agent_config: true + home_agent_lifetime: 100 + home_agent_preference: 150 + + - name: test_case_03 + description: Update IPv6 Router Advertisement configuration + state: merged + input: + - name: '{{ interface1 }}' + ra_fast_retrans: true + router_preference: 'medium' + mtu: 1500 + - name: '{{ interface2 }}' + suppress: true + other_config: false + managed_config: false + - name: 'Portchannel 101' + suppress: false + adv_interval_option: true + dnssl: + - dnssl_name: 'test.com' + - dnssl_name: 'test2.com' + valid_lifetime: 300 + - name: 'Vlan101' + suppress: false + ra_prefixes: + - prefix: 1001:1000::/64 + valid_lifetime: 2400 + router_address: false + off_link: true + - prefix: 1001:2000::/64 + no_autoconfig: false + router_address: true + dnssl: + - dnssl_name: 'host.com' + - dnssl_name: 'server.com' + rdnss: + - address: 101::200 + - address: 101::300 + + - name: test_case_04 + description: Delete specific IPv6 Router Advertisement configurations + state: deleted + input: + - name: '{{ interface1 }}' + suppress: false + ra_interval: 450 + min_ra_interval: 300 + ra_retrans_interval: 100 + ra_lifetime: 600 + reachable_time: 600 + ra_fast_retrans: true + router_preference: 'medium' + mtu: 1500 + ra_prefixes: + - prefix: 1000:2000::/64 + - prefix: 1000:3000::/64 + - prefix: 1000:4000::/64 + rdnss: + - address: 100::200 + - name: '{{ interface2 }}' + - name: 'Portchannel 100' + dnssl: + - dnssl_name: 'test1.com' + - name: 'PortChannel 101' + adv_interval_option: true + dnssl: + - dnssl_name: 'test.com' + - name: 'Vlan100' + ra_interval_msec: 200500 + min_ra_interval_msec: 100500 + - name: 'Vlan101' + ra_prefixes: + - prefix: 1001:2000::/64 + dnssl: + - dnssl_name: 'host.com' + - dnssl_name: 'server.com' + rdnss: + - address: 101::200 + - address: 101::300 + + - name: test_case_05 + description: Delete all IPv6 Router Advertisement configurations for specified interfaces + state: deleted + input: + - name: 'PortChannel100' + - name: 'PortChannel101' + + - name: test_case_06 + description: Add IPv6 Router Advertisement configurations for replace + state: merged + input: + - name: '{{ interface1 }}' + suppress: false + ra_interval: 300 + ra_lifetime: 450 + min_ra_interval: 150 + ra_fast_retrans: true + ra_hop_limit: 10 + managed_config: true + reachable_time: 600 + ra_prefixes: + - prefix: 1000:2000::/64 + valid_lifetime: 3000 + router_address: true + - prefix: 1000:3000::/64 + no_autoconfig: true + off_link: true + - name: '{{ interface2 }}' + suppress: false + ra_interval_msec: 100250 + min_ra_interval_msec: 50750 + ra_retrans_interval: 30000 + other_config: true + router_preference: 'low' + dnssl: + - dnssl_name: 'test.com' + valid_lifetime: 300 + - dnssl_name: 'test2.com' + valid_lifetime: 600 + rdnss: + - address: 100::200 + valid_lifetime: 500 + - address: 100::300 + valid_lifetime: 1000 + - name: 'Vlan100' + suppress: false + home_agent_config: true + home_agent_lifetime: 100 + home_agent_preference: 150 + - name: 'Vlan101' + home_agent_config: true + home_agent_lifetime: 100 + home_agent_preference: 150 + + - name: test_case_07 + description: Replace IPv6 Router Advertisement configurations + state: replaced + input: + - name: '{{ interface1 }}' + suppress: false + ra_hop_limit: 15 + ra_prefixes: + - prefix: 1000:1000::/64 + valid_lifetime: 1500 + router_address: true + - prefix: 1000:2000::/64 + valid_lifetime: 1500 + router_address: true + - name: '{{ interface2 }}' + ra_interval: 100 + ra_lifetime: 200 + min_ra_interval: 50 + dnssl: + - dnssl_name: 'host.com' + - dnssl_name: 'test.com' + valid_lifetime: 600 + rdnss: + - address: 100::200 + - address: 100::500 + - name: 'PortChannel 100' + suppress: false + reachable_time: 300 + ra_retrans_interval: 15000 + - name: 'Vlan101' + suppress: true + + - name: test_case_08 + description: Override IPv6 Router Advertisement configurations + state: overridden + input: + - name: '{{ interface1 }}' + suppress: false + ra_interval: 300 + ra_prefixes: + - prefix: 1000:1000::/64 + valid_lifetime: 1500 + router_address: true + - name: '{{ interface2 }}' + min_ra_interval: 50 + dnssl: + - dnssl_name: 'host.com' + rdnss: + - address: 100::100 + - name: 'PortChannel 101' + suppress: false + ra_lifetime: 720 + ra_hop_limit: 25 + router_preference: 'medium' + ra_prefixes: + - prefix: 1001:1000::/64 + dnssl: + - dnssl_name: 'test.com' + rdnss: + - address: 100::200 + - address: 100::300 + + - name: test_case_09 + description: Delete all IPv6 Router Advertisement configurations + state: deleted + input: [] diff --git a/tests/regression/roles/sonic_ipv6_router_advertisement/meta/main.yml b/tests/regression/roles/sonic_ipv6_router_advertisement/meta/main.yml new file mode 100644 index 000000000..d0ceaf6f5 --- /dev/null +++ b/tests/regression/roles/sonic_ipv6_router_advertisement/meta/main.yml @@ -0,0 +1,5 @@ +--- +collections: + - dellemc.enterprise_sonic +dependencies: + - { role: common } diff --git a/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/cleanup_tests.yaml b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/cleanup_tests.yaml new file mode 100644 index 000000000..21252dbb9 --- /dev/null +++ b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/cleanup_tests.yaml @@ -0,0 +1,18 @@ +--- +- name: Delete IPv6 router advertisement configurations + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: [] + state: deleted + failed_when: false + +- name: Delete test VLANs + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ sonic_ipv6_router_advertisement_task_input.vlans }}" + state: deleted + failed_when: false + +- name: Delete test lag interfaces + dellemc.enterprise_sonic.sonic_lag_interfaces: + config: "{{ sonic_ipv6_router_advertisement_task_input.lag_interfaces }}" + state: deleted + failed_when: false diff --git a/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/main.yml b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/main.yml new file mode 100644 index 000000000..ad7eab089 --- /dev/null +++ b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: "Preparations for test" + ansible.builtin.include_tasks: preparation_tests.yaml + +- name: "Test started ..." + ansible.builtin.include_tasks: tasks_template.yaml + loop: "{{ sonic_ipv6_router_advertisement_tests }}" + +- name: "Cleanup started ..." + ansible.builtin.include_tasks: cleanup_tests.yaml diff --git a/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/preparation_tests.yaml b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/preparation_tests.yaml new file mode 100644 index 000000000..30432b6f8 --- /dev/null +++ b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/preparation_tests.yaml @@ -0,0 +1,25 @@ +--- +- name: Delete old IPv6 router advertisement configurations + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: [] + state: deleted + failed_when: false + +- name: Initialize default interfaces + vars: + ansible_connection: network_cli + dellemc.enterprise_sonic.sonic_config: + commands: "{{ default_interface_cli }}" + failed_when: false + +- name: Create lag interfaces + dellemc.enterprise_sonic.sonic_lag_interfaces: + config: "{{ sonic_ipv6_router_advertisement_task_input.lag_interfaces }}" + state: merged + failed_when: false + +- name: Create VLANs + dellemc.enterprise_sonic.sonic_vlans: + config: "{{ sonic_ipv6_router_advertisement_task_input.vlans }}" + state: merged + failed_when: false diff --git a/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/tasks_template.yaml b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/tasks_template.yaml new file mode 100644 index 000000000..7515789a4 --- /dev/null +++ b/tests/regression/roles/sonic_ipv6_router_advertisement/tasks/tasks_template.yaml @@ -0,0 +1,24 @@ +--- +- name: "{{ item.name ~ ' , ' ~ item.description }}" + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: action_task_output + ignore_errors: true + +- name: "Update test report" + ansible.builtin.import_role: + name: common + tasks_from: action.facts.report.yaml + +- name: "{{ item.name ~ ' , ' ~ item.description ~ ' Idempotent' }}" + dellemc.enterprise_sonic.sonic_ipv6_router_advertisement: + config: "{{ item.input }}" + state: "{{ item.state }}" + register: idempotent_task_output + ignore_errors: true + +- name: "Update test report" + ansible.builtin.import_role: + name: common + tasks_from: idempotent.facts.report.yaml diff --git a/tests/regression/test.yaml b/tests/regression/test.yaml index 5ea3065b3..be7594926 100644 --- a/tests/regression/test.yaml +++ b/tests/regression/test.yaml @@ -72,4 +72,5 @@ - sonic_poe - sonic_mgmt_servers - sonic_ssh + - sonic_ipv6_router_advertisement - test_reports diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_ipv6_router_advertisement.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_ipv6_router_advertisement.yaml new file mode 100644 index 000000000..c39da69ad --- /dev/null +++ b/tests/unit/modules/network/sonic/fixtures/sonic_ipv6_router_advertisement.yaml @@ -0,0 +1,843 @@ +--- +merged_01: + module_args: + config: + - name: 'Eth1/5' + suppress: false + ra_interval: 120 + min_ra_interval: 60 + ra_lifetime: 180 + ra_hop_limit: 12 + ra_fast_retrans: false + ra_retrans_interval: 10000 + mtu: 1500 + router_preference: 'medium' + managed_config: true + other_config: true + home_agent_config: true + home_agent_lifetime: 6000 + home_agent_preference: 30 + dnssl: + - dnssl_name: 'testhost.com' + valid_lifetime: 720 + - dnssl_name: 'testhost2.com' + ra_prefixes: + - prefix: '100:100::/64' + router_address: true + no_autoconfig: true + off_link: true + - prefix: '100:200::/64' + valid_lifetime: 3600 + rdnss: + - address: '1010::1010' + - address: '1010::2010' + valid_lifetime: 4800 + - name: 'Eth1/6' + suppress: true + ra_fast_retrans: true + - name: 'Vlan100' + min_ra_interval_msec: 40250 + ra_interval_msec: 90500 + router_preference: 'high' + facts_get_requests: + - path: 'data/openconfig-interfaces:interfaces/interface' + response: + code: 200 + value: + openconfig-interfaces:interface: + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:ra-fast-retrans: false + interval: 360 + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + config: + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:MEDIUM + config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/suppress' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f6/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-fast-retrans' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval' + method: 'patch' + data: + openconfig-interfaces-ext:min-ra-interval: 60 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/interval' + method: 'patch' + data: + interval: 120 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/lifetime' + method: 'patch' + data: + lifetime: 180 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f5/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:ra-hop-limit: 12 + openconfig-interfaces-ext:ra-fast-retrans: false + openconfig-interfaces-ext:ra-retrans-interval: 10000 + openconfig-interfaces-ext:mtu: 1500 + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:MEDIUM + openconfig-interfaces-ext:managed-config: true + openconfig-interfaces-ext:other-config: true + openconfig-interfaces-ext:home-agent-config: true + openconfig-interfaces-ext:home-agent-lifetime: 6000 + openconfig-interfaces-ext:home-agent-preference: 30 + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'testhost.com' + config: + dnssl-name: 'testhost.com' + valid-lifetime: 720 + - dnssl-name: 'testhost2.com' + config: + dnssl-name: 'testhost2.com' + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '100:100::/64' + config: + prefix: '100:100::/64' + router-address: true + no-autoconfig: true + off-link: true + - prefix: '100:200::/64' + config: + prefix: '100:200::/64' + valid-lifetime: 3600 + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '1010::1010' + config: + address: '1010::1010' + - address: '1010::2010' + config: + address: '1010::2010' + valid-lifetime: 4800 + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval-msec' + method: 'patch' + data: + openconfig-interfaces-ext:min-ra-interval-msec: 40250 + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-interval-msec' + method: 'patch' + data: + openconfig-interfaces-ext:ra-interval-msec: 90500 + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + config: + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:HIGH + +merged_02: + module_args: + config: + - name: 'Vlan200' + suppress: true + facts_get_requests: + - path: 'data/openconfig-interfaces:interfaces/interface' + response: + code: 200 + value: + openconfig-interfaces:interface: + - name: 'Eth1/6' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + interval: 360 + config_requests: [] + +deleted_01: + module_args: + config: + - name: 'Eth1/1' + suppress: false + ra_interval: 450 + min_ra_interval: 300 + ra_retrans_interval: 100 + ra_lifetime: 600 + reachable_time: 600 + ra_fast_retrans: true + router_preference: 'medium' + mtu: 1500 + ra_prefixes: + - prefix: 1000:2000::/64 + - prefix: 1000:3000::/64 + - prefix: 1000:4000::/64 + rdnss: + - address: 100::200 + - name: 'Eth1/2' + - name: 'Portchannel 100' + dnssl: + - dnssl_name: 'test1.com' + - name: 'PortChannel 101' + adv_interval_option: true + dnssl: + - dnssl_name: 'test.com' + - name: 'Vlan100' + ra_interval_msec: 200500 + min_ra_interval_msec: 100500 + - name: 'Vlan101' + ra_prefixes: + - prefix: 1001:2000::/64 + dnssl: + - dnssl_name: 'host.com' + - dnssl_name: 'server.com' + rdnss: + - address: 101::200 + - address: 101::300 + state: deleted + facts_get_requests: + - path: 'data/openconfig-interfaces:interfaces/interface' + response: + code: 200 + value: + openconfig-interfaces:interface: + - name: 'Eth1/1' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + interval: 450 + lifetime: 600 + openconfig-interfaces-ext:min-ra-interval: 300 + openconfig-interfaces-ext:mtu: 1500 + openconfig-interfaces-ext:ra-retrans-interval: 100 + openconfig-interfaces-ext:reachable-time: 600 + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:MEDIUM + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1000:2000::/64' + config: + prefix: '1000:2000::/64' + valid-lifetime: 2400 + - prefix: '1000:3000::/64' + config: + prefix: '1000:3000::/64' + - prefix: '1000:4000::/64' + config: + prefix: '1000:4000::/64' + off-link: true + no-autoconfig: true + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::200' + config: + address: '100::200' + - address: '100::300' + config: + address: '100::300' + - name: 'Eth1/2' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + openconfig-interfaces-ext:ra-hop-limit: 10 + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test1.com' + config: + dnssl-name: 'test1.com' + - dnssl-name: 'test2.com' + config: + dnssl-name: 'test2.com' + valid-lifetime: 300 + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '101::200' + config: + address: '101::200' + - address: '101::300' + config: + address: '101::300' + - name: 'Portchannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test.com' + config: + dnssl-name: 'test.com' + - dnssl-name: 'test2.com' + config: + dnssl-name: 'test2.com' + valid-lifetime: 300 + - name: 'PortChannel101' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:adv-interval-option: true + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test.com' + config: + dnssl-name: 'test.com' + - dnssl-name: 'test2.com' + config: + dnssl-name: 'test2.com' + valid-lifetime: 300 + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + config: + openconfig-interfaces-ext:home-agent-config: true + openconfig-interfaces-ext:home-agent-lifetime: 100 + openconfig-interfaces-ext:home-agent-preference: 150 + openconfig-interfaces-ext:ra-interval-msec: 200500 + openconfig-interfaces-ext:min-ra-interval-msec: 100500 + - name: 'Vlan101' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1001:1000::/64' + config: + prefix: '1001:1000::/64' + off-link: true + valid-lifetime: 2400 + - prefix: '1001:2000::/64' + config: + prefix: '1001:2000::/64' + router-address: true + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'host.com' + config: + dnssl-name: 'host.com' + - dnssl-name: 'server.com' + config: + dnssl-name: 'server.com' + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '101::200' + config: + address: '101::200' + - address: '101::300' + config: + address: '101::300' + config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/suppress' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/lifetime' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:mtu' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-retrans-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:reachable-time' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:router-preference' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:ra-prefixes' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:rdnss-addresses/rdnss-address=100::200' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=PortChannel101/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:adv-interval-option' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=PortChannel101/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:dns-search-names/dns-search-name=test.com' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-interval-msec' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval-msec' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:ra-prefixes/ra-prefix=1001:2000::%2f64' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:dns-search-names' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:rdnss-addresses' + method: 'delete' + +deleted_02: + module_args: + config: [] + state: deleted + facts_get_requests: + - path: 'data/openconfig-interfaces:interfaces/interface' + response: + code: 200 + value: + openconfig-interfaces:interface: + - name: 'Eth1/1' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + interval: 450 + lifetime: 600 + openconfig-interfaces-ext:mtu: 1500 + openconfig-interfaces-ext:ra-retrans-interval: 100 + openconfig-interfaces-ext:reachable-time: 600 + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:MEDIUM + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1000:2000::/64' + config: + prefix: '1000:2000::/64' + valid-lifetime: 2400 + - prefix: '1000:3000::/64' + config: + prefix: '1000:3000::/64' + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::200' + config: + address: '100::200' + - name: 'Eth1/2' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + openconfig-interfaces-ext:ra-hop-limit: 10 + openconfig-interfaces-ext:min-ra-interval: 100 + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test2.com' + config: + dnssl-name: 'test2.com' + valid-lifetime: 300 + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '101::200' + config: + address: '101::200' + - address: '101::300' + config: + address: '101::300' + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + suppress: false + config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement' + method: 'delete' + +replaced_01: + module_args: + config: + - name: 'Eth1/31' + suppress: false + ra_hop_limit: 15 + ra_prefixes: + - prefix: 1000:1000::/64 + valid_lifetime: 1500 + router_address: true + - prefix: 1000:2000::/64 + valid_lifetime: 1500 + router_address: true + - name: 'Eth1/32' + ra_interval: 100 + ra_lifetime: 200 + min_ra_interval: 50 + dnssl: + - dnssl_name: 'host.com' + - dnssl_name: 'test.com' + valid_lifetime: 600 + rdnss: + - address: 100::200 + - address: 100::500 + - name: 'PortChannel 100' + suppress: false + reachable_time: 300 + ra_retrans_interval: 15000 + - name: 'Vlan101' + suppress: true + state: replaced + facts_get_requests: + - path: 'data/openconfig-interfaces:interfaces/interface' + response: + code: 200 + value: + openconfig-interfaces:interface: + - name: 'Eth1/31' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + interval: 300 + lifetime: 450 + openconfig-interfaces-ext:min-ra-interval: 150 + openconfig-interfaces-ext:managed-config: true + openconfig-interfaces-ext:ra-hop-limit: 10 + openconfig-interfaces-ext:reachable-time: 600 + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1000:2000::/64' + config: + prefix: '1000:2000::/64' + router-address: true + valid-lifetime: 3000 + - prefix: '1000:3000::/64' + config: + prefix: '1000:3000::/64' + off-link: true + no-autoconfig: true + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::300' + config: + address: '100::300' + - name: 'Eth1/32' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:min-ra-interval-msec: 50750 + openconfig-interfaces-ext:ra-interval-msec: 100250 + openconfig-interfaces-ext:ra-retrans-interval: 30000 + openconfig-interfaces-ext:other-config: true + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:LOW + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::200' + config: + address: '100:200' + valid-lifetime: 500 + - address: '100:300' + config: + address: '100:300' + valid-lifetime: 1000 + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test.com' + config: + dnss-name: 'test.com' + valid-lifetime: 600 + - dnssl-name: 'test2.com' + config: + dnssl-name: 'test2.com' + valid-lifetime: 600 + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:home-agent-config: true + openconfig-interfaces-ext:home-agent-lifetime: 100 + openconfig-interfaces-ext:home-agent-preference: 150 + - name: 'Vlan101' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:home-agent-config: true + openconfig-interfaces-ext:home-agent-lifetime: 100 + openconfig-interfaces-ext:home-agent-preference: 150 + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1001:1000::/64' + config: + prefix: '1001:1000::/64' + off-link: true + valid-lifetime: 2400 + config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/lifetime' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:managed-config' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:reachable-time' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:ra-prefixes' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:rdnss-addresses' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f31/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + config: + openconfig-interfaces-ext:ra-hop-limit: 15 + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1000:1000::/64' + config: + prefix: '1000:1000::/64' + router-address: true + valid-lifetime: 1500 + - prefix: '1000:2000::/64' + config: + prefix: '1000:2000::/64' + router-address: true + valid-lifetime: 1500 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/suppress' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval-msec' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:other-config' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-interval-msec' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-retrans-interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:router-preference' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:rdnss-addresses' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:dns-search-names/dns-search-name=test2.com' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:min-ra-interval' + method: 'patch' + data: + openconfig-interfaces-ext:min-ra-interval: 50 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/interval' + method: 'patch' + data: + interval: 100 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/lifetime' + method: 'patch' + data: + lifetime: 200 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f32/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'host.com' + config: + dnssl-name: 'host.com' + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::200' + config: + address: '100::200' + - address: '100::500' + config: + address: '100::500' + - path: 'data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:ra-retrans-interval: 15000 + openconfig-interfaces-ext:reachable-time: 300 + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/suppress' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:home-agent-config' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:home-agent-lifetime' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:home-agent-preference' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan101/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:ra-prefixes' + method: 'delete' + +overridden_01: + module_args: + config: + - name: 'Eth1/1' + suppress: false + ra_interval: 300 + ra_prefixes: + - prefix: 1000:1000::/64 + valid_lifetime: 1500 + router_address: true + - name: 'Eth1/2' + min_ra_interval: 50 + dnssl: + - dnssl_name: 'host.com' + rdnss: + - address: 100::100 + - name: 'PortChannel 101' + suppress: false + ra_lifetime: 720 + ra_hop_limit: 25 + router_preference: 'medium' + ra_prefixes: + - prefix: 1001:1000::/64 + dnssl: + - dnssl_name: 'test.com' + rdnss: + - address: 100::200 + - address: 100::300 + state: overridden + facts_get_requests: + - path: 'data/openconfig-interfaces:interfaces/interface' + response: + code: 200 + value: + openconfig-interfaces:interface: + - name: 'Eth1/1' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:ra-hop-limit: 15 + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1000:1000::/64' + config: + prefix: '1000:1000::/64' + router-address: true + valid-lifetime: 1500 + - prefix: '1000:2000::/64' + config: + prefix: '1000:2000::/64' + router-address: true + valid-lifetime: 1500 + - name: 'Eth1/2' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + interval: 100 + lifetime: 200 + openconfig-interfaces-ext:min-ra-interval: 50 + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::200' + config: + address: '100::200' + - address: '100::300' + config: + address: '100::300' + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test.com' + config: + dnss-name: 'test.com' + valid-lifetime: 600 + - dnssl-name: 'host.com' + config: + dnssl-name: 'host.com' + - name: 'PortChannel100' + subinterfaces: + subinterface: + - index: 0 + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:reachable-time: 300 + openconfig-interfaces-ext:ra-retrans-interval: 15000 + - name: 'Vlan100' + openconfig-vlan:routed-vlan: + openconfig-if-ip:ipv6: + router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:home-agent-config: true + openconfig-interfaces-ext:home-agent-lifetime: 100 + openconfig-interfaces-ext:home-agent-preference: 150 + config_requests: + - path: 'data/openconfig-interfaces:interfaces/interface=PortChannel100/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Vlan100/openconfig-vlan:routed-vlan/openconfig-if-ip:ipv6/router-advertisement' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/openconfig-interfaces-ext:ra-hop-limit' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:ra-prefixes/ra-prefix=1000:2000::%2f64' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f1/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/interval' + method: 'patch' + data: + interval: 300 + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/interval' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/lifetime' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:dns-search-names/dns-search-name=test.com' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/openconfig-interfaces-ext:rdnss-addresses' + method: 'delete' + - path: 'data/openconfig-interfaces:interfaces/interface=Eth1%2f2/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::100' + config: + address: '100::100' + - path: 'data/openconfig-interfaces:interfaces/interface=PortChannel101/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement/config/lifetime' + method: 'patch' + data: + lifetime: 720 + - path: 'data/openconfig-interfaces:interfaces/interface=PortChannel101/subinterfaces/subinterface=0/openconfig-if-ip:ipv6/router-advertisement' + method: 'patch' + data: + openconfig-if-ip:router-advertisement: + config: + suppress: false + openconfig-interfaces-ext:ra-hop-limit: 25 + openconfig-interfaces-ext:router-preference: openconfig-interfaces-ext:MEDIUM + openconfig-interfaces-ext:dns-search-names: + dns-search-name: + - dnssl-name: 'test.com' + config: + dnssl-name: 'test.com' + openconfig-interfaces-ext:ra-prefixes: + ra-prefix: + - prefix: '1001:1000::/64' + config: + prefix: '1001:1000::/64' + openconfig-interfaces-ext:rdnss-addresses: + rdnss-address: + - address: '100::200' + config: + address: '100::200' + - address: '100::300' + config: + address: '100::300' diff --git a/tests/unit/modules/network/sonic/test_sonic_ipv6_router_advertisement.py b/tests/unit/modules/network/sonic/test_sonic_ipv6_router_advertisement.py new file mode 100644 index 000000000..231adf35d --- /dev/null +++ b/tests/unit/modules/network/sonic/test_sonic_ipv6_router_advertisement.py @@ -0,0 +1,96 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.dellemc.enterprise_sonic.tests.unit.compat.mock import ( + patch, +) +from ansible_collections.dellemc.enterprise_sonic.plugins.modules import ( + sonic_ipv6_router_advertisement, +) +from ansible_collections.dellemc.enterprise_sonic.tests.unit.modules.utils import ( + set_module_args, +) +from .sonic_module import TestSonicModule + + +class TestSonicIpv6RouterAdvertisementModule(TestSonicModule): + module = sonic_ipv6_router_advertisement + + @classmethod + def setUpClass(cls): + cls.mock_facts_edit_config = patch( + 'ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.{0}.{0}.edit_config'.format('ipv6_router_advertisement') + ) + cls.mock_config_edit_config = patch( + 'ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.{0}.{0}.edit_config'.format('ipv6_router_advertisement') + ) + cls.mock_get_interface_naming_mode = patch( + 'ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils.get_device_interface_naming_mode' + ) + cls.fixture_data = cls.load_fixtures('sonic_ipv6_router_advertisement.yaml') + + def setUp(self): + super(TestSonicIpv6RouterAdvertisementModule, self).setUp() + self.facts_edit_config = self.mock_facts_edit_config.start() + self.config_edit_config = self.mock_config_edit_config.start() + + self.facts_edit_config.side_effect = self.facts_side_effect + self.config_edit_config.side_effect = self.config_side_effect + + self.get_interface_naming_mode = self.mock_get_interface_naming_mode.start() + self.get_interface_naming_mode.return_value = 'standard' + + def tearDown(self): + super(TestSonicIpv6RouterAdvertisementModule, self).tearDown() + self.mock_facts_edit_config.stop() + self.mock_config_edit_config.stop() + self.mock_get_interface_naming_mode.stop() + + def test_sonic_ipv6_router_advertisement_merged_01(self): + set_module_args(self.fixture_data['merged_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_01']['facts_get_requests']) + self.initialize_config_requests(self.fixture_data['merged_01']['config_requests']) + + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ipv6_router_advertisement_merged_02(self): + set_module_args(self.fixture_data['merged_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['merged_02']['facts_get_requests']) + self.initialize_config_requests(self.fixture_data['merged_02']['config_requests']) + + result = self.execute_module(changed=False) + self.validate_config_requests() + + def test_sonic_ipv6_router_advertisement_deleted_01(self): + set_module_args(self.fixture_data['deleted_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_01']['facts_get_requests']) + self.initialize_config_requests(self.fixture_data['deleted_01']['config_requests']) + + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ipv6_router_advertisement_deleted_02(self): + set_module_args(self.fixture_data['deleted_02']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['deleted_02']['facts_get_requests']) + self.initialize_config_requests(self.fixture_data['deleted_02']['config_requests']) + + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ipv6_router_advertisement_replaced_01(self): + set_module_args(self.fixture_data['replaced_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['replaced_01']['facts_get_requests']) + self.initialize_config_requests(self.fixture_data['replaced_01']['config_requests']) + + result = self.execute_module(changed=True) + self.validate_config_requests() + + def test_sonic_ipv6_router_advertisement_overridden_01(self): + set_module_args(self.fixture_data['overridden_01']['module_args']) + self.initialize_facts_get_requests(self.fixture_data['overridden_01']['facts_get_requests']) + self.initialize_config_requests(self.fixture_data['overridden_01']['config_requests']) + + result = self.execute_module(changed=True) + self.validate_config_requests() From 6a0cb7ade169372a752c238615eaf9732ca03e0b Mon Sep 17 00:00:00 2001 From: ArunSaravananBalachandran Date: Mon, 30 Dec 2024 15:12:00 +0530 Subject: [PATCH 2/4] Update documentation --- .../ipv6_router_advertisement.py | 2 +- plugins/modules/sonic_ipv6_router_advertisement.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py index ce93e4c60..f84ca6fac 100644 --- a/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py +++ b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py @@ -43,7 +43,7 @@ TEST_KEYS = [ {'dnssl': {'dnssl_name': ''}}, {'ra_prefixes': {'prefix': ''}}, - {'rdnss': {'address'}} + {'rdnss': {'address': ''}} ] TEST_KEYS_formatted_diff = [ {'config': {'name': ''}}, diff --git a/plugins/modules/sonic_ipv6_router_advertisement.py b/plugins/modules/sonic_ipv6_router_advertisement.py index c62e1fe30..0e1515186 100644 --- a/plugins/modules/sonic_ipv6_router_advertisement.py +++ b/plugins/modules/sonic_ipv6_router_advertisement.py @@ -213,10 +213,10 @@ state: description: - The state of the configuration after module completion. - - C(merged) - Merges provided interface-specific PIM configuration with on-device configuration. - - C(replaced) - Replaces on-device PIM configuration of the specified interfaces with provided configuration. - - C(overridden) - Overrides all on-device interface-specific PIM configurations with the provided configuration. - - C(deleted) - Deletes on-device interface-specific PIM configuration. + - C(merged) - Merges provided interface-specific IPv6 router advertisement configuration with on-device configuration. + - C(replaced) - Replaces on-device IPv6 router advertisement configuration of the specified interfaces with provided configuration. + - C(overridden) - Overrides all on-device interface-specific IPv6 router advertisement configurations with the provided configuration. + - C(deleted) - Deletes on-device interface-specific IPv6 router advertisement configuration. type: str choices: - merged From 93519f2df4914eca9392f2e0926f3f2c48ccb414 Mon Sep 17 00:00:00 2001 From: ArunSaravananBalachandran Date: Mon, 30 Dec 2024 16:42:06 +0530 Subject: [PATCH 3/4] Remove unused argument --- .../ipv6_router_advertisement/ipv6_router_advertisement.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py b/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py index d0d7c339b..7afcb03fe 100644 --- a/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py +++ b/plugins/module_utils/network/sonic/facts/ipv6_router_advertisement/ipv6_router_advertisement.py @@ -122,7 +122,7 @@ def get_ipv6_router_advertisement(self): intf_name = interface['name'] if intf_name.startswith('Vlan'): ipv6_conf = interface.get('openconfig-vlan:routed-vlan', {}).get('openconfig-if-ip:ipv6') - ipv6_rtadv_config = self.render_config(ipv6_conf, intf_name, is_vlan=True) + ipv6_rtadv_config = self.render_config(ipv6_conf, intf_name) if ipv6_rtadv_config: ipv6_rtadv_configs.append(ipv6_rtadv_config) elif not (intf_name == 'eth0' or intf_name.startswith('Management') or '.' in intf_name or '|' in intf_name): @@ -138,7 +138,7 @@ def get_ipv6_router_advertisement(self): return ipv6_rtadv_configs - def render_config(self, ipv6_conf, intf_name, is_vlan=False): + def render_config(self, ipv6_conf, intf_name): ipv6_rtadv = {} if not ipv6_conf: return ipv6_rtadv From 731f75aecee771d27e9b7e82abc71cba95eda4e3 Mon Sep 17 00:00:00 2001 From: ArunSaravananBalachandran Date: Mon, 30 Dec 2024 17:58:26 +0530 Subject: [PATCH 4/4] Fix missing import --- .../ipv6_router_advertisement/ipv6_router_advertisement.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py index f84ca6fac..c769b1a41 100644 --- a/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py +++ b/plugins/module_utils/network/sonic/config/ipv6_router_advertisement/ipv6_router_advertisement.py @@ -36,6 +36,7 @@ get_new_config, get_formatted_config_diff ) +from ansible.module_utils.connection import ConnectionError DELETE = 'DELETE' PATCH = 'PATCH' @@ -225,8 +226,8 @@ def _state_replaced_overridden(self, want, have, state): continue conf = next((item for item in want if item['name'] == intf_name), {}) - # Delete all interface rtadv config if not specified in 'overridden', or - # if only interface name is specified + # Delete all interface router advertisement config if not specified in 'overridden', + # or if only interface name is specified if not conf: if state == 'overridden': del_commands.append({'name': have_conf['name']}) @@ -314,7 +315,7 @@ def _state_deleted(self, want, have): if len(have_conf.keys()) == 1: continue - # Delete all L3 config if only interface name is specified + # Delete all interface router advertisement config if only interface name is specified if len(conf.keys()) == 1: commands.append(conf) requests.extend(self.get_delete_ipv6_rtadv_requests(conf, have_conf, True))