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