1# -*- coding: utf-8 -*- 2# Copyright 2019 Red Hat 3# GNU General Public License v3.0+ 4# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5""" 6The eos_lag_interfaces class 7It is in this file where the current configuration (as dict) 8is compared to the provided configuration (as dict) and the command set 9necessary to bring the current configuration to it's desired end-state is 10created 11""" 12 13from __future__ import absolute_import, division, print_function 14 15import re 16 17__metaclass__ = type 18 19from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( 20 to_list, 21 dict_diff, 22) 23 24from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( 25 ConfigBase, 26) 27from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( 28 Facts, 29) 30from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( 31 normalize_interface, 32) 33 34 35class Lag_interfaces(ConfigBase): 36 """ 37 The eos_lag_interfaces class 38 """ 39 40 gather_subset = ["!all", "!min"] 41 42 gather_network_resources = ["lag_interfaces"] 43 44 def get_lag_interfaces_facts(self, data=None): 45 """Get the 'facts' (the current configuration) 46 47 :rtype: A dictionary 48 :returns: The current configuration as a dictionary 49 """ 50 facts, _warnings = Facts(self._module).get_facts( 51 self.gather_subset, self.gather_network_resources, data=data 52 ) 53 lag_interfaces_facts = facts["ansible_network_resources"].get( 54 "lag_interfaces" 55 ) 56 if not lag_interfaces_facts: 57 return [] 58 return lag_interfaces_facts 59 60 def execute_module(self): 61 """Execute the module 62 63 :rtype: A dictionary 64 :returns: The result from module execution 65 """ 66 result = {"changed": False} 67 commands = list() 68 warnings = list() 69 70 if self.state in self.ACTION_STATES: 71 existing_lag_interfaces_facts = self.get_lag_interfaces_facts() 72 else: 73 existing_lag_interfaces_facts = {} 74 75 if self.state in self.ACTION_STATES or self.state == "rendered": 76 commands.extend(self.set_config(existing_lag_interfaces_facts)) 77 78 if commands and self.state in self.ACTION_STATES: 79 if not self._module.check_mode: 80 self._connection.edit_config(commands) 81 result["changed"] = True 82 if self.state in self.ACTION_STATES: 83 result["commands"] = commands 84 85 if self.state in self.ACTION_STATES or self.state == "gathered": 86 changed_lag_interfaces_facts = self.get_lag_interfaces_facts() 87 elif self.state == "rendered": 88 result["rendered"] = commands 89 elif self.state == "parsed": 90 running_config = self._module.params["running_config"] 91 if not running_config: 92 self._module.fail_json( 93 msg="value of running_config parameter must not be empty for state parsed" 94 ) 95 result["parsed"] = self.get_lag_interfaces_facts( 96 data=running_config 97 ) 98 99 if self.state in self.ACTION_STATES: 100 result["before"] = existing_lag_interfaces_facts 101 if result["changed"]: 102 result["after"] = changed_lag_interfaces_facts 103 elif self.state == "gathered": 104 result["gathered"] = changed_lag_interfaces_facts 105 106 result["warnings"] = warnings 107 return result 108 109 def set_config(self, existing_lag_interfaces_facts): 110 """Collect the configuration from the args passed to the module, 111 collect the current configuration (as a dict from facts) 112 113 :rtype: A list 114 :returns: the commands necessary to migrate the current configuration 115 to the desired configuration 116 """ 117 want = self._module.params["config"] 118 have = existing_lag_interfaces_facts 119 resp = self.set_state(want, have) 120 return to_list(resp) 121 122 def set_state(self, want, have): 123 """Select the appropriate function based on the state provided 124 125 :param want: the desired configuration as a dictionary 126 :param have: the current configuration as a dictionary 127 :rtype: A list 128 :returns: the commands necessary to migrate the current configuration 129 to the desired configuration 130 """ 131 state = self._module.params["state"] 132 if ( 133 state in ("merged", "replaced", "overridden", "rendered") 134 and not want 135 ): 136 self._module.fail_json( 137 msg="value of config parameter must not be empty for state {0}".format( 138 state 139 ) 140 ) 141 if state == "overridden": 142 commands = self._state_overridden(want, have) 143 elif state == "deleted": 144 commands = self._state_deleted(want, have) 145 elif state == "merged" or state == "rendered": 146 commands = self._state_merged(want, have) 147 elif state == "replaced": 148 commands = self._state_replaced(want, have) 149 return commands 150 151 @staticmethod 152 def _state_replaced(want, have): 153 """The command generator when state is replaced 154 :rtype: A list 155 :returns: the commands necessary to migrate the current configuration 156 to the desired configuration 157 """ 158 commands = [] 159 for interface in want: 160 interface_name = normalize_interface(interface["name"]) 161 for extant in have: 162 if extant["name"] == interface_name: 163 break 164 else: 165 extant = dict(name=interface_name) 166 167 commands.extend(set_config(interface, extant)) 168 commands.extend(remove_config(interface, extant)) 169 170 return commands 171 172 @staticmethod 173 def _state_overridden(want, have): 174 """The command generator when state is overridden 175 :rtype: A list 176 :returns: the commands necessary to migrate the current configuration 177 to the desired configuration 178 """ 179 commands = [] 180 for extant in have: 181 for interface in want: 182 if normalize_interface(interface["name"]) == extant["name"]: 183 break 184 else: 185 interface = dict(name=extant["name"]) 186 commands.extend(remove_config(interface, extant)) 187 188 for interface in want: 189 interface_name = normalize_interface(interface["name"]) 190 for extant in have: 191 if extant["name"] == interface_name: 192 break 193 else: 194 extant = dict(name=interface_name) 195 commands.extend(set_config(interface, extant)) 196 197 return commands 198 199 @staticmethod 200 def _state_merged(want, have): 201 """The command generator when state is merged 202 :rtype: A list 203 :returns: the commands necessary to merge the provided into 204 the current configuration 205 """ 206 commands = [] 207 for interface in want: 208 interface_name = normalize_interface(interface["name"]) 209 for extant in have: 210 if extant["name"] == interface_name: 211 break 212 else: 213 extant = dict(name=interface_name) 214 215 commands.extend(set_config(interface, extant)) 216 217 return commands 218 219 @staticmethod 220 def _state_deleted(want, have): 221 """The command generator when state is deleted 222 :rtype: A list 223 :returns: the commands necessary to remove the current configuration 224 of the provided objects 225 """ 226 commands = [] 227 for interface in want: 228 interface_name = normalize_interface(interface["name"]) 229 for extant in have: 230 if extant["name"] == interface_name: 231 break 232 else: 233 extant = dict(name=interface_name) 234 235 # Clearing all args, send empty dictionary 236 interface = dict(name=interface_name) 237 commands.extend(remove_config(interface, extant)) 238 239 return commands 240 241 242def set_config(want, have): 243 commands = [] 244 to_set = dict_diff(have, want) 245 for member in to_set.get("members", []): 246 channel_id = re.search(r"\d.*", want["name"]) 247 if channel_id: 248 commands.extend( 249 [ 250 "interface {0}".format(member["member"]), 251 "channel-group {0} mode {1}".format( 252 channel_id.group(0), member["mode"] 253 ), 254 ] 255 ) 256 257 return commands 258 259 260def remove_config(want, have): 261 commands = [] 262 if not want.get("members"): 263 return ["no interface {0}".format(want["name"])] 264 265 to_remove = dict_diff(want, have) 266 for member in to_remove.get("members", []): 267 commands.extend( 268 ["interface {0}".format(member["member"]), "no channel-group"] 269 ) 270 271 return commands 272