1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# (c) 2017, Ansible by Red Hat, inc 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['deprecated'], 13 'supported_by': 'network'} 14 15 16DOCUMENTATION = """ 17--- 18module: ios_interface 19version_added: "2.4" 20author: "Ganesh Nalawade (@ganeshrn)" 21short_description: Manage Interface on Cisco IOS network devices 22description: 23 - This module provides declarative management of Interfaces 24 on Cisco IOS network devices. 25deprecated: 26 removed_in: '2.13' 27 alternative: ios_interfaces 28 why: Newer and updated modules released with more functionality in Ansible 2.9 29notes: 30 - Tested against IOS 15.6 31options: 32 name: 33 description: 34 - Name of the Interface. 35 required: true 36 description: 37 description: 38 - Description of Interface. 39 enabled: 40 description: 41 - Interface link status. 42 type: bool 43 speed: 44 description: 45 - Interface link speed. 46 mtu: 47 description: 48 - Maximum size of transmit packet. 49 duplex: 50 description: 51 - Interface link status 52 default: auto 53 choices: ['full', 'half', 'auto'] 54 tx_rate: 55 description: 56 - Transmit rate in bits per second (bps). 57 - This is state check parameter only. 58 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 59 rx_rate: 60 description: 61 - Receiver rate in bits per second (bps). 62 - This is state check parameter only. 63 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 64 neighbors: 65 description: 66 - Check the operational state of given interface C(name) for CDP/LLDP neighbor. 67 - The following suboptions are available. 68 suboptions: 69 host: 70 description: 71 - "CDP/LLDP neighbor host for given interface C(name)." 72 port: 73 description: 74 - "CDP/LLDP neighbor port to which given interface C(name) is connected." 75 aggregate: 76 description: List of Interfaces definitions. 77 delay: 78 description: 79 - Time in seconds to wait before checking for the operational state on remote 80 device. This wait is applicable for operational state argument which are 81 I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). 82 default: 10 83 state: 84 description: 85 - State of the Interface configuration, C(up) means present and 86 operationally up and C(down) means present and operationally C(down) 87 default: present 88 choices: ['present', 'absent', 'up', 'down'] 89extends_documentation_fragment: ios 90""" 91 92EXAMPLES = """ 93- name: configure interface 94 ios_interface: 95 name: GigabitEthernet0/2 96 description: test-interface 97 speed: 100 98 duplex: half 99 mtu: 512 100 101- name: remove interface 102 ios_interface: 103 name: Loopback9 104 state: absent 105 106- name: make interface up 107 ios_interface: 108 name: GigabitEthernet0/2 109 enabled: True 110 111- name: make interface down 112 ios_interface: 113 name: GigabitEthernet0/2 114 enabled: False 115 116- name: Check intent arguments 117 ios_interface: 118 name: GigabitEthernet0/2 119 state: up 120 tx_rate: ge(0) 121 rx_rate: le(0) 122 123- name: Check neighbors intent arguments 124 ios_interface: 125 name: Gi0/0 126 neighbors: 127 - port: eth0 128 host: netdev 129 130- name: Config + intent 131 ios_interface: 132 name: GigabitEthernet0/2 133 enabled: False 134 state: down 135 136- name: Add interface using aggregate 137 ios_interface: 138 aggregate: 139 - { name: GigabitEthernet0/1, mtu: 256, description: test-interface-1 } 140 - { name: GigabitEthernet0/2, mtu: 516, description: test-interface-2 } 141 duplex: full 142 speed: 100 143 state: present 144 145- name: Delete interface using aggregate 146 ios_interface: 147 aggregate: 148 - name: Loopback9 149 - name: Loopback10 150 state: absent 151""" 152 153RETURN = """ 154commands: 155 description: The list of configuration mode commands to send to the device. 156 returned: always, except for the platforms that use Netconf transport to manage the device. 157 type: list 158 sample: 159 - interface GigabitEthernet0/2 160 - description test-interface 161 - duplex half 162 - mtu 512 163""" 164import re 165 166from copy import deepcopy 167from time import sleep 168 169from ansible.module_utils._text import to_text 170from ansible.module_utils.basic import AnsibleModule 171from ansible.module_utils.connection import exec_command 172from ansible.module_utils.network.ios.ios import get_config, load_config 173from ansible.module_utils.network.ios.ios import ios_argument_spec, check_args 174from ansible.module_utils.network.common.config import NetworkConfig 175from ansible.module_utils.network.common.utils import conditional, remove_default_spec 176 177 178def validate_mtu(value, module): 179 if value and not 64 <= int(value) <= 9600: 180 module.fail_json(msg='mtu must be between 64 and 9600') 181 182 183def validate_param_values(module, obj, param=None): 184 if param is None: 185 param = module.params 186 for key in obj: 187 # validate the param value (if validator func exists) 188 validator = globals().get('validate_%s' % key) 189 if callable(validator): 190 validator(param.get(key), module) 191 192 193def parse_shutdown(configobj, name): 194 cfg = configobj['interface %s' % name] 195 cfg = '\n'.join(cfg.children) 196 match = re.search(r'^shutdown', cfg, re.M) 197 if match: 198 return True 199 else: 200 return False 201 202 203def parse_config_argument(configobj, name, arg=None): 204 cfg = configobj['interface %s' % name] 205 cfg = '\n'.join(cfg.children) 206 match = re.search(r'%s (.+)$' % arg, cfg, re.M) 207 if match: 208 return match.group(1) 209 210 211def search_obj_in_list(name, lst): 212 for o in lst: 213 if o['name'] == name: 214 return o 215 216 return None 217 218 219def add_command_to_interface(interface, cmd, commands): 220 if interface not in commands: 221 commands.append(interface) 222 commands.append(cmd) 223 224 225def map_config_to_obj(module): 226 config = get_config(module) 227 configobj = NetworkConfig(indent=1, contents=config) 228 229 match = re.findall(r'^interface (\S+)', config, re.M) 230 if not match: 231 return list() 232 233 instances = list() 234 235 for item in set(match): 236 obj = { 237 'name': item, 238 'description': parse_config_argument(configobj, item, 'description'), 239 'speed': parse_config_argument(configobj, item, 'speed'), 240 'duplex': parse_config_argument(configobj, item, 'duplex'), 241 'mtu': parse_config_argument(configobj, item, 'mtu'), 242 'disable': True if parse_shutdown(configobj, item) else False, 243 'state': 'present' 244 } 245 instances.append(obj) 246 return instances 247 248 249def map_params_to_obj(module): 250 obj = [] 251 aggregate = module.params.get('aggregate') 252 if aggregate: 253 for item in aggregate: 254 for key in item: 255 if item.get(key) is None: 256 item[key] = module.params[key] 257 258 validate_param_values(module, item, item) 259 d = item.copy() 260 261 if d['enabled']: 262 d['disable'] = False 263 else: 264 d['disable'] = True 265 266 obj.append(d) 267 268 else: 269 params = { 270 'name': module.params['name'], 271 'description': module.params['description'], 272 'speed': module.params['speed'], 273 'mtu': module.params['mtu'], 274 'duplex': module.params['duplex'], 275 'state': module.params['state'], 276 'delay': module.params['delay'], 277 'tx_rate': module.params['tx_rate'], 278 'rx_rate': module.params['rx_rate'], 279 'neighbors': module.params['neighbors'] 280 } 281 282 validate_param_values(module, params) 283 if module.params['enabled']: 284 params.update({'disable': False}) 285 else: 286 params.update({'disable': True}) 287 288 obj.append(params) 289 return obj 290 291 292def map_obj_to_commands(updates): 293 commands = list() 294 want, have = updates 295 296 args = ('speed', 'description', 'duplex', 'mtu') 297 for w in want: 298 name = w['name'] 299 disable = w['disable'] 300 state = w['state'] 301 302 obj_in_have = search_obj_in_list(name, have) 303 interface = 'interface ' + name 304 305 if state == 'absent' and obj_in_have: 306 commands.append('no ' + interface) 307 308 elif state in ('present', 'up', 'down'): 309 if obj_in_have: 310 for item in args: 311 candidate = w.get(item) 312 running = obj_in_have.get(item) 313 if candidate != running: 314 if candidate: 315 cmd = item + ' ' + str(candidate) 316 add_command_to_interface(interface, cmd, commands) 317 318 if disable and not obj_in_have.get('disable', False): 319 add_command_to_interface(interface, 'shutdown', commands) 320 elif not disable and obj_in_have.get('disable', False): 321 add_command_to_interface(interface, 'no shutdown', commands) 322 else: 323 commands.append(interface) 324 for item in args: 325 value = w.get(item) 326 if value: 327 commands.append(item + ' ' + str(value)) 328 329 if disable: 330 commands.append('no shutdown') 331 return commands 332 333 334def check_declarative_intent_params(module, want, result): 335 failed_conditions = [] 336 have_neighbors_lldp = None 337 have_neighbors_cdp = None 338 for w in want: 339 want_state = w.get('state') 340 want_tx_rate = w.get('tx_rate') 341 want_rx_rate = w.get('rx_rate') 342 want_neighbors = w.get('neighbors') 343 344 if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: 345 continue 346 347 if result['changed']: 348 sleep(w['delay']) 349 350 command = 'show interfaces %s' % w['name'] 351 rc, out, err = exec_command(module, command) 352 if rc != 0: 353 module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) 354 355 if want_state in ('up', 'down'): 356 match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) 357 have_state = None 358 if match: 359 have_state = match.group(1) 360 if have_state is None or not conditional(want_state, have_state.strip()): 361 failed_conditions.append('state ' + 'eq(%s)' % want_state) 362 363 if want_tx_rate: 364 match = re.search(r'%s (\d+)' % 'output rate', out, re.M) 365 have_tx_rate = None 366 if match: 367 have_tx_rate = match.group(1) 368 369 if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): 370 failed_conditions.append('tx_rate ' + want_tx_rate) 371 372 if want_rx_rate: 373 match = re.search(r'%s (\d+)' % 'input rate', out, re.M) 374 have_rx_rate = None 375 if match: 376 have_rx_rate = match.group(1) 377 378 if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): 379 failed_conditions.append('rx_rate ' + want_rx_rate) 380 381 if want_neighbors: 382 have_host = [] 383 have_port = [] 384 385 # Process LLDP neighbors 386 if have_neighbors_lldp is None: 387 rc, have_neighbors_lldp, err = exec_command(module, 'show lldp neighbors detail') 388 if rc != 0: 389 module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) 390 391 if have_neighbors_lldp: 392 lines = have_neighbors_lldp.strip().split('Local Intf: ') 393 for line in lines: 394 field = line.split('\n') 395 if field[0].strip() == w['name']: 396 for item in field: 397 if item.startswith('System Name:'): 398 have_host.append(item.split(':')[1].strip()) 399 if item.startswith('Port Description:'): 400 have_port.append(item.split(':')[1].strip()) 401 402 # Process CDP neighbors 403 if have_neighbors_cdp is None: 404 rc, have_neighbors_cdp, err = exec_command(module, 'show cdp neighbors detail') 405 if rc != 0: 406 module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) 407 408 if have_neighbors_cdp: 409 neighbors_cdp = re.findall('Device ID: (.*?)\n.*?Interface: (.*?), Port ID .outgoing port.: (.*?)\n', have_neighbors_cdp, re.S) 410 for host, localif, remoteif in neighbors_cdp: 411 if localif == w['name']: 412 have_host.append(host) 413 have_port.append(remoteif) 414 415 for item in want_neighbors: 416 host = item.get('host') 417 port = item.get('port') 418 if host and host not in have_host: 419 failed_conditions.append('host ' + host) 420 if port and port not in have_port: 421 failed_conditions.append('port ' + port) 422 return failed_conditions 423 424 425def main(): 426 """ main entry point for module execution 427 """ 428 neighbors_spec = dict( 429 host=dict(), 430 port=dict() 431 ) 432 433 element_spec = dict( 434 name=dict(), 435 description=dict(), 436 speed=dict(), 437 mtu=dict(), 438 duplex=dict(choices=['full', 'half', 'auto']), 439 enabled=dict(default=True, type='bool'), 440 tx_rate=dict(), 441 rx_rate=dict(), 442 neighbors=dict(type='list', elements='dict', options=neighbors_spec), 443 delay=dict(default=10, type='int'), 444 state=dict(default='present', 445 choices=['present', 'absent', 'up', 'down']) 446 ) 447 448 aggregate_spec = deepcopy(element_spec) 449 aggregate_spec['name'] = dict(required=True) 450 451 # remove default in aggregate spec, to handle common arguments 452 remove_default_spec(aggregate_spec) 453 454 argument_spec = dict( 455 aggregate=dict(type='list', elements='dict', options=aggregate_spec), 456 ) 457 458 argument_spec.update(element_spec) 459 argument_spec.update(ios_argument_spec) 460 461 required_one_of = [['name', 'aggregate']] 462 mutually_exclusive = [['name', 'aggregate']] 463 464 module = AnsibleModule(argument_spec=argument_spec, 465 required_one_of=required_one_of, 466 mutually_exclusive=mutually_exclusive, 467 supports_check_mode=True) 468 warnings = list() 469 check_args(module, warnings) 470 471 result = {'changed': False} 472 if warnings: 473 result['warnings'] = warnings 474 475 want = map_params_to_obj(module) 476 have = map_config_to_obj(module) 477 478 commands = map_obj_to_commands((want, have)) 479 result['commands'] = commands 480 481 if commands: 482 if not module.check_mode: 483 load_config(module, commands) 484 result['changed'] = True 485 486 failed_conditions = check_declarative_intent_params(module, want, result) 487 488 if failed_conditions: 489 msg = 'One or more conditional statements have not been satisfied' 490 module.fail_json(msg=msg, failed_conditions=failed_conditions) 491 492 module.exit_json(**result) 493 494 495if __name__ == '__main__': 496 main() 497