1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (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: iosxr_interface 19version_added: "2.4" 20author: 21 - "Ganesh Nalawade (@ganeshrn)" 22 - "Kedar Kekan (@kedarX)" 23short_description: Manage Interface on Cisco IOS XR network devices 24description: 25 - This module provides declarative management of Interfaces 26 on Cisco IOS XR network devices. 27deprecated: 28 removed_in: '2.13' 29 alternative: iosxr_interfaces 30 why: Newer and updated modules released with more functionality in Ansible 2.9 31requirements: 32 - ncclient >= 0.5.3 when using netconf 33 - lxml >= 4.1.1 when using netconf 34extends_documentation_fragment: iosxr 35notes: 36 - This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html). 37 - Tested against IOS XRv 6.1.3. 38 - Preconfiguration of physical interfaces is not supported with C(netconf) transport. 39options: 40 name: 41 description: 42 - Name of the interface to configure in C(type + path) format. e.g. C(GigabitEthernet0/0/0/0) 43 required: true 44 description: 45 description: 46 - Description of Interface being configured. 47 enabled: 48 description: 49 - Removes the shutdown configuration, which removes the forced administrative down on the interface, 50 enabling it to move to an up or down state. 51 type: bool 52 default: True 53 active: 54 description: 55 - Whether the interface is C(active) or C(preconfigured). Preconfiguration allows you to configure modular 56 services cards before they are inserted into the router. When the cards are inserted, they are instantly 57 configured. Active cards are the ones already inserted. 58 choices: ['active', 'preconfigure'] 59 default: active 60 version_added: 2.5 61 speed: 62 description: 63 - Configure the speed for an interface. Default is auto-negotiation when not configured. 64 choices: ['10', '100', '1000'] 65 mtu: 66 description: 67 - Sets the MTU value for the interface. Range is between 64 and 65535' 68 duplex: 69 description: 70 - Configures the interface duplex mode. Default is auto-negotiation when not configured. 71 choices: ['full', 'half'] 72 tx_rate: 73 description: 74 - Transmit rate in bits per second (bps). 75 - This is state check parameter only. 76 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 77 rx_rate: 78 description: 79 - Receiver rate in bits per second (bps). 80 - This is state check parameter only. 81 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 82 aggregate: 83 description: 84 - List of Interface definitions. Include multiple interface configurations together, 85 one each on a separate line 86 delay: 87 description: 88 - Time in seconds to wait before checking for the operational state on remote 89 device. This wait is applicable for operational state argument which are 90 I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). 91 default: 10 92 state: 93 description: 94 - State of the Interface configuration, C(up) means present and 95 operationally up and C(down) means present and operationally C(down) 96 default: present 97 choices: ['present', 'absent', 'up', 'down'] 98""" 99 100EXAMPLES = """ 101- name: configure interface 102 iosxr_interface: 103 name: GigabitEthernet0/0/0/2 104 description: test-interface 105 speed: 100 106 duplex: half 107 mtu: 512 108 109- name: remove interface 110 iosxr_interface: 111 name: GigabitEthernet0/0/0/2 112 state: absent 113 114- name: make interface up 115 iosxr_interface: 116 name: GigabitEthernet0/0/0/2 117 enabled: True 118 119- name: make interface down 120 iosxr_interface: 121 name: GigabitEthernet0/0/0/2 122 enabled: False 123 124- name: Create interface using aggregate 125 iosxr_interface: 126 aggregate: 127 - name: GigabitEthernet0/0/0/3 128 - name: GigabitEthernet0/0/0/2 129 speed: 100 130 duplex: full 131 mtu: 512 132 state: present 133 134- name: Create interface using aggregate along with additional params in aggregate 135 iosxr_interface: 136 aggregate: 137 - { name: GigabitEthernet0/0/0/3, description: test-interface 3 } 138 - { name: GigabitEthernet0/0/0/2, description: test-interface 2 } 139 speed: 100 140 duplex: full 141 mtu: 512 142 state: present 143 144- name: Delete interface using aggregate 145 iosxr_interface: 146 aggregate: 147 - name: GigabitEthernet0/0/0/3 148 - name: GigabitEthernet0/0/0/2 149 state: absent 150 151- name: Check intent arguments 152 iosxr_interface: 153 name: GigabitEthernet0/0/0/5 154 state: up 155 delay: 20 156 157- name: Config + intent 158 iosxr_interface: 159 name: GigabitEthernet0/0/0/5 160 enabled: False 161 state: down 162 delay: 20 163""" 164 165RETURN = """ 166commands: 167 description: The list of configuration mode commands sent to device with transport C(cli) 168 returned: always (empty list when no commands to send) 169 type: list 170 sample: 171 - interface GigabitEthernet0/0/0/2 172 - description test-interface 173 - duplex half 174 - mtu 512 175 176xml: 177 description: NetConf rpc xml sent to device with transport C(netconf) 178 returned: always (empty list when no xml rpc to send) 179 type: list 180 version_added: 2.5 181 sample: 182 - '<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> 183 <interface-configurations xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"> 184 <interface-configuration xc:operation="merge"> 185 <active>act</active> 186 <interface-name>GigabitEthernet0/0/0/0</interface-name> 187 <description>test-interface-0</description> 188 <mtus><mtu> 189 <owner>GigabitEthernet</owner> 190 <mtu>512</mtu> 191 </mtu></mtus> 192 <ethernet xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-drivers-media-eth-cfg"> 193 <speed>100</speed> 194 <duplex>half</duplex> 195 </ethernet> 196 </interface-configuration> 197 </interface-configurations></config>' 198""" 199import re 200from time import sleep 201from copy import deepcopy 202import collections 203 204from ansible.module_utils.basic import AnsibleModule 205from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, build_xml 206from ansible.module_utils.network.iosxr.iosxr import run_commands, iosxr_argument_spec, get_oper 207from ansible.module_utils.network.iosxr.iosxr import is_netconf, is_cliconf, etree_findall, etree_find 208from ansible.module_utils.network.common.utils import conditional, remove_default_spec 209 210 211def validate_mtu(value): 212 if value and not 64 <= int(value) <= 65535: 213 return False, 'mtu must be between 64 and 65535' 214 return True, None 215 216 217class ConfigBase(object): 218 def __init__(self, module): 219 self._module = module 220 self._result = {'changed': False, 'warnings': []} 221 self._want = list() 222 self._have = list() 223 224 def validate_param_values(self, param=None): 225 for key, value in param.items(): 226 # validate the param value (if validator func exists) 227 validator = globals().get('validate_%s' % key) 228 if callable(validator): 229 rc, msg = validator(value) 230 if not rc: 231 self._module.fail_json(msg=msg) 232 233 def map_params_to_obj(self): 234 aggregate = self._module.params.get('aggregate') 235 if aggregate: 236 for item in aggregate: 237 for key in item: 238 if item.get(key) is None: 239 item[key] = self._module.params[key] 240 241 self.validate_param_values(item) 242 d = item.copy() 243 244 match = re.match(r"(^[a-z]+)([0-9/]+$)", d['name'], re.I) 245 if match: 246 d['owner'] = match.groups()[0] 247 248 if d['active'] == 'preconfigure': 249 d['active'] = 'pre' 250 else: 251 d['active'] = 'act' 252 253 self._want.append(d) 254 255 else: 256 self.validate_param_values(self._module.params) 257 params = { 258 'name': self._module.params['name'], 259 'description': self._module.params['description'], 260 'speed': self._module.params['speed'], 261 'mtu': self._module.params['mtu'], 262 'duplex': self._module.params['duplex'], 263 'state': self._module.params['state'], 264 'delay': self._module.params['delay'], 265 'tx_rate': self._module.params['tx_rate'], 266 'rx_rate': self._module.params['rx_rate'], 267 'enabled': self._module.params['enabled'], 268 'active': self._module.params['active'], 269 } 270 271 match = re.match(r"(^[a-z]+)([0-9/]+$)", params['name'], re.I) 272 if match: 273 params['owner'] = match.groups()[0] 274 275 if params['active'] == 'preconfigure': 276 params['active'] = 'pre' 277 else: 278 params['active'] = 'act' 279 280 self._want.append(params) 281 282 283class CliConfiguration(ConfigBase): 284 def __init__(self, module): 285 super(CliConfiguration, self).__init__(module) 286 287 def parse_shutdown(self, intf_config): 288 for cfg in intf_config: 289 match = re.search(r'%s' % 'shutdown', cfg, re.M) 290 if match: 291 return True 292 return False 293 294 def parse_config_argument(self, intf_config, arg): 295 for cfg in intf_config: 296 match = re.search(r'%s (.+)$' % arg, cfg, re.M) 297 if match: 298 return match.group(1) 299 300 def search_obj_in_list(self, name): 301 for obj in self._have: 302 if obj['name'] == name: 303 return obj 304 return None 305 306 def map_config_to_obj(self): 307 data = get_config(self._module, config_filter='interface') 308 data_lines = data.splitlines() 309 start_indexes = [i for i, e in enumerate(data_lines) if e.startswith('interface')] 310 end_indexes = [i for i, e in enumerate(data_lines) if e == '!'] 311 312 intf_configs = list() 313 for start_index, end_index in zip(start_indexes, end_indexes): 314 intf_configs.append([i.strip() for i in data_lines[start_index:end_index]]) 315 316 if not intf_configs: 317 return list() 318 319 for intf_config in intf_configs: 320 name = intf_config[0].strip().split()[1] 321 322 active = 'act' 323 if name == 'preconfigure': 324 active = 'pre' 325 name = intf_config[0].strip().split()[2] 326 327 obj = { 328 'name': name, 329 'description': self.parse_config_argument(intf_config, 'description'), 330 'speed': self.parse_config_argument(intf_config, 'speed'), 331 'duplex': self.parse_config_argument(intf_config, 'duplex'), 332 'mtu': self.parse_config_argument(intf_config, 'mtu'), 333 'enabled': not bool(self.parse_shutdown(intf_config)), 334 'active': active, 335 'state': 'present' 336 } 337 self._have.append(obj) 338 339 def map_obj_to_commands(self): 340 commands = list() 341 342 args = ('speed', 'description', 'duplex', 'mtu') 343 for want_item in self._want: 344 name = want_item['name'] 345 disable = not want_item['enabled'] 346 state = want_item['state'] 347 348 obj_in_have = self.search_obj_in_list(name) 349 interface = 'interface ' + name 350 351 if state == 'absent' and obj_in_have: 352 commands.append('no ' + interface) 353 354 elif state in ('present', 'up', 'down'): 355 if obj_in_have: 356 for item in args: 357 candidate = want_item.get(item) 358 running = obj_in_have.get(item) 359 if candidate != running: 360 if candidate: 361 cmd = interface + ' ' + item + ' ' + str(candidate) 362 commands.append(cmd) 363 364 if disable and obj_in_have.get('enabled', False): 365 commands.append(interface + ' shutdown') 366 elif not disable and not obj_in_have.get('enabled', False): 367 commands.append('no ' + interface + ' shutdown') 368 else: 369 for item in args: 370 value = want_item.get(item) 371 if value: 372 commands.append(interface + ' ' + item + ' ' + str(value)) 373 if not disable: 374 commands.append('no ' + interface + ' shutdown') 375 self._result['commands'] = commands 376 377 if commands: 378 commit = not self._module.check_mode 379 diff = load_config(self._module, commands, commit=commit) 380 if diff: 381 self._result['diff'] = dict(prepared=diff) 382 self._result['changed'] = True 383 384 def check_declarative_intent_params(self): 385 failed_conditions = [] 386 for want_item in self._want: 387 want_state = want_item.get('state') 388 want_tx_rate = want_item.get('tx_rate') 389 want_rx_rate = want_item.get('rx_rate') 390 if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate: 391 continue 392 393 if self._result['changed']: 394 sleep(want_item['delay']) 395 396 command = 'show interfaces {0!s}'.format(want_item['name']) 397 out = run_commands(self._module, command)[0] 398 399 if want_state in ('up', 'down'): 400 match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) 401 have_state = None 402 if match: 403 have_state = match.group(1) 404 if have_state.strip() == 'administratively': 405 match = re.search(r'%s (\w+)' % 'administratively', out, re.M) 406 if match: 407 have_state = match.group(1) 408 409 if have_state is None or not conditional(want_state, have_state.strip()): 410 failed_conditions.append('state ' + 'eq({0!s})'.format(want_state)) 411 412 if want_tx_rate: 413 match = re.search(r'%s (\d+)' % 'output rate', out, re.M) 414 have_tx_rate = None 415 if match: 416 have_tx_rate = match.group(1) 417 418 if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): 419 failed_conditions.append('tx_rate ' + want_tx_rate) 420 421 if want_rx_rate: 422 match = re.search(r'%s (\d+)' % 'input rate', out, re.M) 423 have_rx_rate = None 424 if match: 425 have_rx_rate = match.group(1) 426 427 if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): 428 failed_conditions.append('rx_rate ' + want_rx_rate) 429 430 if failed_conditions: 431 msg = 'One or more conditional statements have not been satisfied' 432 self._module.fail_json(msg=msg, failed_conditions=failed_conditions) 433 434 def run(self): 435 self.map_params_to_obj() 436 self.map_config_to_obj() 437 self.map_obj_to_commands() 438 self.check_declarative_intent_params() 439 440 return self._result 441 442 443class NCConfiguration(ConfigBase): 444 def __init__(self, module): 445 super(NCConfiguration, self).__init__(module) 446 447 self._intf_meta = collections.OrderedDict() 448 self._shut_meta = collections.OrderedDict() 449 self._data_rate_meta = collections.OrderedDict() 450 self._line_state_meta = collections.OrderedDict() 451 452 def map_obj_to_xml_rpc(self): 453 self._intf_meta.update([ 454 ('interface-configuration', {'xpath': 'interface-configurations/interface-configuration', 'tag': True, 'attrib': 'operation'}), 455 ('a:active', {'xpath': 'interface-configurations/interface-configuration/active', 'operation': 'edit'}), 456 ('a:name', {'xpath': 'interface-configurations/interface-configuration/interface-name'}), 457 ('a:description', {'xpath': 'interface-configurations/interface-configuration/description', 'operation': 'edit'}), 458 ('mtus', {'xpath': 'interface-configurations/interface-configuration/mtus', 'tag': True, 'operation': 'edit'}), 459 ('mtu', {'xpath': 'interface-configurations/interface-configuration/mtus/mtu', 'tag': True, 'operation': 'edit'}), 460 ('a:owner', {'xpath': 'interface-configurations/interface-configuration/mtus/mtu/owner', 'operation': 'edit'}), 461 ('a:mtu', {'xpath': 'interface-configurations/interface-configuration/mtus/mtu/mtu', 'operation': 'edit'}), 462 ('CEthernet', {'xpath': 'interface-configurations/interface-configuration/ethernet', 'tag': True, 'operation': 'edit', 'ns': True}), 463 ('a:speed', {'xpath': 'interface-configurations/interface-configuration/ethernet/speed', 'operation': 'edit'}), 464 ('a:duplex', {'xpath': 'interface-configurations/interface-configuration/ethernet/duplex', 'operation': 'edit'}), 465 ]) 466 467 self._shut_meta.update([ 468 ('interface-configuration', {'xpath': 'interface-configurations/interface-configuration', 'tag': True}), 469 ('a:active', {'xpath': 'interface-configurations/interface-configuration/active', 'operation': 'edit'}), 470 ('a:name', {'xpath': 'interface-configurations/interface-configuration/interface-name'}), 471 ('shutdown', {'xpath': 'interface-configurations/interface-configuration/shutdown', 'tag': True, 'operation': 'edit', 'attrib': 'operation'}), 472 ]) 473 state = self._module.params['state'] 474 475 _get_filter = build_xml('interface-configurations', xmap=self._intf_meta, params=self._want, opcode="filter") 476 477 running = get_config(self._module, source='running', config_filter=_get_filter) 478 intfcfg_nodes = etree_findall(running, 'interface-configuration') 479 480 intf_list = set() 481 shut_list = set() 482 for item in intfcfg_nodes: 483 intf_name = etree_find(item, 'interface-name').text 484 if intf_name is not None: 485 intf_list.add(intf_name) 486 487 if etree_find(item, 'shutdown') is not None: 488 shut_list.add(intf_name) 489 490 intf_params = list() 491 shut_params = list() 492 noshut_params = list() 493 for index, item in enumerate(self._want): 494 if item['name'] in intf_list: 495 intf_params.append(item) 496 if not item['enabled']: 497 shut_params.append(item) 498 if item['name'] in shut_list and item['enabled']: 499 noshut_params.append(item) 500 501 opcode = None 502 if state == 'absent': 503 if intf_params: 504 opcode = "delete" 505 elif state in ('present', 'up', 'down'): 506 intf_params = self._want 507 opcode = 'merge' 508 509 self._result['xml'] = [] 510 _edit_filter_list = list() 511 if opcode: 512 _edit_filter_list.append(build_xml('interface-configurations', xmap=self._intf_meta, 513 params=intf_params, opcode=opcode)) 514 515 if opcode == 'merge': 516 if len(shut_params): 517 _edit_filter_list.append(build_xml('interface-configurations', xmap=self._shut_meta, 518 params=shut_params, opcode='merge')) 519 if len(noshut_params): 520 _edit_filter_list.append(build_xml('interface-configurations', xmap=self._shut_meta, 521 params=noshut_params, opcode='delete')) 522 diff = None 523 if len(_edit_filter_list): 524 commit = not self._module.check_mode 525 diff = load_config(self._module, _edit_filter_list, commit=commit, running=running, 526 nc_get_filter=_get_filter) 527 528 if diff: 529 if self._module._diff: 530 self._result['diff'] = dict(prepared=diff) 531 532 self._result['xml'] = _edit_filter_list 533 self._result['changed'] = True 534 535 def check_declarative_intent_params(self): 536 failed_conditions = [] 537 538 self._data_rate_meta.update([ 539 ('interfaces', {'xpath': 'infra-statistics/interfaces', 'tag': True}), 540 ('interface', {'xpath': 'infra-statistics/interfaces/interface', 'tag': True}), 541 ('a:name', {'xpath': 'infra-statistics/interfaces/interface/interface-name'}), 542 ('cache', {'xpath': 'infra-statistics/interfaces/interface/cache', 'tag': True}), 543 ('data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate', 'tag': True}), 544 ('input-data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate/input-data-rate', 'tag': True}), 545 ('output-data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate/output-data-rate', 'tag': True}), 546 ]) 547 548 self._line_state_meta.update([ 549 ('data-nodes', {'xpath': 'interface-properties/data-nodes', 'tag': True}), 550 ('data-node', {'xpath': 'interface-properties/data-nodes/data-node', 'tag': True}), 551 ('system-view', {'xpath': 'interface-properties/data-nodes/data-node/system-view', 'tag': True}), 552 ('interfaces', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces', 'tag': True}), 553 ('interface', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces/interface', 'tag': True}), 554 ('a:name', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces/interface/interface-name'}), 555 ('line-state', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces/interface/line-state', 'tag': True}), 556 ]) 557 558 _rate_filter = build_xml('infra-statistics', xmap=self._data_rate_meta, params=self._want, opcode="filter") 559 out = get_oper(self._module, filter=_rate_filter) 560 data_rate_list = etree_findall(out, 'interface') 561 data_rate_map = dict() 562 for item in data_rate_list: 563 data_rate_map.update({etree_find(item, 'interface-name').text: dict()}) 564 data_rate_map[etree_find(item, 'interface-name').text].update({'input-data-rate': etree_find(item, 'input-data-rate').text, 565 'output-data-rate': etree_find(item, 'output-data-rate').text}) 566 567 _line_state_filter = build_xml('interface-properties', xmap=self._line_state_meta, params=self._want, opcode="filter") 568 out = get_oper(self._module, filter=_line_state_filter) 569 line_state_list = etree_findall(out, 'interface') 570 line_state_map = dict() 571 for item in line_state_list: 572 line_state_map.update({etree_find(item, 'interface-name').text: etree_find(item, 'line-state').text}) 573 574 for want_item in self._want: 575 want_state = want_item.get('state') 576 want_tx_rate = want_item.get('tx_rate') 577 want_rx_rate = want_item.get('rx_rate') 578 if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate: 579 continue 580 581 if self._result['changed']: 582 sleep(want_item['delay']) 583 584 if want_state in ('up', 'down'): 585 if want_state not in line_state_map[want_item['name']]: 586 failed_conditions.append('state ' + 'eq({0!s})'.format(want_state)) 587 588 if want_tx_rate: 589 if want_tx_rate != data_rate_map[want_item['name']]['output-data-rate']: 590 failed_conditions.append('tx_rate ' + want_tx_rate) 591 592 if want_rx_rate: 593 if want_rx_rate != data_rate_map[want_item['name']]['input-data-rate']: 594 failed_conditions.append('rx_rate ' + want_rx_rate) 595 596 if failed_conditions: 597 msg = 'One or more conditional statements have not been satisfied' 598 self._module.fail_json(msg=msg, failed_conditions=failed_conditions) 599 600 def run(self): 601 self.map_params_to_obj() 602 self.map_obj_to_xml_rpc() 603 self.check_declarative_intent_params() 604 return self._result 605 606 607def main(): 608 """ main entry point for module execution 609 """ 610 element_spec = dict( 611 name=dict(type='str'), 612 description=dict(type='str'), 613 speed=dict(choices=['10', '100', '1000']), 614 mtu=dict(), 615 duplex=dict(choices=['full', 'half']), 616 enabled=dict(default=True, type='bool'), 617 active=dict(default='active', type='str', choices=['active', 'preconfigure']), 618 tx_rate=dict(), 619 rx_rate=dict(), 620 delay=dict(default=10, type='int'), 621 state=dict(default='present', 622 choices=['present', 'absent', 'up', 'down']) 623 ) 624 625 aggregate_spec = deepcopy(element_spec) 626 aggregate_spec['name'] = dict(required=True) 627 628 # remove default in aggregate spec, to handle common arguments 629 remove_default_spec(aggregate_spec) 630 631 argument_spec = dict( 632 aggregate=dict(type='list', elements='dict', options=aggregate_spec), 633 ) 634 635 argument_spec.update(element_spec) 636 argument_spec.update(iosxr_argument_spec) 637 638 required_one_of = [['name', 'aggregate']] 639 mutually_exclusive = [['name', 'aggregate']] 640 641 module = AnsibleModule(argument_spec=argument_spec, 642 required_one_of=required_one_of, 643 mutually_exclusive=mutually_exclusive, 644 supports_check_mode=True) 645 646 config_object = None 647 if is_cliconf(module): 648 # Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported 649 # module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead", 650 # version='2.9') 651 config_object = CliConfiguration(module) 652 elif is_netconf(module): 653 if module.params['active'] == 'preconfigure': 654 module.fail_json(msg="Physical interface pre-configuration is not supported with transport 'netconf'") 655 config_object = NCConfiguration(module) 656 657 result = {} 658 if config_object: 659 result = config_object.run() 660 module.exit_json(**result) 661 662 663if __name__ == '__main__': 664 main() 665