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_l2_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__metaclass__ = type 15 16from ansible.module_utils.network.common.cfg.base import ConfigBase 17from ansible.module_utils.network.common.utils import to_list, param_list_to_dict 18from ansible.module_utils.network.eos.facts.facts import Facts 19from ansible.module_utils.network.eos.utils.utils import normalize_interface 20 21 22class L2_interfaces(ConfigBase): 23 """ 24 The eos_l2_interfaces class 25 """ 26 27 gather_subset = [ 28 '!all', 29 '!min', 30 ] 31 32 gather_network_resources = [ 33 'l2_interfaces', 34 ] 35 36 def get_l2_interfaces_facts(self): 37 """ Get the 'facts' (the current configuration) 38 39 :rtype: A dictionary 40 :returns: The current configuration as a dictionary 41 """ 42 facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) 43 l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces') 44 if not l2_interfaces_facts: 45 return [] 46 return l2_interfaces_facts 47 48 def execute_module(self): 49 """ Execute the module 50 51 :rtype: A dictionary 52 :returns: The result from module execution 53 """ 54 result = {'changed': False} 55 commands = list() 56 warnings = list() 57 58 existing_l2_interfaces_facts = self.get_l2_interfaces_facts() 59 commands.extend(self.set_config(existing_l2_interfaces_facts)) 60 if commands: 61 if not self._module.check_mode: 62 self._connection.edit_config(commands) 63 result['changed'] = True 64 result['commands'] = commands 65 66 changed_l2_interfaces_facts = self.get_l2_interfaces_facts() 67 68 result['before'] = existing_l2_interfaces_facts 69 if result['changed']: 70 result['after'] = changed_l2_interfaces_facts 71 72 result['warnings'] = warnings 73 return result 74 75 def set_config(self, existing_l2_interfaces_facts): 76 """ Collect the configuration from the args passed to the module, 77 collect the current configuration (as a dict from facts) 78 79 :rtype: A list 80 :returns: the commands necessary to migrate the current configuration 81 to the desired configuration 82 """ 83 want = self._module.params['config'] 84 have = existing_l2_interfaces_facts 85 resp = self.set_state(want, have) 86 return to_list(resp) 87 88 def set_state(self, want, have): 89 """ Select the appropriate function based on the state provided 90 91 :param want: the desired configuration as a dictionary 92 :param have: the current configuration as a dictionary 93 :rtype: A list 94 :returns: the commands necessary to migrate the current configuration 95 to the desired configuration 96 """ 97 state = self._module.params['state'] 98 want = param_list_to_dict(want) 99 have = param_list_to_dict(have) 100 if state == 'overridden': 101 commands = self._state_overridden(want, have) 102 elif state == 'deleted': 103 commands = self._state_deleted(want, have) 104 elif state == 'merged': 105 commands = self._state_merged(want, have) 106 elif state == 'replaced': 107 commands = self._state_replaced(want, have) 108 return commands 109 110 @staticmethod 111 def _state_replaced(want, have): 112 """ The command generator when state is replaced 113 114 :rtype: A list 115 :returns: the commands necessary to migrate the current configuration 116 to the desired configuration 117 """ 118 commands = [] 119 for key, desired in want.items(): 120 interface_name = normalize_interface(key) 121 if interface_name in have: 122 extant = have[interface_name] 123 else: 124 extant = dict() 125 126 intf_commands = set_interface(desired, extant) 127 intf_commands.extend(clear_interface(desired, extant)) 128 129 if intf_commands: 130 commands.append("interface {0}".format(interface_name)) 131 commands.extend(intf_commands) 132 133 return commands 134 135 @staticmethod 136 def _state_overridden(want, have): 137 """ The command generator when state is overridden 138 139 :rtype: A list 140 :returns: the commands necessary to migrate the current configuration 141 to the desired configuration 142 """ 143 commands = [] 144 for key, extant in have.items(): 145 if key in want: 146 desired = want[key] 147 else: 148 desired = dict() 149 150 intf_commands = set_interface(desired, extant) 151 intf_commands.extend(clear_interface(desired, extant)) 152 153 if intf_commands: 154 commands.append("interface {0}".format(key)) 155 commands.extend(intf_commands) 156 157 return commands 158 159 @staticmethod 160 def _state_merged(want, have): 161 """ The command generator when state is merged 162 163 :rtype: A list 164 :returns: the commands necessary to merge the provided into 165 the current configuration 166 """ 167 commands = [] 168 for key, desired in want.items(): 169 interface_name = normalize_interface(key) 170 if interface_name in have: 171 extant = have[interface_name] 172 else: 173 extant = dict() 174 175 intf_commands = set_interface(desired, extant) 176 177 if intf_commands: 178 commands.append("interface {0}".format(interface_name)) 179 commands.extend(intf_commands) 180 181 return commands 182 183 @staticmethod 184 def _state_deleted(want, have): 185 """ The command generator when state is deleted 186 187 :rtype: A list 188 :returns: the commands necessary to remove the current configuration 189 of the provided objects 190 """ 191 commands = [] 192 for key in want: 193 desired = dict() 194 if key in have: 195 extant = have[key] 196 else: 197 continue 198 199 intf_commands = clear_interface(desired, extant) 200 201 if intf_commands: 202 commands.append("interface {0}".format(key)) 203 commands.extend(intf_commands) 204 205 return commands 206 207 208def set_interface(want, have): 209 commands = [] 210 wants_access = want.get("access") 211 if wants_access: 212 access_vlan = wants_access.get("vlan") 213 if access_vlan and access_vlan != have.get("access", {}).get("vlan"): 214 commands.append("switchport access vlan {0}".format(access_vlan)) 215 216 wants_trunk = want.get("trunk") 217 if wants_trunk: 218 has_trunk = have.get("trunk", {}) 219 native_vlan = wants_trunk.get("native_vlan") 220 if native_vlan and native_vlan != has_trunk.get("native_vlan"): 221 commands.append("switchport trunk native vlan {0}".format(native_vlan)) 222 223 allowed_vlans = want['trunk'].get("trunk_allowed_vlans") 224 if allowed_vlans: 225 allowed_vlans = ','.join(allowed_vlans) 226 commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans)) 227 return commands 228 229 230def clear_interface(want, have): 231 commands = [] 232 if "access" in have and not want.get('access'): 233 commands.append("no switchport access vlan") 234 235 has_trunk = have.get("trunk") or {} 236 wants_trunk = want.get("trunk") or {} 237 if "trunk_allowed_vlans" in has_trunk and "trunk_allowed_vlans" not in wants_trunk: 238 commands.append("no switchport trunk allowed vlan") 239 if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk: 240 commands.append("no switchport trunk native vlan") 241 return commands 242