From 243f55a0b1108c9730d7fc2d6e6cb6ed018aea13 Mon Sep 17 00:00:00 2001 From: Shade Talabi Date: Fri, 27 Sep 2024 15:08:13 -0700 Subject: [PATCH 1/7] Add fallback and fast_rate attributes to LAG interfaces module --- .../argspec/lag_interfaces/lag_interfaces.py | 6 +- .../config/lag_interfaces/lag_interfaces.py | 234 +++++++++++------- .../facts/lag_interfaces/lag_interfaces.py | 171 ++++++------- plugins/modules/sonic_lag_interfaces.py | 42 +++- tests/regression/hosts | 5 +- .../regression/roles/common/defaults/main.yml | 4 +- .../sonic_lag_interfaces/defaults/main.yml | 25 +- tests/regression/test.yaml | 62 ----- .../sonic/fixtures/sonic_lag_interfaces.yaml | 40 ++- 9 files changed, 319 insertions(+), 270 deletions(-) diff --git a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py index db984758d..98d0f002a 100644 --- a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved +# 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) @@ -67,7 +67,9 @@ def __init__(self, **kwargs): "df_preference": {"type": "int"}, }, "type": "dict" - } + }, + "fallback": {"type": "bool"}, + "fast_rate": {"type": "bool"} }, "type": "list" }, diff --git a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py index f36ac2dbc..49789c056 100644 --- a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved +# 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) """ @@ -65,6 +65,7 @@ PATCH = 'patch' DELETE = 'delete' TEST_KEYS = [ + {'config': {'name': ''}}, {'interfaces': {'member': ''}}, ] TEST_KEYS_formatted_diff = [ @@ -111,8 +112,8 @@ def execute_module(self): :returns: The result from module execution """ result = {'changed': False} - warnings = list() - commands = list() + warnings = [] + commands = [] existing_lag_interfaces_facts = self.get_lag_interfaces_facts() commands, requests = self.set_config(existing_lag_interfaces_facts) if commands: @@ -200,36 +201,36 @@ def _state_replaced(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = list() - commands = list() - delete_list = list() + requests = [] + commands = [] + delete_list = [] delete_list = get_diff(have, want, TEST_KEYS) delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list) - replaced_list = list() + replaced_list = [] for i in want: - list_obj = search_obj_in_list(i['name'], delete_members, "name") + list_obj = search_obj_in_list(i['name'], delete_members, 'name') if list_obj: replaced_list.append(list_obj) requests = self.get_delete_lag_interfaces_requests(replaced_list) if requests: - commands.extend(update_states(replaced_list, "deleted")) + commands.extend(update_states(replaced_list, 'deleted')) es_commands, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, want, have, True) if es_requests: - es_cmds = list() + es_cmds = [] for cmd in es_commands: po_name = cmd['name'] cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) if not cmd_in_cmds: es_cmds.append(cmd) if es_cmds: - commands.extend(update_states(es_cmds, "deleted")) + commands.extend(update_states(es_cmds, 'deleted')) requests.extend(es_requests) - replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "replaced") + replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, 'replaced') if replaced_requests: commands.extend(replaced_commands) requests.extend(replaced_requests) @@ -243,15 +244,15 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = list() - commands = list() - delete_list = list() + requests = [] + commands = [] + delete_list = [] delete_list = get_diff(have, want, TEST_KEYS) delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list) - replaced_list = list() + replaced_list = [] for i in want: - list_obj = search_obj_in_list(i['name'], delete_members, "name") + list_obj = search_obj_in_list(i['name'], delete_members, 'name') if list_obj: replaced_list.append(list_obj) @@ -260,23 +261,25 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): nu, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, want, have, True) requests.extend(es_requests) - commands.extend(update_states(replaced_list, "deleted")) + commands.extend(update_states(replaced_list, 'deleted')) - deleted_po_list = list() + deleted_po_list = [] + deleted_name_list = [] for i in delete_list: - list_obj = search_obj_in_list(i['name'], want, "name") + list_obj = search_obj_in_list(i['name'], want, 'name') if not list_obj: deleted_po_list.append(i) + deleted_name_list.append({'name': i['name']}) nu, es_requests = self.get_delete_po_ethernet_segment_requests(deleted_po_list, have) requests.extend(es_requests) - requests_deleted_po = self.get_delete_portchannel_requests(deleted_po_list) + requests_deleted_po = self.get_delete_portchannel_requests(deleted_name_list) requests.extend(requests_deleted_po) commands_del = self.prune_commands(deleted_po_list) - commands.extend(update_states(commands_del, "deleted")) + commands.extend(update_states(commands_del, 'deleted')) - override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "overridden") + override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, 'overridden') commands.extend(override_commands) requests.extend(override_requests) @@ -290,7 +293,8 @@ def _state_merged(self, want, have, diff_members, diff_portchannels): the current configuration """ commands, requests = self.template_for_lag_creation(have, diff_members, - diff_portchannels, "merged") + diff_portchannels, 'merged') + return commands, requests def _state_deleted(self, want, have, diff): @@ -300,23 +304,23 @@ def _state_deleted(self, want, have, diff): :returns: the commands necessary to remove the current configuration of the provided objects """ - commands = list() - requests = list() - portchannel_requests = list() + commands = [] + requests = [] + portchannel_requests = [] # if want is none, then delete all the lag interfaces and all portchannels if not want: requests = self.get_delete_all_lag_interfaces_requests() portchannel_requests = self.get_delete_all_portchannel_requests() requests.extend(portchannel_requests) commands_del = self.prune_commands(have) - commands.extend(update_states(commands_del, "deleted")) + commands.extend(update_states(commands_del, 'deleted')) else: # delete specific lag interfaces and specific portchannels po_commands = get_diff(want, diff, TEST_KEYS) po_commands = remove_empties_from_list(po_commands) want_members, want_portchannels = self.diff_list_for_member_creation(po_commands) del_commands, del_requests = self.template_for_lag_deletion(want, have, want_members, - want_portchannels, "deleted") + want_portchannels, 'deleted') if del_commands: commands.extend(del_commands) if del_requests: @@ -324,27 +328,21 @@ def _state_deleted(self, want, have, diff): return commands, requests def diff_list_for_member_creation(self, diff): - diff_members = list() - diff_portchannels = list() + diff_members = [] + diff_portchannels = [] for x in diff: - if "members" in x.keys() or "ethernet_segment" in x.keys(): + if 'members' in x.keys() or 'ethernet_segment' in x.keys(): diff_members.append(x) else: diff_portchannels.append(x) return diff_members, diff_portchannels def template_for_lag_creation(self, have, diff_members, diff_portchannels, state_name): - commands = list() - requests = list() + commands = [] + requests = [] if diff_members: - commands_portchannels, requests = self.call_create_port_channel(diff_members, have) - if commands_portchannels: - po_list = [{'name': x['name']} for x in commands_portchannels if x['name']] - else: - po_list = [] - if po_list: - commands.extend(update_states(po_list, state_name)) - diff_members_remove_none = [x for x in diff_members if x.get("members")] + commands_portchannels, requests = self.call_create_portchannel(diff_members, have) + diff_members_remove_none = [x for x in diff_members if x.get('members')] if diff_members_remove_none: request = self.create_lag_interfaces_requests(diff_members_remove_none) if request: @@ -358,21 +356,22 @@ def template_for_lag_creation(self, have, diff_members, diff_portchannels, state commands.extend(update_states(diff_members, state_name)) if diff_portchannels: - portchannels, po_requests = self.call_create_port_channel(diff_portchannels, have) + portchannels, po_requests = self.call_create_portchannel(diff_portchannels, have) requests.extend(po_requests) commands.extend(update_states(portchannels, state_name)) return commands, requests def template_for_lag_deletion(self, want, have, delete_members, delete_portchannels, state_name): - commands = list() - requests = list() - portchannel_requests = list() + commands = [] + requests = [] + portchannel_requests = [] if delete_members: - delete_members_remove_none = [x for x in delete_members if "members" in x.keys() and x["members"]] + del_po_requests = self.get_delete_portchannel_requests(delete_members) + delete_members_remove_none = [x for x in delete_members if 'members' in x.keys() and x['members']] requests = self.get_delete_lag_interfaces_requests(delete_members_remove_none) - delete_all_members = [x for x in delete_members if "members" in x.keys() and not x["members"]] - delete_all_list = list() + delete_all_members = [x for x in delete_members if 'members' in x.keys() and not x['members']] + delete_all_list = [] if delete_all_members: for i in delete_all_members: list_obj = search_obj_in_list(i['name'], have, "name") @@ -386,13 +385,15 @@ def template_for_lag_deletion(self, want, have, delete_members, delete_portchann requests.extend(deleteall_requests) elif deleteall_requests: requests = deleteall_requests + if del_po_requests: + requests.extend(del_po_requests) if requests: commands.extend(update_states(delete_members, state_name)) es_commands, es_requests = self.get_delete_ethernet_segment_requests(delete_members, want, have, False) if es_requests: - es_cmds = list() + es_cmds = [] for cmd in es_commands: po_name = cmd['name'] cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) @@ -435,13 +436,13 @@ def create_lag_interfaces_requests(self, commands): def create_ethernet_segment_requests(self, diff_members, have): es_commands = [] es_path = 'data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments' - es_payload_list = list() + es_payload_list = [] for cmd in diff_members: po_name = cmd['name'] - cmd_es = cmd.get('ethernet_segment', None) + cmd_es = cmd.get('ethernet_segment') if cmd_es: - es = dict() + es = {} have_po = next((po for po in have if po['name'] == po_name), {}) have_es = have_po.get('ethernet_segment', {}) @@ -499,57 +500,82 @@ def create_ethernet_segment_requests(self, diff_members, have): def build_create_payload_member(self, name): payload_template = """{\n"openconfig-if-aggregate:aggregate-id": "{{name}}"\n}""" - input_data = {"name": name} + input_data = {'name': name} env = jinja2.Environment(autoescape=False) t = env.from_string(payload_template) intended_payload = t.render(input_data) ret_payload = json.loads(intended_payload) return ret_payload - def build_create_payload_portchannel(self, name, mode): - payload_template = """{\n"openconfig-interfaces:interfaces": {"interface": [{\n"name": "{{name}}",\n"config": {\n"name": "{{name}}"\n}""" - input_data = {"name": name} - if mode == "static": - payload_template += """,\n "openconfig-if-aggregation:aggregation": {\n"config": {\n"lag-type": "{{mode}}"\n}\n}\n""" - input_data["mode"] = mode.upper() - payload_template += """}\n]\n}\n}""" - env = jinja2.Environment(autoescape=False) - t = env.from_string(payload_template) - intended_payload = t.render(input_data) - ret_payload = json.loads(intended_payload) - return ret_payload - - def create_port_channel(self, cmd): - requests = [] + def create_portchannel(self, cmd): + request = None + portchannel_list = [] path = 'data/openconfig-interfaces:interfaces' - for i in cmd: - payload = self.build_create_payload_portchannel(i['name'], i.get('mode', None)) + + for portchannel in cmd: + portchannel_dict = {} + aggregation_cfg_dict = {} + name = portchannel.get('name') + fallback = portchannel.get('fallback') + fast_rate = portchannel.get('fast_rate') + mode = portchannel.get('mode') + + portchannel_dict.update({'name': name, 'config': {'name': name}}) + if fallback is not None: + aggregation_cfg_dict['fallback'] = fallback + if fast_rate is not None: + aggregation_cfg_dict['fast-rate'] = fast_rate + if mode: + aggregation_cfg_dict['lag-type'] = mode.upper() + if aggregation_cfg_dict: + portchannel_dict.update({'openconfig-if-aggregate:aggregation': {'config': aggregation_cfg_dict}}) + if portchannel_dict: + portchannel_list.append(portchannel_dict) + + if portchannel_list: + payload = {'openconfig-interfaces:interfaces': {'interface': portchannel_list}} request = {'path': path, 'method': PATCH, 'data': payload} - requests.append(request) - return requests - def call_create_port_channel(self, commands, have): - commands_list = list() - for c in commands: - if not any(d['name'] == c['name'] for d in have): - commands_list.append(c) - requests = self.create_port_channel(commands_list) + return request + + def call_create_portchannel(self, commands, have): + commands_list = [] + requests = [] + + have_po_dict = {have_po.get('name'): have_po for have_po in have} + for po in commands: + name = po.get('name') + have_po = have_po_dict.get(name) + if not have_po: + commands_list.append(po) + continue + fallback = po.get('fallback') + fast_rate = po.get('fast_rate') + mode = po.get('mode') + have_fallback = have_po.get('fallback') + have_fast_rate = have_po.get('fast_rate') + have_mode = have_po.get('mode') + + if ((fallback is not None and fallback != have_fallback) or (fast_rate is not None and fast_rate != have_fast_rate) or + (mode and mode != have_mode)): + commands_list.append(po) + + if commands_list: + requests.append(self.create_portchannel(commands_list)) return commands_list, requests def get_delete_all_lag_interfaces_requests(self): requests = [] delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL_MEMBER/PORTCHANNEL_MEMBER_LIST' - method = DELETE - delete_all_lag_request = {"path": delete_all_lag_url, "method": method} + delete_all_lag_request = {'path': delete_all_lag_url, 'method': DELETE} requests.append(delete_all_lag_request) return requests def get_delete_all_portchannel_requests(self): requests = [] delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST' - method = DELETE - delete_all_lag_request = {"path": delete_all_lag_url, "method": method} + delete_all_lag_request = {'path': delete_all_lag_url, 'method': DELETE} requests.append(delete_all_lag_request) return requests @@ -557,7 +583,6 @@ def get_delete_lag_interfaces_requests(self, commands): requests = [] # Create URL and payload url = 'data/openconfig-interfaces:interfaces/interface={}/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id' - method = DELETE for c in commands: if c.get('members') and c['members'].get('interfaces'): interfaces = c['members']['interfaces'] @@ -565,8 +590,8 @@ def get_delete_lag_interfaces_requests(self, commands): continue for each in interfaces: - ifname = each["member"] - request = {"path": url.format(ifname), "method": method} + ifname = each['member'] + request = {'path': url.format(ifname), 'method': DELETE} requests.append(request) return requests @@ -599,7 +624,7 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet for cmd in delete_members: po_name = cmd['name'] - cmd_es = cmd.get('ethernet_segment', None) + cmd_es = cmd.get('ethernet_segment') if cmd_es: have_po = next((po for po in have if po['name'] == po_name), {}) have_es = have_po.get('ethernet_segment', {}) @@ -638,15 +663,32 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet return es_commands, es_requests + def get_delete_portchannel_request(self, name, attr): + url = f'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST={name}' + if attr: + url += f'/{attr}' + request = {'path': url, 'method': DELETE} + return request + def get_delete_portchannel_requests(self, commands): requests = [] - # Create URL and payload - url = 'data/openconfig-interfaces:interfaces/interface={}' - method = DELETE + # Generate list of requests from commands for c in commands: - name = c["name"] - request = {"path": url.format(name), "method": method} - requests.append(request) + name = c['name'] + fallback = c.get('fallback') + fast_rate = c.get('fast_rate') + mode = c.get('mode') + ethernet_segment = c.get('ethernet_segment') + members = c.get('members') + + if fallback is not None: + requests.append(self.get_delete_portchannel_request(name, 'fallback')) + if fast_rate is not None: + requests.append(self.get_delete_portchannel_request(name, 'fast_rate')) + if mode: + requests.append(self.get_delete_portchannel_request(name, 'static')) + if fallback is None and fast_rate is None and not mode and not ethernet_segment and not members: + requests.append(self.get_delete_portchannel_request(name, None)) return requests @@ -676,9 +718,9 @@ def validate_want(self, want, state): return for conf in want: - es = conf.get('ethernet_segment', None) + es = conf.get('ethernet_segment') if es: - esi = es.get('esi', None) + esi = es.get('esi') if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: if esi and esi != 'AUTO': self._module.fail_json(msg='value of esi must be "AUTO" for esi_type {0}'.format(es['esi_type'])) @@ -694,7 +736,7 @@ def preprocess_want(self, want, state): if not want: return for conf in want: - es = conf.get('ethernet_segment', None) + es = conf.get('ethernet_segment') if es: if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: if state != 'deleted': diff --git a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py index cf97879e5..e25fd7242 100644 --- a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved +# 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) """ @@ -25,8 +25,6 @@ ) from ansible.module_utils.connection import ConnectionError -GET = "get" - class Lag_interfacesFacts(object): """ The sonic lag_interfaces fact class @@ -48,41 +46,74 @@ def __init__(self, module, subspec='config', options='options'): def get_all_portchannels(self): """Get all the interfaces available in chassis""" - request = [{"path": "data/sonic-portchannel:sonic-portchannel", "method": GET}] + data = [] + request = {'path': 'data/sonic-portchannel:sonic-portchannel', 'method': 'get'} try: response = edit_config(self._module, to_request(self._module, request)) + if response[0][1]: + data = response[0][1].get('sonic-portchannel:sonic-portchannel', []) except ConnectionError as exc: self._module.fail_json(msg=str(exc), code=exc.code) - if response[0][1]: - data = response[0][1]['sonic-portchannel:sonic-portchannel'] - else: - data = [] return data def get_po_and_po_members(self, data): - if data is not None: - if "PORTCHANNEL_MEMBER" in data: - portchannel_members_list = data["PORTCHANNEL_MEMBER"]["PORTCHANNEL_MEMBER_LIST"] - else: - portchannel_members_list = [] - if "PORTCHANNEL" in data: - portchannel_list = data["PORTCHANNEL"]["PORTCHANNEL_LIST"] - else: - portchannel_list = [] - if portchannel_list: - for i in portchannel_list: - if not any(d["name"] == i["name"] for d in portchannel_members_list): - portchannel_members_list.append({'ifname': None, 'name': i['name']}) + all_portchannels_list = [] + merged_portchannels = [] + if data: - return portchannel_members_list - else: - return [] + if 'PORTCHANNEL' in data: + portchannel_list = data['PORTCHANNEL']['PORTCHANNEL_LIST'] + all_portchannels_list.extend(portchannel_list) + if 'PORTCHANNEL_MEMBER' in data: + portchannel_members_list = data['PORTCHANNEL_MEMBER']['PORTCHANNEL_MEMBER_LIST'] + all_portchannels_list.extend(portchannel_members_list) + + if all_portchannels_list: + mode_dict = {True: 'static', False: 'lacp'} + for portchannel in all_portchannels_list: + name = portchannel.get('name') + fallback = portchannel.get('fallback') + fast_rate = portchannel.get('fast_rate') + member = portchannel.get('ifname') + mode = portchannel.get('static') + + # Find if portchannel already exists and update + matched = next((portchannel for portchannel in merged_portchannels if portchannel['name'] == name), None) + if matched: + if fallback is not None: + matched['fallback'] = fallback + if fast_rate is not None: + matched['fast_rate'] = fast_rate + if member: + if 'members' in matched and matched['members'].get('interfaces'): + matched['members']['interfaces'].append({'member': member}) + else: + matched['members'] = {'interfaces': [{'member': member}]} + if mode is not None: + matched['mode'] = mode_dict[mode] + # Create new portchannel if it doesn't already exist + else: + new_portchannel = {} + if name: + new_portchannel['name'] = name + if fallback is not None: + new_portchannel['fallback'] = fallback + if fast_rate is not None: + new_portchannel['fast_rate'] = fast_rate + if member: + new_portchannel['members'] = {'interfaces': [{'member': member}]} + if mode is not None: + new_portchannel['mode'] = mode_dict[mode] + if new_portchannel: + merged_portchannels.append(new_portchannel) + + return merged_portchannels def get_ethernet_segments(self, data): es_list = [] if data: - if "EVPN_ETHERNET_SEGMENT" in data: + if 'EVPN_ETHERNET_SEGMENT' in data: es_list = data["EVPN_ETHERNET_SEGMENT"]["EVPN_ETHERNET_SEGMENT_LIST"] return es_list @@ -97,42 +128,35 @@ def populate_facts(self, connection, ansible_facts, data=None): objs = [] if not data: data = self.get_all_portchannels() + objs = self.get_po_and_po_members(data) + + es_data = self.get_ethernet_segments(data) + for es in es_data: + po_name = es['ifname'] + esi_t = es['esi_type'] + esi = es['esi'] + if 'df_pref' in es: + df_pref = es['df_pref'] + else: + df_pref = None - po_data = self.get_po_and_po_members(data) - for conf in po_data: - if conf: - obj = self.render_config(self.generated_spec, conf) - obj = self.transform_config(obj) - if obj: - self.merge_portchannels(objs, obj) - - es_data = self.get_ethernet_segments(data) - for es in es_data: - po_name = es['ifname'] - esi_t = es['esi_type'] - esi = es['esi'] - if 'df_pref' in es: - df_pref = es['df_pref'] - else: - df_pref = None - - if esi_t == 'TYPE_1_LACP_BASED': - esi_type = 'auto_lacp' - elif esi_t == 'TYPE_3_MAC_BASED': - esi_type = 'auto_system_mac' - elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': - esi_type = 'ethernet_segment_id' + if esi_t == 'TYPE_1_LACP_BASED': + esi_type = 'auto_lacp' + elif esi_t == 'TYPE_3_MAC_BASED': + esi_type = 'auto_system_mac' + elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': + esi_type = 'ethernet_segment_id' - if df_pref: - es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} - else: - es_dict = {'esi_type': esi_type, 'esi': esi} + if df_pref: + es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} + else: + es_dict = {'esi_type': esi_type, 'esi': esi} - have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) - if have_po_conf: - have_po_conf['ethernet_segment'] = es_dict - else: - self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) + have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) + if have_po_conf: + have_po_conf['ethernet_segment'] = es_dict + else: + self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) facts = {} if objs: @@ -143,34 +167,3 @@ def populate_facts(self, connection, ansible_facts, data=None): ansible_facts['ansible_network_resources'].update(facts) return ansible_facts - - def render_config(self, spec, conf): - return conf - - def transform_config(self, conf): - trans_cfg = dict() - trans_cfg['name'] = conf['name'] - trans_cfg['members'] = dict() - if conf['ifname']: - interfaces = list() - interface = {'member': conf['ifname']} - interfaces.append(interface) - trans_cfg['members'] = {'interfaces': interfaces} - return trans_cfg - - def merge_portchannels(self, configs, conf): - if len(configs) == 0: - configs.append(conf) - else: - new_interface = None - if conf.get('members') and conf['members'].get('interfaces'): - new_interface = conf['members']['interfaces'][0] - else: - configs.append(conf) - if new_interface: - matched = next((cfg for cfg in configs if cfg['name'] == conf['name']), None) - if matched and matched.get('members'): - ext_interfaces = matched.get('members').get('interfaces', []) - ext_interfaces.append(new_interface) - else: - configs.append(conf) diff --git a/plugins/modules/sonic_lag_interfaces.py b/plugins/modules/sonic_lag_interfaces.py index c676fec81..ccc412bf9 100644 --- a/plugins/modules/sonic_lag_interfaces.py +++ b/plugins/modules/sonic_lag_interfaces.py @@ -103,6 +103,16 @@ - The preference for Designated Forwarder election method. The range of df_preference value is from 1 to 65535. type: int + fallback: + description: + - Enable/disable LACP fallback. + version_added: 3.1.0 + type: bool + fast_rate: + description: + - When set to true LACP packets will be sent every second; otherwise, the LACP packets will be sent every 30 seconds. + version_added: 3.1.0 + type: bool state: description: - The state that the configuration should be left in. @@ -139,6 +149,9 @@ esi_type: auto_lacp df_preference: 2222 - name: PortChannel12 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/15 @@ -165,7 +178,9 @@ # evpn ethernet-segment auto-lacp # df-preference 2222 # ! -# interface PortChannel12 +# interface PortChannel12 mode active +# fast_rate +# fallback # no shutdown # # @@ -186,7 +201,9 @@ # speed 100000 # no shutdown # ! -# interface PortChannel10 +# interface PortChannel10 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -203,6 +220,8 @@ esi_type: auto_system_mac df_preference: 6666 - name: PortChannel10 + fallback: false + fast_rate: false members: interfaces: - member: Eth1/7 @@ -231,7 +250,7 @@ # speed 100000 # no shutdown # -# interface PortChannel10 +# interface PortChannel10 mode active # no shutdown # ! # evpn ethernet-segment auto-system-mac @@ -270,6 +289,9 @@ dellemc.enterprise_sonic.sonic_lag_interfaces: config: - name: PortChannel20 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/6 @@ -292,7 +314,9 @@ # speed 100000 # no shutdown # -# interface PortChannel20 +# interface PortChannel20 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -302,7 +326,9 @@ # # Before state: # ------------- -# interface PortChannel 10 +# interface PortChannel 10 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -350,7 +376,9 @@ # speed 100000 # no shutdown # ! -# interface PortChannel10 +# interface PortChannel10 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -360,6 +388,8 @@ sonic_lag_interfaces: config: - name: PortChannel10 + fallback: true + fast_rate: true members: interfaces: - member: Eth1/10 diff --git a/tests/regression/hosts b/tests/regression/hosts index eb3fd75ff..d49853a2e 100644 --- a/tests/regression/hosts +++ b/tests/regression/hosts @@ -1,10 +1,7 @@ -sonic1 ansible_host=100.94.81.17 ansible_user=admin ansible_password=admin -sonic2 ansible_host=100.94.81.19 ansible_user=admin ansible_password=admin -#sonic2 ansible_user=admin ansible_password=admin +sonic1 ansible_host=100.94.123.25 ansible_user=admin ansible_password=Sonic@Dell [datacenter] sonic1 -sonic2 [datacenter:vars] ansible_network_os=dellemc.enterprise_sonic.sonic diff --git a/tests/regression/roles/common/defaults/main.yml b/tests/regression/roles/common/defaults/main.yml index be0c9eaa0..0b76e199b 100644 --- a/tests/regression/roles/common/defaults/main.yml +++ b/tests/regression/roles/common/defaults/main.yml @@ -28,7 +28,7 @@ single_run_idem_condition: "{{ 'Passed' if (single_run_task_output.failed == fal else 'Failed' }}" -REPORT_DIR: "/var/www/html/ansible/regression" +REPORT_DIR: "~" empty: [] module_name1: debug @@ -37,7 +37,7 @@ std_name: STANDARD std_ext_name: STANDARD_EXT native_name: NATIVE -interface_mode: STANDARD +interface_mode: NATIVE default_interface_cli_std: default interface range Eth 1/5-1/10 default_interface_cli_native: default interface range Ethernet20-40 diff --git a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml index ff0aa41b4..177e7a8ba 100644 --- a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml +++ b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml @@ -38,14 +38,13 @@ tests: members: interfaces: - member: "{{ interface3 }}" - - name: PortChannel43 - mode: static + fallback: true + fast_rate: true - name: test_case_03 description: Update interface parameters state: merged input: - name: PortChannel40 - mode: static members: interfaces: - member: "{{ interface1 }}" @@ -54,11 +53,12 @@ tests: esi_type: auto_lacp df_preference: 2222 - name: PortChannel41 - mode: lacp members: interfaces: - member: "{{ interface3 }}" - member: "{{ interface4 }}" + fallback: false + fast_rate: false - name: PortChannel42 - name: test_case_04 description: Delete interface parameters @@ -74,6 +74,8 @@ tests: - name: PortChannel41 members: interfaces: + fallback: false + fast_rate: false - name: PortChannel42 - name: test_case_05 description: Update interface parameters @@ -108,6 +110,13 @@ tests: esi_type: auto_lacp df_preference: 2233 - name: test_case_07 + description: Replace portchannel fallback and fast_rate + state: replaced + input: + - name: po41 + fallback: true + fast_rate: true + - name: test_case_08 description: Override portchannel configuration state: overridden input: @@ -122,7 +131,7 @@ tests: members: interfaces: - member: "{{ interface2 }}" - - name: test_case_08 + - name: test_case_09 description: Override all portchannel configuration state: overridden input: @@ -138,17 +147,19 @@ tests: interfaces: - member: "{{ interface5 }}" - name: po43 + fallback: true + fast_rate: true members: interfaces: - member: "{{ interface6 }}" - - name: test_case_09 + - name: test_case_10 description: Create standalone portchannels state: merged input: - name: portchannel42 - name: portchannel 12 - name: po10 - - name: test_case_10 + - name: test_case_11 description: Update interface parameters state: deleted input: [] diff --git a/tests/regression/test.yaml b/tests/regression/test.yaml index 87c657c77..a94128bdf 100644 --- a/tests/regression/test.yaml +++ b/tests/regression/test.yaml @@ -8,67 +8,5 @@ collections: - dellemc.enterprise_sonic roles: - - sonic_api - - sonic_command - - sonic_config - - sonic_image_management - - sonic_system - - sonic_interfaces - - sonic_l2_interfaces - sonic_lag_interfaces - - sonic_mclag - - sonic_vlans - - sonic_l3_interfaces - - sonic_bgp_communities - - sonic_bgp_ext_communities - - sonic_bgp_as_paths - - sonic_bgp - - sonic_bgp_af - - sonic_bgp_neighbors - - sonic_bgp_neighbors_af - - sonic_ospfv2_interfaces - - sonic_ospfv2 - - sonic_dhcp_snooping - - sonic_vlan_mapping - - sonic_vrfs - - sonic_vrrp - - sonic_vxlan - - sonic_port_breakout - - sonic_users - - sonic_aaa - - sonic_ldap - - sonic_tacacs_server - - sonic_radius_server - - sonic_prefix_lists - - sonic_static_routes - - sonic_ntp - - sonic_logging - - sonic_ip_neighbor - - sonic_port_group - - sonic_dhcp_relay - - sonic_acl_interfaces - - sonic_l2_acls - - sonic_l3_acls - - sonic_lldp_global - - sonic_mac - - sonic_bfd - - sonic_copp - - sonic_route_maps - - sonic_lldp_interfaces - - sonic_stp - - sonic_sflow - - sonic_fips - - sonic_roce - - sonic_qos_buffer - - sonic_qos_pfc - - sonic_qos_maps - - sonic_qos_scheduler - - sonic_qos_wred - - sonic_qos_interfaces - - sonic_pim_global - - sonic_pim_interfaces - - sonic_login_lockout - - sonic_ospf_area - - sonic_poe - - sonic_mgmt_servers - test_reports diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml index d8415baf9..996c9e527 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml @@ -16,6 +16,9 @@ merged_01: - member: Eth1/21 - member: Eth1/22 - name: PortChannel30 + fallback: true + fast_rate: true + mode: lacp existing_lag_interfaces_config: - path: "data/openconfig-interfaces:interfaces/interface" response: @@ -53,6 +56,11 @@ merged_01: - name: PortChannel30 config: name: PortChannel30 + openconfig-if-aggregate:aggregation: + config: + fallback: true + fast-rate: true + lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F11/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: @@ -126,6 +134,9 @@ deleted_02: interfaces: - member: Eth1/23 - name: PortChannel30 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/31 @@ -141,6 +152,9 @@ deleted_02: - name: PortChannel10 - name: PortChannel20 - name: PortChannel30 + fallback: true + fast_rate: true + static: false - name: PortChannel40 PORTCHANNEL_MEMBER: PORTCHANNEL_MEMBER_LIST: @@ -183,10 +197,16 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f23/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" data: + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/fallback" + method: "delete" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/fast_rate" + method: "delete" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/static" + method: "delete" - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f31/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" data: - - path: "data/openconfig-interfaces:interfaces/interface=PortChannel40" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel40" method: "delete" data: - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" @@ -245,6 +265,9 @@ replaced_01: ethernet_segment: esi_type: auto_system_mac - name: PortChannel20 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/23 @@ -276,6 +299,11 @@ replaced_01: - name: PortChannel20 config: name: PortChannel20 + openconfig-if-aggregate:aggregation: + config: + fallback: true + fast-rate: true + lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: @@ -312,6 +340,9 @@ overridden_01: ethernet_segment: esi_type: auto_lacp - name: PortChannel20 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/23 @@ -342,7 +373,7 @@ overridden_01: - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" method: "delete" data: - - path: "data/openconfig-interfaces:interfaces/interface=PortChannel30" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30" method: "delete" data: - path: "data/openconfig-interfaces:interfaces" @@ -353,6 +384,11 @@ overridden_01: - name: PortChannel20 config: name: PortChannel20 + openconfig-if-aggregate:aggregation: + config: + fallback: true + fast-rate: true + lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: From c77dfc1a371edfe5d3068b2e698517173f6c6e30 Mon Sep 17 00:00:00 2001 From: Shade Talabi Date: Fri, 27 Sep 2024 15:14:17 -0700 Subject: [PATCH 2/7] Add fragment --- .../fragments/453-add-fallback-fast-rate-attributes.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml diff --git a/changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml b/changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml new file mode 100644 index 000000000..f7e309a66 --- /dev/null +++ b/changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml @@ -0,0 +1,4 @@ +--- +minor_changes: + - sonic_lag_interfaces - Add fallback and fast_rate attributes to LAG interfaces module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/453). + From 5ab6612759c7b099fab785c6c7adaeaf87679c32 Mon Sep 17 00:00:00 2001 From: Shade Talabi Date: Fri, 27 Sep 2024 15:14:21 -0700 Subject: [PATCH 3/7] Revert "Add fallback and fast_rate attributes to LAG interfaces module" This reverts commit 243f55a0b1108c9730d7fc2d6e6cb6ed018aea13. --- .../argspec/lag_interfaces/lag_interfaces.py | 6 +- .../config/lag_interfaces/lag_interfaces.py | 234 +++++++----------- .../facts/lag_interfaces/lag_interfaces.py | 171 +++++++------ plugins/modules/sonic_lag_interfaces.py | 42 +--- tests/regression/hosts | 5 +- .../regression/roles/common/defaults/main.yml | 4 +- .../sonic_lag_interfaces/defaults/main.yml | 25 +- tests/regression/test.yaml | 62 +++++ .../sonic/fixtures/sonic_lag_interfaces.yaml | 40 +-- 9 files changed, 270 insertions(+), 319 deletions(-) diff --git a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py index 98d0f002a..db984758d 100644 --- a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2020 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) @@ -67,9 +67,7 @@ def __init__(self, **kwargs): "df_preference": {"type": "int"}, }, "type": "dict" - }, - "fallback": {"type": "bool"}, - "fast_rate": {"type": "bool"} + } }, "type": "list" }, diff --git a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py index 49789c056..f36ac2dbc 100644 --- a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2020 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) """ @@ -65,7 +65,6 @@ PATCH = 'patch' DELETE = 'delete' TEST_KEYS = [ - {'config': {'name': ''}}, {'interfaces': {'member': ''}}, ] TEST_KEYS_formatted_diff = [ @@ -112,8 +111,8 @@ def execute_module(self): :returns: The result from module execution """ result = {'changed': False} - warnings = [] - commands = [] + warnings = list() + commands = list() existing_lag_interfaces_facts = self.get_lag_interfaces_facts() commands, requests = self.set_config(existing_lag_interfaces_facts) if commands: @@ -201,36 +200,36 @@ def _state_replaced(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = [] - commands = [] - delete_list = [] + requests = list() + commands = list() + delete_list = list() delete_list = get_diff(have, want, TEST_KEYS) delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list) - replaced_list = [] + replaced_list = list() for i in want: - list_obj = search_obj_in_list(i['name'], delete_members, 'name') + list_obj = search_obj_in_list(i['name'], delete_members, "name") if list_obj: replaced_list.append(list_obj) requests = self.get_delete_lag_interfaces_requests(replaced_list) if requests: - commands.extend(update_states(replaced_list, 'deleted')) + commands.extend(update_states(replaced_list, "deleted")) es_commands, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, want, have, True) if es_requests: - es_cmds = [] + es_cmds = list() for cmd in es_commands: po_name = cmd['name'] cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) if not cmd_in_cmds: es_cmds.append(cmd) if es_cmds: - commands.extend(update_states(es_cmds, 'deleted')) + commands.extend(update_states(es_cmds, "deleted")) requests.extend(es_requests) - replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, 'replaced') + replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "replaced") if replaced_requests: commands.extend(replaced_commands) requests.extend(replaced_requests) @@ -244,15 +243,15 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = [] - commands = [] - delete_list = [] + requests = list() + commands = list() + delete_list = list() delete_list = get_diff(have, want, TEST_KEYS) delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list) - replaced_list = [] + replaced_list = list() for i in want: - list_obj = search_obj_in_list(i['name'], delete_members, 'name') + list_obj = search_obj_in_list(i['name'], delete_members, "name") if list_obj: replaced_list.append(list_obj) @@ -261,25 +260,23 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): nu, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, want, have, True) requests.extend(es_requests) - commands.extend(update_states(replaced_list, 'deleted')) + commands.extend(update_states(replaced_list, "deleted")) - deleted_po_list = [] - deleted_name_list = [] + deleted_po_list = list() for i in delete_list: - list_obj = search_obj_in_list(i['name'], want, 'name') + list_obj = search_obj_in_list(i['name'], want, "name") if not list_obj: deleted_po_list.append(i) - deleted_name_list.append({'name': i['name']}) nu, es_requests = self.get_delete_po_ethernet_segment_requests(deleted_po_list, have) requests.extend(es_requests) - requests_deleted_po = self.get_delete_portchannel_requests(deleted_name_list) + requests_deleted_po = self.get_delete_portchannel_requests(deleted_po_list) requests.extend(requests_deleted_po) commands_del = self.prune_commands(deleted_po_list) - commands.extend(update_states(commands_del, 'deleted')) + commands.extend(update_states(commands_del, "deleted")) - override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, 'overridden') + override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "overridden") commands.extend(override_commands) requests.extend(override_requests) @@ -293,8 +290,7 @@ def _state_merged(self, want, have, diff_members, diff_portchannels): the current configuration """ commands, requests = self.template_for_lag_creation(have, diff_members, - diff_portchannels, 'merged') - + diff_portchannels, "merged") return commands, requests def _state_deleted(self, want, have, diff): @@ -304,23 +300,23 @@ def _state_deleted(self, want, have, diff): :returns: the commands necessary to remove the current configuration of the provided objects """ - commands = [] - requests = [] - portchannel_requests = [] + commands = list() + requests = list() + portchannel_requests = list() # if want is none, then delete all the lag interfaces and all portchannels if not want: requests = self.get_delete_all_lag_interfaces_requests() portchannel_requests = self.get_delete_all_portchannel_requests() requests.extend(portchannel_requests) commands_del = self.prune_commands(have) - commands.extend(update_states(commands_del, 'deleted')) + commands.extend(update_states(commands_del, "deleted")) else: # delete specific lag interfaces and specific portchannels po_commands = get_diff(want, diff, TEST_KEYS) po_commands = remove_empties_from_list(po_commands) want_members, want_portchannels = self.diff_list_for_member_creation(po_commands) del_commands, del_requests = self.template_for_lag_deletion(want, have, want_members, - want_portchannels, 'deleted') + want_portchannels, "deleted") if del_commands: commands.extend(del_commands) if del_requests: @@ -328,21 +324,27 @@ def _state_deleted(self, want, have, diff): return commands, requests def diff_list_for_member_creation(self, diff): - diff_members = [] - diff_portchannels = [] + diff_members = list() + diff_portchannels = list() for x in diff: - if 'members' in x.keys() or 'ethernet_segment' in x.keys(): + if "members" in x.keys() or "ethernet_segment" in x.keys(): diff_members.append(x) else: diff_portchannels.append(x) return diff_members, diff_portchannels def template_for_lag_creation(self, have, diff_members, diff_portchannels, state_name): - commands = [] - requests = [] + commands = list() + requests = list() if diff_members: - commands_portchannels, requests = self.call_create_portchannel(diff_members, have) - diff_members_remove_none = [x for x in diff_members if x.get('members')] + commands_portchannels, requests = self.call_create_port_channel(diff_members, have) + if commands_portchannels: + po_list = [{'name': x['name']} for x in commands_portchannels if x['name']] + else: + po_list = [] + if po_list: + commands.extend(update_states(po_list, state_name)) + diff_members_remove_none = [x for x in diff_members if x.get("members")] if diff_members_remove_none: request = self.create_lag_interfaces_requests(diff_members_remove_none) if request: @@ -356,22 +358,21 @@ def template_for_lag_creation(self, have, diff_members, diff_portchannels, state commands.extend(update_states(diff_members, state_name)) if diff_portchannels: - portchannels, po_requests = self.call_create_portchannel(diff_portchannels, have) + portchannels, po_requests = self.call_create_port_channel(diff_portchannels, have) requests.extend(po_requests) commands.extend(update_states(portchannels, state_name)) return commands, requests def template_for_lag_deletion(self, want, have, delete_members, delete_portchannels, state_name): - commands = [] - requests = [] - portchannel_requests = [] + commands = list() + requests = list() + portchannel_requests = list() if delete_members: - del_po_requests = self.get_delete_portchannel_requests(delete_members) - delete_members_remove_none = [x for x in delete_members if 'members' in x.keys() and x['members']] + delete_members_remove_none = [x for x in delete_members if "members" in x.keys() and x["members"]] requests = self.get_delete_lag_interfaces_requests(delete_members_remove_none) - delete_all_members = [x for x in delete_members if 'members' in x.keys() and not x['members']] - delete_all_list = [] + delete_all_members = [x for x in delete_members if "members" in x.keys() and not x["members"]] + delete_all_list = list() if delete_all_members: for i in delete_all_members: list_obj = search_obj_in_list(i['name'], have, "name") @@ -385,15 +386,13 @@ def template_for_lag_deletion(self, want, have, delete_members, delete_portchann requests.extend(deleteall_requests) elif deleteall_requests: requests = deleteall_requests - if del_po_requests: - requests.extend(del_po_requests) if requests: commands.extend(update_states(delete_members, state_name)) es_commands, es_requests = self.get_delete_ethernet_segment_requests(delete_members, want, have, False) if es_requests: - es_cmds = [] + es_cmds = list() for cmd in es_commands: po_name = cmd['name'] cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) @@ -436,13 +435,13 @@ def create_lag_interfaces_requests(self, commands): def create_ethernet_segment_requests(self, diff_members, have): es_commands = [] es_path = 'data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments' - es_payload_list = [] + es_payload_list = list() for cmd in diff_members: po_name = cmd['name'] - cmd_es = cmd.get('ethernet_segment') + cmd_es = cmd.get('ethernet_segment', None) if cmd_es: - es = {} + es = dict() have_po = next((po for po in have if po['name'] == po_name), {}) have_es = have_po.get('ethernet_segment', {}) @@ -500,82 +499,57 @@ def create_ethernet_segment_requests(self, diff_members, have): def build_create_payload_member(self, name): payload_template = """{\n"openconfig-if-aggregate:aggregate-id": "{{name}}"\n}""" - input_data = {'name': name} + input_data = {"name": name} env = jinja2.Environment(autoescape=False) t = env.from_string(payload_template) intended_payload = t.render(input_data) ret_payload = json.loads(intended_payload) return ret_payload - def create_portchannel(self, cmd): - request = None - portchannel_list = [] - path = 'data/openconfig-interfaces:interfaces' - - for portchannel in cmd: - portchannel_dict = {} - aggregation_cfg_dict = {} - name = portchannel.get('name') - fallback = portchannel.get('fallback') - fast_rate = portchannel.get('fast_rate') - mode = portchannel.get('mode') - - portchannel_dict.update({'name': name, 'config': {'name': name}}) - if fallback is not None: - aggregation_cfg_dict['fallback'] = fallback - if fast_rate is not None: - aggregation_cfg_dict['fast-rate'] = fast_rate - if mode: - aggregation_cfg_dict['lag-type'] = mode.upper() - if aggregation_cfg_dict: - portchannel_dict.update({'openconfig-if-aggregate:aggregation': {'config': aggregation_cfg_dict}}) - if portchannel_dict: - portchannel_list.append(portchannel_dict) - - if portchannel_list: - payload = {'openconfig-interfaces:interfaces': {'interface': portchannel_list}} - request = {'path': path, 'method': PATCH, 'data': payload} - - return request + def build_create_payload_portchannel(self, name, mode): + payload_template = """{\n"openconfig-interfaces:interfaces": {"interface": [{\n"name": "{{name}}",\n"config": {\n"name": "{{name}}"\n}""" + input_data = {"name": name} + if mode == "static": + payload_template += """,\n "openconfig-if-aggregation:aggregation": {\n"config": {\n"lag-type": "{{mode}}"\n}\n}\n""" + input_data["mode"] = mode.upper() + payload_template += """}\n]\n}\n}""" + env = jinja2.Environment(autoescape=False) + t = env.from_string(payload_template) + intended_payload = t.render(input_data) + ret_payload = json.loads(intended_payload) + return ret_payload - def call_create_portchannel(self, commands, have): - commands_list = [] + def create_port_channel(self, cmd): requests = [] + path = 'data/openconfig-interfaces:interfaces' + for i in cmd: + payload = self.build_create_payload_portchannel(i['name'], i.get('mode', None)) + request = {'path': path, 'method': PATCH, 'data': payload} + requests.append(request) + return requests - have_po_dict = {have_po.get('name'): have_po for have_po in have} - for po in commands: - name = po.get('name') - have_po = have_po_dict.get(name) - if not have_po: - commands_list.append(po) - continue - fallback = po.get('fallback') - fast_rate = po.get('fast_rate') - mode = po.get('mode') - have_fallback = have_po.get('fallback') - have_fast_rate = have_po.get('fast_rate') - have_mode = have_po.get('mode') - - if ((fallback is not None and fallback != have_fallback) or (fast_rate is not None and fast_rate != have_fast_rate) or - (mode and mode != have_mode)): - commands_list.append(po) - - if commands_list: - requests.append(self.create_portchannel(commands_list)) + def call_create_port_channel(self, commands, have): + commands_list = list() + for c in commands: + if not any(d['name'] == c['name'] for d in have): + commands_list.append(c) + requests = self.create_port_channel(commands_list) return commands_list, requests def get_delete_all_lag_interfaces_requests(self): requests = [] delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL_MEMBER/PORTCHANNEL_MEMBER_LIST' - delete_all_lag_request = {'path': delete_all_lag_url, 'method': DELETE} + method = DELETE + delete_all_lag_request = {"path": delete_all_lag_url, "method": method} requests.append(delete_all_lag_request) return requests def get_delete_all_portchannel_requests(self): requests = [] delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST' - delete_all_lag_request = {'path': delete_all_lag_url, 'method': DELETE} + method = DELETE + delete_all_lag_request = {"path": delete_all_lag_url, "method": method} requests.append(delete_all_lag_request) return requests @@ -583,6 +557,7 @@ def get_delete_lag_interfaces_requests(self, commands): requests = [] # Create URL and payload url = 'data/openconfig-interfaces:interfaces/interface={}/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id' + method = DELETE for c in commands: if c.get('members') and c['members'].get('interfaces'): interfaces = c['members']['interfaces'] @@ -590,8 +565,8 @@ def get_delete_lag_interfaces_requests(self, commands): continue for each in interfaces: - ifname = each['member'] - request = {'path': url.format(ifname), 'method': DELETE} + ifname = each["member"] + request = {"path": url.format(ifname), "method": method} requests.append(request) return requests @@ -624,7 +599,7 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet for cmd in delete_members: po_name = cmd['name'] - cmd_es = cmd.get('ethernet_segment') + cmd_es = cmd.get('ethernet_segment', None) if cmd_es: have_po = next((po for po in have if po['name'] == po_name), {}) have_es = have_po.get('ethernet_segment', {}) @@ -663,32 +638,15 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet return es_commands, es_requests - def get_delete_portchannel_request(self, name, attr): - url = f'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST={name}' - if attr: - url += f'/{attr}' - request = {'path': url, 'method': DELETE} - return request - def get_delete_portchannel_requests(self, commands): requests = [] - # Generate list of requests from commands + # Create URL and payload + url = 'data/openconfig-interfaces:interfaces/interface={}' + method = DELETE for c in commands: - name = c['name'] - fallback = c.get('fallback') - fast_rate = c.get('fast_rate') - mode = c.get('mode') - ethernet_segment = c.get('ethernet_segment') - members = c.get('members') - - if fallback is not None: - requests.append(self.get_delete_portchannel_request(name, 'fallback')) - if fast_rate is not None: - requests.append(self.get_delete_portchannel_request(name, 'fast_rate')) - if mode: - requests.append(self.get_delete_portchannel_request(name, 'static')) - if fallback is None and fast_rate is None and not mode and not ethernet_segment and not members: - requests.append(self.get_delete_portchannel_request(name, None)) + name = c["name"] + request = {"path": url.format(name), "method": method} + requests.append(request) return requests @@ -718,9 +676,9 @@ def validate_want(self, want, state): return for conf in want: - es = conf.get('ethernet_segment') + es = conf.get('ethernet_segment', None) if es: - esi = es.get('esi') + esi = es.get('esi', None) if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: if esi and esi != 'AUTO': self._module.fail_json(msg='value of esi must be "AUTO" for esi_type {0}'.format(es['esi_type'])) @@ -736,7 +694,7 @@ def preprocess_want(self, want, state): if not want: return for conf in want: - es = conf.get('ethernet_segment') + es = conf.get('ethernet_segment', None) if es: if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: if state != 'deleted': diff --git a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py index e25fd7242..cf97879e5 100644 --- a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2024 Dell Inc. or its subsidiaries. All Rights Reserved +# Copyright 2020 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) """ @@ -25,6 +25,8 @@ ) from ansible.module_utils.connection import ConnectionError +GET = "get" + class Lag_interfacesFacts(object): """ The sonic lag_interfaces fact class @@ -46,74 +48,41 @@ def __init__(self, module, subspec='config', options='options'): def get_all_portchannels(self): """Get all the interfaces available in chassis""" - data = [] - request = {'path': 'data/sonic-portchannel:sonic-portchannel', 'method': 'get'} + request = [{"path": "data/sonic-portchannel:sonic-portchannel", "method": GET}] try: response = edit_config(self._module, to_request(self._module, request)) - if response[0][1]: - data = response[0][1].get('sonic-portchannel:sonic-portchannel', []) except ConnectionError as exc: self._module.fail_json(msg=str(exc), code=exc.code) + if response[0][1]: + data = response[0][1]['sonic-portchannel:sonic-portchannel'] + else: + data = [] return data def get_po_and_po_members(self, data): - all_portchannels_list = [] - merged_portchannels = [] - + if data is not None: + if "PORTCHANNEL_MEMBER" in data: + portchannel_members_list = data["PORTCHANNEL_MEMBER"]["PORTCHANNEL_MEMBER_LIST"] + else: + portchannel_members_list = [] + if "PORTCHANNEL" in data: + portchannel_list = data["PORTCHANNEL"]["PORTCHANNEL_LIST"] + else: + portchannel_list = [] + if portchannel_list: + for i in portchannel_list: + if not any(d["name"] == i["name"] for d in portchannel_members_list): + portchannel_members_list.append({'ifname': None, 'name': i['name']}) if data: - if 'PORTCHANNEL' in data: - portchannel_list = data['PORTCHANNEL']['PORTCHANNEL_LIST'] - all_portchannels_list.extend(portchannel_list) - if 'PORTCHANNEL_MEMBER' in data: - portchannel_members_list = data['PORTCHANNEL_MEMBER']['PORTCHANNEL_MEMBER_LIST'] - all_portchannels_list.extend(portchannel_members_list) - - if all_portchannels_list: - mode_dict = {True: 'static', False: 'lacp'} - for portchannel in all_portchannels_list: - name = portchannel.get('name') - fallback = portchannel.get('fallback') - fast_rate = portchannel.get('fast_rate') - member = portchannel.get('ifname') - mode = portchannel.get('static') - - # Find if portchannel already exists and update - matched = next((portchannel for portchannel in merged_portchannels if portchannel['name'] == name), None) - if matched: - if fallback is not None: - matched['fallback'] = fallback - if fast_rate is not None: - matched['fast_rate'] = fast_rate - if member: - if 'members' in matched and matched['members'].get('interfaces'): - matched['members']['interfaces'].append({'member': member}) - else: - matched['members'] = {'interfaces': [{'member': member}]} - if mode is not None: - matched['mode'] = mode_dict[mode] - # Create new portchannel if it doesn't already exist - else: - new_portchannel = {} - if name: - new_portchannel['name'] = name - if fallback is not None: - new_portchannel['fallback'] = fallback - if fast_rate is not None: - new_portchannel['fast_rate'] = fast_rate - if member: - new_portchannel['members'] = {'interfaces': [{'member': member}]} - if mode is not None: - new_portchannel['mode'] = mode_dict[mode] - if new_portchannel: - merged_portchannels.append(new_portchannel) - - return merged_portchannels + return portchannel_members_list + else: + return [] def get_ethernet_segments(self, data): es_list = [] if data: - if 'EVPN_ETHERNET_SEGMENT' in data: + if "EVPN_ETHERNET_SEGMENT" in data: es_list = data["EVPN_ETHERNET_SEGMENT"]["EVPN_ETHERNET_SEGMENT_LIST"] return es_list @@ -128,35 +97,42 @@ def populate_facts(self, connection, ansible_facts, data=None): objs = [] if not data: data = self.get_all_portchannels() - objs = self.get_po_and_po_members(data) - - es_data = self.get_ethernet_segments(data) - for es in es_data: - po_name = es['ifname'] - esi_t = es['esi_type'] - esi = es['esi'] - if 'df_pref' in es: - df_pref = es['df_pref'] - else: - df_pref = None - if esi_t == 'TYPE_1_LACP_BASED': - esi_type = 'auto_lacp' - elif esi_t == 'TYPE_3_MAC_BASED': - esi_type = 'auto_system_mac' - elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': - esi_type = 'ethernet_segment_id' + po_data = self.get_po_and_po_members(data) + for conf in po_data: + if conf: + obj = self.render_config(self.generated_spec, conf) + obj = self.transform_config(obj) + if obj: + self.merge_portchannels(objs, obj) + + es_data = self.get_ethernet_segments(data) + for es in es_data: + po_name = es['ifname'] + esi_t = es['esi_type'] + esi = es['esi'] + if 'df_pref' in es: + df_pref = es['df_pref'] + else: + df_pref = None - if df_pref: - es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} - else: - es_dict = {'esi_type': esi_type, 'esi': esi} + if esi_t == 'TYPE_1_LACP_BASED': + esi_type = 'auto_lacp' + elif esi_t == 'TYPE_3_MAC_BASED': + esi_type = 'auto_system_mac' + elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': + esi_type = 'ethernet_segment_id' - have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) - if have_po_conf: - have_po_conf['ethernet_segment'] = es_dict - else: - self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) + if df_pref: + es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} + else: + es_dict = {'esi_type': esi_type, 'esi': esi} + + have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) + if have_po_conf: + have_po_conf['ethernet_segment'] = es_dict + else: + self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) facts = {} if objs: @@ -167,3 +143,34 @@ def populate_facts(self, connection, ansible_facts, data=None): ansible_facts['ansible_network_resources'].update(facts) return ansible_facts + + def render_config(self, spec, conf): + return conf + + def transform_config(self, conf): + trans_cfg = dict() + trans_cfg['name'] = conf['name'] + trans_cfg['members'] = dict() + if conf['ifname']: + interfaces = list() + interface = {'member': conf['ifname']} + interfaces.append(interface) + trans_cfg['members'] = {'interfaces': interfaces} + return trans_cfg + + def merge_portchannels(self, configs, conf): + if len(configs) == 0: + configs.append(conf) + else: + new_interface = None + if conf.get('members') and conf['members'].get('interfaces'): + new_interface = conf['members']['interfaces'][0] + else: + configs.append(conf) + if new_interface: + matched = next((cfg for cfg in configs if cfg['name'] == conf['name']), None) + if matched and matched.get('members'): + ext_interfaces = matched.get('members').get('interfaces', []) + ext_interfaces.append(new_interface) + else: + configs.append(conf) diff --git a/plugins/modules/sonic_lag_interfaces.py b/plugins/modules/sonic_lag_interfaces.py index ccc412bf9..c676fec81 100644 --- a/plugins/modules/sonic_lag_interfaces.py +++ b/plugins/modules/sonic_lag_interfaces.py @@ -103,16 +103,6 @@ - The preference for Designated Forwarder election method. The range of df_preference value is from 1 to 65535. type: int - fallback: - description: - - Enable/disable LACP fallback. - version_added: 3.1.0 - type: bool - fast_rate: - description: - - When set to true LACP packets will be sent every second; otherwise, the LACP packets will be sent every 30 seconds. - version_added: 3.1.0 - type: bool state: description: - The state that the configuration should be left in. @@ -149,9 +139,6 @@ esi_type: auto_lacp df_preference: 2222 - name: PortChannel12 - fallback: true - fast_rate: true - mode: lacp members: interfaces: - member: Eth1/15 @@ -178,9 +165,7 @@ # evpn ethernet-segment auto-lacp # df-preference 2222 # ! -# interface PortChannel12 mode active -# fast_rate -# fallback +# interface PortChannel12 # no shutdown # # @@ -201,9 +186,7 @@ # speed 100000 # no shutdown # ! -# interface PortChannel10 mode active -# fast_rate -# fallback +# interface PortChannel10 # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -220,8 +203,6 @@ esi_type: auto_system_mac df_preference: 6666 - name: PortChannel10 - fallback: false - fast_rate: false members: interfaces: - member: Eth1/7 @@ -250,7 +231,7 @@ # speed 100000 # no shutdown # -# interface PortChannel10 mode active +# interface PortChannel10 # no shutdown # ! # evpn ethernet-segment auto-system-mac @@ -289,9 +270,6 @@ dellemc.enterprise_sonic.sonic_lag_interfaces: config: - name: PortChannel20 - fallback: true - fast_rate: true - mode: lacp members: interfaces: - member: Eth1/6 @@ -314,9 +292,7 @@ # speed 100000 # no shutdown # -# interface PortChannel20 mode active -# fast_rate -# fallback +# interface PortChannel20 # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -326,9 +302,7 @@ # # Before state: # ------------- -# interface PortChannel 10 mode active -# fast_rate -# fallback +# interface PortChannel 10 # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -376,9 +350,7 @@ # speed 100000 # no shutdown # ! -# interface PortChannel10 mode active -# fast_rate -# fallback +# interface PortChannel10 # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -388,8 +360,6 @@ sonic_lag_interfaces: config: - name: PortChannel10 - fallback: true - fast_rate: true members: interfaces: - member: Eth1/10 diff --git a/tests/regression/hosts b/tests/regression/hosts index d49853a2e..eb3fd75ff 100644 --- a/tests/regression/hosts +++ b/tests/regression/hosts @@ -1,7 +1,10 @@ -sonic1 ansible_host=100.94.123.25 ansible_user=admin ansible_password=Sonic@Dell +sonic1 ansible_host=100.94.81.17 ansible_user=admin ansible_password=admin +sonic2 ansible_host=100.94.81.19 ansible_user=admin ansible_password=admin +#sonic2 ansible_user=admin ansible_password=admin [datacenter] sonic1 +sonic2 [datacenter:vars] ansible_network_os=dellemc.enterprise_sonic.sonic diff --git a/tests/regression/roles/common/defaults/main.yml b/tests/regression/roles/common/defaults/main.yml index 0b76e199b..be0c9eaa0 100644 --- a/tests/regression/roles/common/defaults/main.yml +++ b/tests/regression/roles/common/defaults/main.yml @@ -28,7 +28,7 @@ single_run_idem_condition: "{{ 'Passed' if (single_run_task_output.failed == fal else 'Failed' }}" -REPORT_DIR: "~" +REPORT_DIR: "/var/www/html/ansible/regression" empty: [] module_name1: debug @@ -37,7 +37,7 @@ std_name: STANDARD std_ext_name: STANDARD_EXT native_name: NATIVE -interface_mode: NATIVE +interface_mode: STANDARD default_interface_cli_std: default interface range Eth 1/5-1/10 default_interface_cli_native: default interface range Ethernet20-40 diff --git a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml index 177e7a8ba..ff0aa41b4 100644 --- a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml +++ b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml @@ -38,13 +38,14 @@ tests: members: interfaces: - member: "{{ interface3 }}" - fallback: true - fast_rate: true + - name: PortChannel43 + mode: static - name: test_case_03 description: Update interface parameters state: merged input: - name: PortChannel40 + mode: static members: interfaces: - member: "{{ interface1 }}" @@ -53,12 +54,11 @@ tests: esi_type: auto_lacp df_preference: 2222 - name: PortChannel41 + mode: lacp members: interfaces: - member: "{{ interface3 }}" - member: "{{ interface4 }}" - fallback: false - fast_rate: false - name: PortChannel42 - name: test_case_04 description: Delete interface parameters @@ -74,8 +74,6 @@ tests: - name: PortChannel41 members: interfaces: - fallback: false - fast_rate: false - name: PortChannel42 - name: test_case_05 description: Update interface parameters @@ -110,13 +108,6 @@ tests: esi_type: auto_lacp df_preference: 2233 - name: test_case_07 - description: Replace portchannel fallback and fast_rate - state: replaced - input: - - name: po41 - fallback: true - fast_rate: true - - name: test_case_08 description: Override portchannel configuration state: overridden input: @@ -131,7 +122,7 @@ tests: members: interfaces: - member: "{{ interface2 }}" - - name: test_case_09 + - name: test_case_08 description: Override all portchannel configuration state: overridden input: @@ -147,19 +138,17 @@ tests: interfaces: - member: "{{ interface5 }}" - name: po43 - fallback: true - fast_rate: true members: interfaces: - member: "{{ interface6 }}" - - name: test_case_10 + - name: test_case_09 description: Create standalone portchannels state: merged input: - name: portchannel42 - name: portchannel 12 - name: po10 - - name: test_case_11 + - name: test_case_10 description: Update interface parameters state: deleted input: [] diff --git a/tests/regression/test.yaml b/tests/regression/test.yaml index a94128bdf..87c657c77 100644 --- a/tests/regression/test.yaml +++ b/tests/regression/test.yaml @@ -8,5 +8,67 @@ collections: - dellemc.enterprise_sonic roles: + - sonic_api + - sonic_command + - sonic_config + - sonic_image_management + - sonic_system + - sonic_interfaces + - sonic_l2_interfaces - sonic_lag_interfaces + - sonic_mclag + - sonic_vlans + - sonic_l3_interfaces + - sonic_bgp_communities + - sonic_bgp_ext_communities + - sonic_bgp_as_paths + - sonic_bgp + - sonic_bgp_af + - sonic_bgp_neighbors + - sonic_bgp_neighbors_af + - sonic_ospfv2_interfaces + - sonic_ospfv2 + - sonic_dhcp_snooping + - sonic_vlan_mapping + - sonic_vrfs + - sonic_vrrp + - sonic_vxlan + - sonic_port_breakout + - sonic_users + - sonic_aaa + - sonic_ldap + - sonic_tacacs_server + - sonic_radius_server + - sonic_prefix_lists + - sonic_static_routes + - sonic_ntp + - sonic_logging + - sonic_ip_neighbor + - sonic_port_group + - sonic_dhcp_relay + - sonic_acl_interfaces + - sonic_l2_acls + - sonic_l3_acls + - sonic_lldp_global + - sonic_mac + - sonic_bfd + - sonic_copp + - sonic_route_maps + - sonic_lldp_interfaces + - sonic_stp + - sonic_sflow + - sonic_fips + - sonic_roce + - sonic_qos_buffer + - sonic_qos_pfc + - sonic_qos_maps + - sonic_qos_scheduler + - sonic_qos_wred + - sonic_qos_interfaces + - sonic_pim_global + - sonic_pim_interfaces + - sonic_login_lockout + - sonic_ospf_area + - sonic_poe + - sonic_mgmt_servers - test_reports diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml index 996c9e527..d8415baf9 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml @@ -16,9 +16,6 @@ merged_01: - member: Eth1/21 - member: Eth1/22 - name: PortChannel30 - fallback: true - fast_rate: true - mode: lacp existing_lag_interfaces_config: - path: "data/openconfig-interfaces:interfaces/interface" response: @@ -56,11 +53,6 @@ merged_01: - name: PortChannel30 config: name: PortChannel30 - openconfig-if-aggregate:aggregation: - config: - fallback: true - fast-rate: true - lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F11/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: @@ -134,9 +126,6 @@ deleted_02: interfaces: - member: Eth1/23 - name: PortChannel30 - fallback: true - fast_rate: true - mode: lacp members: interfaces: - member: Eth1/31 @@ -152,9 +141,6 @@ deleted_02: - name: PortChannel10 - name: PortChannel20 - name: PortChannel30 - fallback: true - fast_rate: true - static: false - name: PortChannel40 PORTCHANNEL_MEMBER: PORTCHANNEL_MEMBER_LIST: @@ -197,16 +183,10 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f23/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" data: - - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/fallback" - method: "delete" - - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/fast_rate" - method: "delete" - - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/static" - method: "delete" - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f31/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" data: - - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel40" + - path: "data/openconfig-interfaces:interfaces/interface=PortChannel40" method: "delete" data: - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" @@ -265,9 +245,6 @@ replaced_01: ethernet_segment: esi_type: auto_system_mac - name: PortChannel20 - fallback: true - fast_rate: true - mode: lacp members: interfaces: - member: Eth1/23 @@ -299,11 +276,6 @@ replaced_01: - name: PortChannel20 config: name: PortChannel20 - openconfig-if-aggregate:aggregation: - config: - fallback: true - fast-rate: true - lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: @@ -340,9 +312,6 @@ overridden_01: ethernet_segment: esi_type: auto_lacp - name: PortChannel20 - fallback: true - fast_rate: true - mode: lacp members: interfaces: - member: Eth1/23 @@ -373,7 +342,7 @@ overridden_01: - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" method: "delete" data: - - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30" + - path: "data/openconfig-interfaces:interfaces/interface=PortChannel30" method: "delete" data: - path: "data/openconfig-interfaces:interfaces" @@ -384,11 +353,6 @@ overridden_01: - name: PortChannel20 config: name: PortChannel20 - openconfig-if-aggregate:aggregation: - config: - fallback: true - fast-rate: true - lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: From 98e463abf91e25a11b9037b7ca375af30afa8bab Mon Sep 17 00:00:00 2001 From: Shade Talabi Date: Fri, 27 Sep 2024 15:30:50 -0700 Subject: [PATCH 4/7] Add fallback and fast_rate attributes to LAG interfaces module --- .../argspec/lag_interfaces/lag_interfaces.py | 6 +- .../config/lag_interfaces/lag_interfaces.py | 234 +++++++++++------- .../facts/lag_interfaces/lag_interfaces.py | 171 ++++++------- plugins/modules/sonic_lag_interfaces.py | 42 +++- .../sonic_lag_interfaces/defaults/main.yml | 25 +- .../sonic/fixtures/sonic_lag_interfaces.yaml | 40 ++- 6 files changed, 316 insertions(+), 202 deletions(-) diff --git a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py index db984758d..98d0f002a 100644 --- a/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved +# 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) @@ -67,7 +67,9 @@ def __init__(self, **kwargs): "df_preference": {"type": "int"}, }, "type": "dict" - } + }, + "fallback": {"type": "bool"}, + "fast_rate": {"type": "bool"} }, "type": "list" }, diff --git a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py index f36ac2dbc..49789c056 100644 --- a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved +# 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) """ @@ -65,6 +65,7 @@ PATCH = 'patch' DELETE = 'delete' TEST_KEYS = [ + {'config': {'name': ''}}, {'interfaces': {'member': ''}}, ] TEST_KEYS_formatted_diff = [ @@ -111,8 +112,8 @@ def execute_module(self): :returns: The result from module execution """ result = {'changed': False} - warnings = list() - commands = list() + warnings = [] + commands = [] existing_lag_interfaces_facts = self.get_lag_interfaces_facts() commands, requests = self.set_config(existing_lag_interfaces_facts) if commands: @@ -200,36 +201,36 @@ def _state_replaced(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = list() - commands = list() - delete_list = list() + requests = [] + commands = [] + delete_list = [] delete_list = get_diff(have, want, TEST_KEYS) delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list) - replaced_list = list() + replaced_list = [] for i in want: - list_obj = search_obj_in_list(i['name'], delete_members, "name") + list_obj = search_obj_in_list(i['name'], delete_members, 'name') if list_obj: replaced_list.append(list_obj) requests = self.get_delete_lag_interfaces_requests(replaced_list) if requests: - commands.extend(update_states(replaced_list, "deleted")) + commands.extend(update_states(replaced_list, 'deleted')) es_commands, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, want, have, True) if es_requests: - es_cmds = list() + es_cmds = [] for cmd in es_commands: po_name = cmd['name'] cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) if not cmd_in_cmds: es_cmds.append(cmd) if es_cmds: - commands.extend(update_states(es_cmds, "deleted")) + commands.extend(update_states(es_cmds, 'deleted')) requests.extend(es_requests) - replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "replaced") + replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, 'replaced') if replaced_requests: commands.extend(replaced_commands) requests.extend(replaced_requests) @@ -243,15 +244,15 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - requests = list() - commands = list() - delete_list = list() + requests = [] + commands = [] + delete_list = [] delete_list = get_diff(have, want, TEST_KEYS) delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list) - replaced_list = list() + replaced_list = [] for i in want: - list_obj = search_obj_in_list(i['name'], delete_members, "name") + list_obj = search_obj_in_list(i['name'], delete_members, 'name') if list_obj: replaced_list.append(list_obj) @@ -260,23 +261,25 @@ def _state_overridden(self, want, have, diff_members, diff_portchannels): nu, es_requests = self.get_delete_ethernet_segment_requests(replaced_list, want, have, True) requests.extend(es_requests) - commands.extend(update_states(replaced_list, "deleted")) + commands.extend(update_states(replaced_list, 'deleted')) - deleted_po_list = list() + deleted_po_list = [] + deleted_name_list = [] for i in delete_list: - list_obj = search_obj_in_list(i['name'], want, "name") + list_obj = search_obj_in_list(i['name'], want, 'name') if not list_obj: deleted_po_list.append(i) + deleted_name_list.append({'name': i['name']}) nu, es_requests = self.get_delete_po_ethernet_segment_requests(deleted_po_list, have) requests.extend(es_requests) - requests_deleted_po = self.get_delete_portchannel_requests(deleted_po_list) + requests_deleted_po = self.get_delete_portchannel_requests(deleted_name_list) requests.extend(requests_deleted_po) commands_del = self.prune_commands(deleted_po_list) - commands.extend(update_states(commands_del, "deleted")) + commands.extend(update_states(commands_del, 'deleted')) - override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "overridden") + override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, 'overridden') commands.extend(override_commands) requests.extend(override_requests) @@ -290,7 +293,8 @@ def _state_merged(self, want, have, diff_members, diff_portchannels): the current configuration """ commands, requests = self.template_for_lag_creation(have, diff_members, - diff_portchannels, "merged") + diff_portchannels, 'merged') + return commands, requests def _state_deleted(self, want, have, diff): @@ -300,23 +304,23 @@ def _state_deleted(self, want, have, diff): :returns: the commands necessary to remove the current configuration of the provided objects """ - commands = list() - requests = list() - portchannel_requests = list() + commands = [] + requests = [] + portchannel_requests = [] # if want is none, then delete all the lag interfaces and all portchannels if not want: requests = self.get_delete_all_lag_interfaces_requests() portchannel_requests = self.get_delete_all_portchannel_requests() requests.extend(portchannel_requests) commands_del = self.prune_commands(have) - commands.extend(update_states(commands_del, "deleted")) + commands.extend(update_states(commands_del, 'deleted')) else: # delete specific lag interfaces and specific portchannels po_commands = get_diff(want, diff, TEST_KEYS) po_commands = remove_empties_from_list(po_commands) want_members, want_portchannels = self.diff_list_for_member_creation(po_commands) del_commands, del_requests = self.template_for_lag_deletion(want, have, want_members, - want_portchannels, "deleted") + want_portchannels, 'deleted') if del_commands: commands.extend(del_commands) if del_requests: @@ -324,27 +328,21 @@ def _state_deleted(self, want, have, diff): return commands, requests def diff_list_for_member_creation(self, diff): - diff_members = list() - diff_portchannels = list() + diff_members = [] + diff_portchannels = [] for x in diff: - if "members" in x.keys() or "ethernet_segment" in x.keys(): + if 'members' in x.keys() or 'ethernet_segment' in x.keys(): diff_members.append(x) else: diff_portchannels.append(x) return diff_members, diff_portchannels def template_for_lag_creation(self, have, diff_members, diff_portchannels, state_name): - commands = list() - requests = list() + commands = [] + requests = [] if diff_members: - commands_portchannels, requests = self.call_create_port_channel(diff_members, have) - if commands_portchannels: - po_list = [{'name': x['name']} for x in commands_portchannels if x['name']] - else: - po_list = [] - if po_list: - commands.extend(update_states(po_list, state_name)) - diff_members_remove_none = [x for x in diff_members if x.get("members")] + commands_portchannels, requests = self.call_create_portchannel(diff_members, have) + diff_members_remove_none = [x for x in diff_members if x.get('members')] if diff_members_remove_none: request = self.create_lag_interfaces_requests(diff_members_remove_none) if request: @@ -358,21 +356,22 @@ def template_for_lag_creation(self, have, diff_members, diff_portchannels, state commands.extend(update_states(diff_members, state_name)) if diff_portchannels: - portchannels, po_requests = self.call_create_port_channel(diff_portchannels, have) + portchannels, po_requests = self.call_create_portchannel(diff_portchannels, have) requests.extend(po_requests) commands.extend(update_states(portchannels, state_name)) return commands, requests def template_for_lag_deletion(self, want, have, delete_members, delete_portchannels, state_name): - commands = list() - requests = list() - portchannel_requests = list() + commands = [] + requests = [] + portchannel_requests = [] if delete_members: - delete_members_remove_none = [x for x in delete_members if "members" in x.keys() and x["members"]] + del_po_requests = self.get_delete_portchannel_requests(delete_members) + delete_members_remove_none = [x for x in delete_members if 'members' in x.keys() and x['members']] requests = self.get_delete_lag_interfaces_requests(delete_members_remove_none) - delete_all_members = [x for x in delete_members if "members" in x.keys() and not x["members"]] - delete_all_list = list() + delete_all_members = [x for x in delete_members if 'members' in x.keys() and not x['members']] + delete_all_list = [] if delete_all_members: for i in delete_all_members: list_obj = search_obj_in_list(i['name'], have, "name") @@ -386,13 +385,15 @@ def template_for_lag_deletion(self, want, have, delete_members, delete_portchann requests.extend(deleteall_requests) elif deleteall_requests: requests = deleteall_requests + if del_po_requests: + requests.extend(del_po_requests) if requests: commands.extend(update_states(delete_members, state_name)) es_commands, es_requests = self.get_delete_ethernet_segment_requests(delete_members, want, have, False) if es_requests: - es_cmds = list() + es_cmds = [] for cmd in es_commands: po_name = cmd['name'] cmd_in_cmds = next((po for po in commands if po['name'] == po_name), {}) @@ -435,13 +436,13 @@ def create_lag_interfaces_requests(self, commands): def create_ethernet_segment_requests(self, diff_members, have): es_commands = [] es_path = 'data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments' - es_payload_list = list() + es_payload_list = [] for cmd in diff_members: po_name = cmd['name'] - cmd_es = cmd.get('ethernet_segment', None) + cmd_es = cmd.get('ethernet_segment') if cmd_es: - es = dict() + es = {} have_po = next((po for po in have if po['name'] == po_name), {}) have_es = have_po.get('ethernet_segment', {}) @@ -499,57 +500,82 @@ def create_ethernet_segment_requests(self, diff_members, have): def build_create_payload_member(self, name): payload_template = """{\n"openconfig-if-aggregate:aggregate-id": "{{name}}"\n}""" - input_data = {"name": name} + input_data = {'name': name} env = jinja2.Environment(autoescape=False) t = env.from_string(payload_template) intended_payload = t.render(input_data) ret_payload = json.loads(intended_payload) return ret_payload - def build_create_payload_portchannel(self, name, mode): - payload_template = """{\n"openconfig-interfaces:interfaces": {"interface": [{\n"name": "{{name}}",\n"config": {\n"name": "{{name}}"\n}""" - input_data = {"name": name} - if mode == "static": - payload_template += """,\n "openconfig-if-aggregation:aggregation": {\n"config": {\n"lag-type": "{{mode}}"\n}\n}\n""" - input_data["mode"] = mode.upper() - payload_template += """}\n]\n}\n}""" - env = jinja2.Environment(autoescape=False) - t = env.from_string(payload_template) - intended_payload = t.render(input_data) - ret_payload = json.loads(intended_payload) - return ret_payload - - def create_port_channel(self, cmd): - requests = [] + def create_portchannel(self, cmd): + request = None + portchannel_list = [] path = 'data/openconfig-interfaces:interfaces' - for i in cmd: - payload = self.build_create_payload_portchannel(i['name'], i.get('mode', None)) + + for portchannel in cmd: + portchannel_dict = {} + aggregation_cfg_dict = {} + name = portchannel.get('name') + fallback = portchannel.get('fallback') + fast_rate = portchannel.get('fast_rate') + mode = portchannel.get('mode') + + portchannel_dict.update({'name': name, 'config': {'name': name}}) + if fallback is not None: + aggregation_cfg_dict['fallback'] = fallback + if fast_rate is not None: + aggregation_cfg_dict['fast-rate'] = fast_rate + if mode: + aggregation_cfg_dict['lag-type'] = mode.upper() + if aggregation_cfg_dict: + portchannel_dict.update({'openconfig-if-aggregate:aggregation': {'config': aggregation_cfg_dict}}) + if portchannel_dict: + portchannel_list.append(portchannel_dict) + + if portchannel_list: + payload = {'openconfig-interfaces:interfaces': {'interface': portchannel_list}} request = {'path': path, 'method': PATCH, 'data': payload} - requests.append(request) - return requests - def call_create_port_channel(self, commands, have): - commands_list = list() - for c in commands: - if not any(d['name'] == c['name'] for d in have): - commands_list.append(c) - requests = self.create_port_channel(commands_list) + return request + + def call_create_portchannel(self, commands, have): + commands_list = [] + requests = [] + + have_po_dict = {have_po.get('name'): have_po for have_po in have} + for po in commands: + name = po.get('name') + have_po = have_po_dict.get(name) + if not have_po: + commands_list.append(po) + continue + fallback = po.get('fallback') + fast_rate = po.get('fast_rate') + mode = po.get('mode') + have_fallback = have_po.get('fallback') + have_fast_rate = have_po.get('fast_rate') + have_mode = have_po.get('mode') + + if ((fallback is not None and fallback != have_fallback) or (fast_rate is not None and fast_rate != have_fast_rate) or + (mode and mode != have_mode)): + commands_list.append(po) + + if commands_list: + requests.append(self.create_portchannel(commands_list)) return commands_list, requests def get_delete_all_lag_interfaces_requests(self): requests = [] delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL_MEMBER/PORTCHANNEL_MEMBER_LIST' - method = DELETE - delete_all_lag_request = {"path": delete_all_lag_url, "method": method} + delete_all_lag_request = {'path': delete_all_lag_url, 'method': DELETE} requests.append(delete_all_lag_request) return requests def get_delete_all_portchannel_requests(self): requests = [] delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST' - method = DELETE - delete_all_lag_request = {"path": delete_all_lag_url, "method": method} + delete_all_lag_request = {'path': delete_all_lag_url, 'method': DELETE} requests.append(delete_all_lag_request) return requests @@ -557,7 +583,6 @@ def get_delete_lag_interfaces_requests(self, commands): requests = [] # Create URL and payload url = 'data/openconfig-interfaces:interfaces/interface={}/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id' - method = DELETE for c in commands: if c.get('members') and c['members'].get('interfaces'): interfaces = c['members']['interfaces'] @@ -565,8 +590,8 @@ def get_delete_lag_interfaces_requests(self, commands): continue for each in interfaces: - ifname = each["member"] - request = {"path": url.format(ifname), "method": method} + ifname = each['member'] + request = {'path': url.format(ifname), 'method': DELETE} requests.append(request) return requests @@ -599,7 +624,7 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet for cmd in delete_members: po_name = cmd['name'] - cmd_es = cmd.get('ethernet_segment', None) + cmd_es = cmd.get('ethernet_segment') if cmd_es: have_po = next((po for po in have if po['name'] == po_name), {}) have_es = have_po.get('ethernet_segment', {}) @@ -638,15 +663,32 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet return es_commands, es_requests + def get_delete_portchannel_request(self, name, attr): + url = f'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST={name}' + if attr: + url += f'/{attr}' + request = {'path': url, 'method': DELETE} + return request + def get_delete_portchannel_requests(self, commands): requests = [] - # Create URL and payload - url = 'data/openconfig-interfaces:interfaces/interface={}' - method = DELETE + # Generate list of requests from commands for c in commands: - name = c["name"] - request = {"path": url.format(name), "method": method} - requests.append(request) + name = c['name'] + fallback = c.get('fallback') + fast_rate = c.get('fast_rate') + mode = c.get('mode') + ethernet_segment = c.get('ethernet_segment') + members = c.get('members') + + if fallback is not None: + requests.append(self.get_delete_portchannel_request(name, 'fallback')) + if fast_rate is not None: + requests.append(self.get_delete_portchannel_request(name, 'fast_rate')) + if mode: + requests.append(self.get_delete_portchannel_request(name, 'static')) + if fallback is None and fast_rate is None and not mode and not ethernet_segment and not members: + requests.append(self.get_delete_portchannel_request(name, None)) return requests @@ -676,9 +718,9 @@ def validate_want(self, want, state): return for conf in want: - es = conf.get('ethernet_segment', None) + es = conf.get('ethernet_segment') if es: - esi = es.get('esi', None) + esi = es.get('esi') if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: if esi and esi != 'AUTO': self._module.fail_json(msg='value of esi must be "AUTO" for esi_type {0}'.format(es['esi_type'])) @@ -694,7 +736,7 @@ def preprocess_want(self, want, state): if not want: return for conf in want: - es = conf.get('ethernet_segment', None) + es = conf.get('ethernet_segment') if es: if es['esi_type'] in ['auto_lacp', 'auto_system_mac']: if state != 'deleted': diff --git a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py index cf97879e5..e25fd7242 100644 --- a/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py @@ -1,6 +1,6 @@ # # -*- coding: utf-8 -*- -# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved +# 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) """ @@ -25,8 +25,6 @@ ) from ansible.module_utils.connection import ConnectionError -GET = "get" - class Lag_interfacesFacts(object): """ The sonic lag_interfaces fact class @@ -48,41 +46,74 @@ def __init__(self, module, subspec='config', options='options'): def get_all_portchannels(self): """Get all the interfaces available in chassis""" - request = [{"path": "data/sonic-portchannel:sonic-portchannel", "method": GET}] + data = [] + request = {'path': 'data/sonic-portchannel:sonic-portchannel', 'method': 'get'} try: response = edit_config(self._module, to_request(self._module, request)) + if response[0][1]: + data = response[0][1].get('sonic-portchannel:sonic-portchannel', []) except ConnectionError as exc: self._module.fail_json(msg=str(exc), code=exc.code) - if response[0][1]: - data = response[0][1]['sonic-portchannel:sonic-portchannel'] - else: - data = [] return data def get_po_and_po_members(self, data): - if data is not None: - if "PORTCHANNEL_MEMBER" in data: - portchannel_members_list = data["PORTCHANNEL_MEMBER"]["PORTCHANNEL_MEMBER_LIST"] - else: - portchannel_members_list = [] - if "PORTCHANNEL" in data: - portchannel_list = data["PORTCHANNEL"]["PORTCHANNEL_LIST"] - else: - portchannel_list = [] - if portchannel_list: - for i in portchannel_list: - if not any(d["name"] == i["name"] for d in portchannel_members_list): - portchannel_members_list.append({'ifname': None, 'name': i['name']}) + all_portchannels_list = [] + merged_portchannels = [] + if data: - return portchannel_members_list - else: - return [] + if 'PORTCHANNEL' in data: + portchannel_list = data['PORTCHANNEL']['PORTCHANNEL_LIST'] + all_portchannels_list.extend(portchannel_list) + if 'PORTCHANNEL_MEMBER' in data: + portchannel_members_list = data['PORTCHANNEL_MEMBER']['PORTCHANNEL_MEMBER_LIST'] + all_portchannels_list.extend(portchannel_members_list) + + if all_portchannels_list: + mode_dict = {True: 'static', False: 'lacp'} + for portchannel in all_portchannels_list: + name = portchannel.get('name') + fallback = portchannel.get('fallback') + fast_rate = portchannel.get('fast_rate') + member = portchannel.get('ifname') + mode = portchannel.get('static') + + # Find if portchannel already exists and update + matched = next((portchannel for portchannel in merged_portchannels if portchannel['name'] == name), None) + if matched: + if fallback is not None: + matched['fallback'] = fallback + if fast_rate is not None: + matched['fast_rate'] = fast_rate + if member: + if 'members' in matched and matched['members'].get('interfaces'): + matched['members']['interfaces'].append({'member': member}) + else: + matched['members'] = {'interfaces': [{'member': member}]} + if mode is not None: + matched['mode'] = mode_dict[mode] + # Create new portchannel if it doesn't already exist + else: + new_portchannel = {} + if name: + new_portchannel['name'] = name + if fallback is not None: + new_portchannel['fallback'] = fallback + if fast_rate is not None: + new_portchannel['fast_rate'] = fast_rate + if member: + new_portchannel['members'] = {'interfaces': [{'member': member}]} + if mode is not None: + new_portchannel['mode'] = mode_dict[mode] + if new_portchannel: + merged_portchannels.append(new_portchannel) + + return merged_portchannels def get_ethernet_segments(self, data): es_list = [] if data: - if "EVPN_ETHERNET_SEGMENT" in data: + if 'EVPN_ETHERNET_SEGMENT' in data: es_list = data["EVPN_ETHERNET_SEGMENT"]["EVPN_ETHERNET_SEGMENT_LIST"] return es_list @@ -97,42 +128,35 @@ def populate_facts(self, connection, ansible_facts, data=None): objs = [] if not data: data = self.get_all_portchannels() + objs = self.get_po_and_po_members(data) + + es_data = self.get_ethernet_segments(data) + for es in es_data: + po_name = es['ifname'] + esi_t = es['esi_type'] + esi = es['esi'] + if 'df_pref' in es: + df_pref = es['df_pref'] + else: + df_pref = None - po_data = self.get_po_and_po_members(data) - for conf in po_data: - if conf: - obj = self.render_config(self.generated_spec, conf) - obj = self.transform_config(obj) - if obj: - self.merge_portchannels(objs, obj) - - es_data = self.get_ethernet_segments(data) - for es in es_data: - po_name = es['ifname'] - esi_t = es['esi_type'] - esi = es['esi'] - if 'df_pref' in es: - df_pref = es['df_pref'] - else: - df_pref = None - - if esi_t == 'TYPE_1_LACP_BASED': - esi_type = 'auto_lacp' - elif esi_t == 'TYPE_3_MAC_BASED': - esi_type = 'auto_system_mac' - elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': - esi_type = 'ethernet_segment_id' + if esi_t == 'TYPE_1_LACP_BASED': + esi_type = 'auto_lacp' + elif esi_t == 'TYPE_3_MAC_BASED': + esi_type = 'auto_system_mac' + elif esi_t == 'TYPE_0_OPERATOR_CONFIGURED': + esi_type = 'ethernet_segment_id' - if df_pref: - es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} - else: - es_dict = {'esi_type': esi_type, 'esi': esi} + if df_pref: + es_dict = {'esi_type': esi_type, 'esi': esi, 'df_preference': df_pref} + else: + es_dict = {'esi_type': esi_type, 'esi': esi} - have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) - if have_po_conf: - have_po_conf['ethernet_segment'] = es_dict - else: - self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) + have_po_conf = next((po_conf for po_conf in objs if po_conf['name'] == po_name), {}) + if have_po_conf: + have_po_conf['ethernet_segment'] = es_dict + else: + self._module.fail_json(msg='{0} does not exist for ethernet segment'.format(po_name)) facts = {} if objs: @@ -143,34 +167,3 @@ def populate_facts(self, connection, ansible_facts, data=None): ansible_facts['ansible_network_resources'].update(facts) return ansible_facts - - def render_config(self, spec, conf): - return conf - - def transform_config(self, conf): - trans_cfg = dict() - trans_cfg['name'] = conf['name'] - trans_cfg['members'] = dict() - if conf['ifname']: - interfaces = list() - interface = {'member': conf['ifname']} - interfaces.append(interface) - trans_cfg['members'] = {'interfaces': interfaces} - return trans_cfg - - def merge_portchannels(self, configs, conf): - if len(configs) == 0: - configs.append(conf) - else: - new_interface = None - if conf.get('members') and conf['members'].get('interfaces'): - new_interface = conf['members']['interfaces'][0] - else: - configs.append(conf) - if new_interface: - matched = next((cfg for cfg in configs if cfg['name'] == conf['name']), None) - if matched and matched.get('members'): - ext_interfaces = matched.get('members').get('interfaces', []) - ext_interfaces.append(new_interface) - else: - configs.append(conf) diff --git a/plugins/modules/sonic_lag_interfaces.py b/plugins/modules/sonic_lag_interfaces.py index c676fec81..ccc412bf9 100644 --- a/plugins/modules/sonic_lag_interfaces.py +++ b/plugins/modules/sonic_lag_interfaces.py @@ -103,6 +103,16 @@ - The preference for Designated Forwarder election method. The range of df_preference value is from 1 to 65535. type: int + fallback: + description: + - Enable/disable LACP fallback. + version_added: 3.1.0 + type: bool + fast_rate: + description: + - When set to true LACP packets will be sent every second; otherwise, the LACP packets will be sent every 30 seconds. + version_added: 3.1.0 + type: bool state: description: - The state that the configuration should be left in. @@ -139,6 +149,9 @@ esi_type: auto_lacp df_preference: 2222 - name: PortChannel12 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/15 @@ -165,7 +178,9 @@ # evpn ethernet-segment auto-lacp # df-preference 2222 # ! -# interface PortChannel12 +# interface PortChannel12 mode active +# fast_rate +# fallback # no shutdown # # @@ -186,7 +201,9 @@ # speed 100000 # no shutdown # ! -# interface PortChannel10 +# interface PortChannel10 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -203,6 +220,8 @@ esi_type: auto_system_mac df_preference: 6666 - name: PortChannel10 + fallback: false + fast_rate: false members: interfaces: - member: Eth1/7 @@ -231,7 +250,7 @@ # speed 100000 # no shutdown # -# interface PortChannel10 +# interface PortChannel10 mode active # no shutdown # ! # evpn ethernet-segment auto-system-mac @@ -270,6 +289,9 @@ dellemc.enterprise_sonic.sonic_lag_interfaces: config: - name: PortChannel20 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/6 @@ -292,7 +314,9 @@ # speed 100000 # no shutdown # -# interface PortChannel20 +# interface PortChannel20 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -302,7 +326,9 @@ # # Before state: # ------------- -# interface PortChannel 10 +# interface PortChannel 10 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -350,7 +376,9 @@ # speed 100000 # no shutdown # ! -# interface PortChannel10 +# interface PortChannel10 mode active +# fast_rate +# fallback # no shutdown # ! # evpn ethernet-segment auto-lacp @@ -360,6 +388,8 @@ sonic_lag_interfaces: config: - name: PortChannel10 + fallback: true + fast_rate: true members: interfaces: - member: Eth1/10 diff --git a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml index ff0aa41b4..177e7a8ba 100644 --- a/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml +++ b/tests/regression/roles/sonic_lag_interfaces/defaults/main.yml @@ -38,14 +38,13 @@ tests: members: interfaces: - member: "{{ interface3 }}" - - name: PortChannel43 - mode: static + fallback: true + fast_rate: true - name: test_case_03 description: Update interface parameters state: merged input: - name: PortChannel40 - mode: static members: interfaces: - member: "{{ interface1 }}" @@ -54,11 +53,12 @@ tests: esi_type: auto_lacp df_preference: 2222 - name: PortChannel41 - mode: lacp members: interfaces: - member: "{{ interface3 }}" - member: "{{ interface4 }}" + fallback: false + fast_rate: false - name: PortChannel42 - name: test_case_04 description: Delete interface parameters @@ -74,6 +74,8 @@ tests: - name: PortChannel41 members: interfaces: + fallback: false + fast_rate: false - name: PortChannel42 - name: test_case_05 description: Update interface parameters @@ -108,6 +110,13 @@ tests: esi_type: auto_lacp df_preference: 2233 - name: test_case_07 + description: Replace portchannel fallback and fast_rate + state: replaced + input: + - name: po41 + fallback: true + fast_rate: true + - name: test_case_08 description: Override portchannel configuration state: overridden input: @@ -122,7 +131,7 @@ tests: members: interfaces: - member: "{{ interface2 }}" - - name: test_case_08 + - name: test_case_09 description: Override all portchannel configuration state: overridden input: @@ -138,17 +147,19 @@ tests: interfaces: - member: "{{ interface5 }}" - name: po43 + fallback: true + fast_rate: true members: interfaces: - member: "{{ interface6 }}" - - name: test_case_09 + - name: test_case_10 description: Create standalone portchannels state: merged input: - name: portchannel42 - name: portchannel 12 - name: po10 - - name: test_case_10 + - name: test_case_11 description: Update interface parameters state: deleted input: [] diff --git a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml index d8415baf9..996c9e527 100644 --- a/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml +++ b/tests/unit/modules/network/sonic/fixtures/sonic_lag_interfaces.yaml @@ -16,6 +16,9 @@ merged_01: - member: Eth1/21 - member: Eth1/22 - name: PortChannel30 + fallback: true + fast_rate: true + mode: lacp existing_lag_interfaces_config: - path: "data/openconfig-interfaces:interfaces/interface" response: @@ -53,6 +56,11 @@ merged_01: - name: PortChannel30 config: name: PortChannel30 + openconfig-if-aggregate:aggregation: + config: + fallback: true + fast-rate: true + lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F11/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: @@ -126,6 +134,9 @@ deleted_02: interfaces: - member: Eth1/23 - name: PortChannel30 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/31 @@ -141,6 +152,9 @@ deleted_02: - name: PortChannel10 - name: PortChannel20 - name: PortChannel30 + fallback: true + fast_rate: true + static: false - name: PortChannel40 PORTCHANNEL_MEMBER: PORTCHANNEL_MEMBER_LIST: @@ -183,10 +197,16 @@ deleted_02: - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f23/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" data: + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/fallback" + method: "delete" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/fast_rate" + method: "delete" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30/static" + method: "delete" - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2f31/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "delete" data: - - path: "data/openconfig-interfaces:interfaces/interface=PortChannel40" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel40" method: "delete" data: - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" @@ -245,6 +265,9 @@ replaced_01: ethernet_segment: esi_type: auto_system_mac - name: PortChannel20 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/23 @@ -276,6 +299,11 @@ replaced_01: - name: PortChannel20 config: name: PortChannel20 + openconfig-if-aggregate:aggregation: + config: + fallback: true + fast-rate: true + lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: @@ -312,6 +340,9 @@ overridden_01: ethernet_segment: esi_type: auto_lacp - name: PortChannel20 + fallback: true + fast_rate: true + mode: lacp members: interfaces: - member: Eth1/23 @@ -342,7 +373,7 @@ overridden_01: - path: "data/openconfig-network-instance:network-instances/network-instance=default/evpn/ethernet-segments/ethernet-segment=PortChannel10" method: "delete" data: - - path: "data/openconfig-interfaces:interfaces/interface=PortChannel30" + - path: "data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=PortChannel30" method: "delete" data: - path: "data/openconfig-interfaces:interfaces" @@ -353,6 +384,11 @@ overridden_01: - name: PortChannel20 config: name: PortChannel20 + openconfig-if-aggregate:aggregation: + config: + fallback: true + fast-rate: true + lag-type: LACP - path: "data/openconfig-interfaces:interfaces/interface=Eth1%2F12/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id" method: "patch" data: From 18a81ca5e8f44fe902ffaebaed8f3e5ccca000eb Mon Sep 17 00:00:00 2001 From: stalabi1 <54641848+stalabi1@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:36:21 -0700 Subject: [PATCH 5/7] Correct PR number --- ...tributes.yaml => 458-add-fallback-fast-rate-attributes.yaml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename changelogs/fragments/{453-add-fallback-fast-rate-attributes.yaml => 458-add-fallback-fast-rate-attributes.yaml} (79%) diff --git a/changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml b/changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml similarity index 79% rename from changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml rename to changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml index f7e309a66..246fe3415 100644 --- a/changelogs/fragments/453-add-fallback-fast-rate-attributes.yaml +++ b/changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml @@ -1,4 +1,4 @@ --- minor_changes: - - sonic_lag_interfaces - Add fallback and fast_rate attributes to LAG interfaces module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/453). + - sonic_lag_interfaces - Add fallback and fast_rate attributes to LAG interfaces module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/458). From daba0d6360634965250f3d5ce5d2fcd5ee2920da Mon Sep 17 00:00:00 2001 From: stalabi1 <54641848+stalabi1@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:40:17 -0700 Subject: [PATCH 6/7] Remove blank lines --- changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml b/changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml index 246fe3415..3a3254931 100644 --- a/changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml +++ b/changelogs/fragments/458-add-fallback-fast-rate-attributes.yaml @@ -1,4 +1,3 @@ --- minor_changes: - sonic_lag_interfaces - Add fallback and fast_rate attributes to LAG interfaces module (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/458). - From 8f2cf60804f8f0fc6c3e7fa8716afed35a7bf4f7 Mon Sep 17 00:00:00 2001 From: Shade Talabi Date: Tue, 15 Oct 2024 13:03:02 -0700 Subject: [PATCH 7/7] Remove f-string --- .../network/sonic/config/lag_interfaces/lag_interfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py index 49789c056..43d7dac19 100644 --- a/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py @@ -664,9 +664,9 @@ def get_delete_ethernet_segment_requests(self, delete_members, want, have, delet return es_commands, es_requests def get_delete_portchannel_request(self, name, attr): - url = f'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST={name}' + url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST=%s' % (name) if attr: - url += f'/{attr}' + url += '/%s' % (attr) request = {'path': url, 'method': DELETE} return request