Cliconf and Netconf refactoring iosxr_interface (#33909)
* Cliconf and Netconf refactoring iosxr_interface * adds `xml` key and related changes for netconf output * * review comments changes
This commit is contained in:
parent
8a9865cb10
commit
78a14d7966
11 changed files with 875 additions and 227 deletions
|
@ -77,7 +77,11 @@ class NetconfConnection(Connection):
|
||||||
|
|
||||||
warnings = []
|
warnings = []
|
||||||
for error in error_list:
|
for error in error_list:
|
||||||
message = error.find('./nc:error-message', NS_MAP).text
|
try:
|
||||||
|
message = error.find('./nc:error-message', NS_MAP).text
|
||||||
|
except Exception:
|
||||||
|
message = error.find('./nc:error-info', NS_MAP).text
|
||||||
|
|
||||||
severity = error.find('./nc:error-severity', NS_MAP).text
|
severity = error.find('./nc:error-severity', NS_MAP).text
|
||||||
|
|
||||||
if severity == 'warning' and self.ignore_warning:
|
if severity == 'warning' and self.ignore_warning:
|
||||||
|
|
|
@ -62,7 +62,9 @@ NS_DICT = {
|
||||||
'M:TYPE_NSMAP': {"idx": "urn:ietf:params:xml:ns:yang:iana-if-type"},
|
'M:TYPE_NSMAP': {"idx": "urn:ietf:params:xml:ns:yang:iana-if-type"},
|
||||||
'ETHERNET_NSMAP': {None: "http://openconfig.net/yang/interfaces/ethernet"},
|
'ETHERNET_NSMAP': {None: "http://openconfig.net/yang/interfaces/ethernet"},
|
||||||
'CETHERNET_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-drivers-media-eth-cfg"},
|
'CETHERNET_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-drivers-media-eth-cfg"},
|
||||||
'INTERFACE-CONFIGURATIONS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"}
|
'INTERFACE-CONFIGURATIONS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"},
|
||||||
|
'INFRA-STATISTICS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-statsd-oper"},
|
||||||
|
'INTERFACE-PROPERTIES_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-oper"},
|
||||||
}
|
}
|
||||||
|
|
||||||
iosxr_provider_spec = {
|
iosxr_provider_spec = {
|
||||||
|
@ -253,7 +255,11 @@ def build_xml(container, xmap=None, params=None, opcode=None):
|
||||||
|
|
||||||
|
|
||||||
def etree_find(root, node):
|
def etree_find(root, node):
|
||||||
element = etree.fromstring(root).find('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
try:
|
||||||
|
element = etree.fromstring(root).find('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
||||||
|
except Exception:
|
||||||
|
element = etree.fromstring(etree.tostring(root)).find('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
||||||
|
|
||||||
if element is not None:
|
if element is not None:
|
||||||
return element
|
return element
|
||||||
|
|
||||||
|
@ -261,7 +267,11 @@ def etree_find(root, node):
|
||||||
|
|
||||||
|
|
||||||
def etree_findall(root, node):
|
def etree_findall(root, node):
|
||||||
element = etree.fromstring(root).findall('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
try:
|
||||||
|
element = etree.fromstring(root).findall('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
||||||
|
except Exception:
|
||||||
|
element = etree.fromstring(etree.tostring(root)).findall('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
|
||||||
|
|
||||||
if element is not None:
|
if element is not None:
|
||||||
return element
|
return element
|
||||||
|
|
||||||
|
@ -336,6 +346,17 @@ def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, p
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def get_oper(module, filter=None):
|
||||||
|
global _DEVICE_CONFIGS
|
||||||
|
|
||||||
|
conn = get_connection(module)
|
||||||
|
|
||||||
|
if filter is not None:
|
||||||
|
response = conn.get(filter)
|
||||||
|
|
||||||
|
return to_bytes(etree.tostring(response), errors='surrogate_then_replace').strip()
|
||||||
|
|
||||||
|
|
||||||
def get_config(module, config_filter=None, source='running'):
|
def get_config(module, config_filter=None, source='running'):
|
||||||
global _DEVICE_CONFIGS
|
global _DEVICE_CONFIGS
|
||||||
|
|
||||||
|
@ -370,7 +391,8 @@ def load_config(module, command_filter, commit=False, replace=False,
|
||||||
# conn.discard_changes()
|
# conn.discard_changes()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn.edit_config(command_filter)
|
for filter in to_list(command_filter):
|
||||||
|
conn.edit_config(filter)
|
||||||
|
|
||||||
candidate = get_config(module, source='candidate', config_filter=nc_get_filter)
|
candidate = get_config(module, source='candidate', config_filter=nc_get_filter)
|
||||||
diff = get_config_diff(module, running, candidate)
|
diff = get_config_diff(module, running, candidate)
|
||||||
|
|
|
@ -68,14 +68,29 @@ EXAMPLES = """
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
commands:
|
commands:
|
||||||
description: The list of configuration mode commands to send to the device
|
description: The list of configuration mode commands sent to device with transport C(cli)
|
||||||
returned: always
|
returned: always (empty list when no commands to send)
|
||||||
type: list
|
type: list
|
||||||
sample:
|
sample:
|
||||||
- banner login
|
- banner login
|
||||||
- this is my login banner
|
- this is my login banner
|
||||||
- that contains a multiline
|
- that contains a multiline
|
||||||
- string
|
- string
|
||||||
|
|
||||||
|
xml:
|
||||||
|
description: NetConf rpc xml sent to device with transport C(netconf)
|
||||||
|
returned: always (empty list when no xml rpc to send)
|
||||||
|
type: list
|
||||||
|
version_added: 2.5
|
||||||
|
sample:
|
||||||
|
- '<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<banners xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-infra-cfg">
|
||||||
|
<banner xc:operation="merge">
|
||||||
|
<banner-name>motd</banner-name>
|
||||||
|
<banner-text>Ansible banner example</banner-text>
|
||||||
|
</banner>
|
||||||
|
</banners>
|
||||||
|
</config>'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -83,9 +98,9 @@ import collections
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, discard_config
|
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
||||||
from ansible.module_utils.network.iosxr.iosxr import build_xml, is_cliconf, is_netconf
|
from ansible.module_utils.network.iosxr.iosxr import build_xml, is_cliconf
|
||||||
from ansible.module_utils.network.iosxr.iosxr import etree_find
|
from ansible.module_utils.network.iosxr.iosxr import etree_find, is_netconf
|
||||||
|
|
||||||
|
|
||||||
class ConfigBase(object):
|
class ConfigBase(object):
|
||||||
|
@ -162,7 +177,7 @@ class NCConfiguration(ConfigBase):
|
||||||
('a:text', {'xpath': 'banner/banner-text', 'operation': 'edit'})
|
('a:text', {'xpath': 'banner/banner-text', 'operation': 'edit'})
|
||||||
])
|
])
|
||||||
|
|
||||||
def map_obj_to_commands(self):
|
def map_obj_to_xml_rpc(self):
|
||||||
state = self._module.params['state']
|
state = self._module.params['state']
|
||||||
_get_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode="filter")
|
_get_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode="filter")
|
||||||
|
|
||||||
|
@ -180,7 +195,7 @@ class NCConfiguration(ConfigBase):
|
||||||
elif state == 'present':
|
elif state == 'present':
|
||||||
opcode = 'merge'
|
opcode = 'merge'
|
||||||
|
|
||||||
self._result['commands'] = []
|
self._result['xml'] = []
|
||||||
if opcode:
|
if opcode:
|
||||||
_edit_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode=opcode)
|
_edit_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode=opcode)
|
||||||
|
|
||||||
|
@ -189,7 +204,7 @@ class NCConfiguration(ConfigBase):
|
||||||
diff = load_config(self._module, _edit_filter, commit=commit, running=running, nc_get_filter=_get_filter)
|
diff = load_config(self._module, _edit_filter, commit=commit, running=running, nc_get_filter=_get_filter)
|
||||||
|
|
||||||
if diff:
|
if diff:
|
||||||
self._result['commands'] = _edit_filter
|
self._result['xml'] = _edit_filter
|
||||||
if self._module._diff:
|
if self._module._diff:
|
||||||
self._result['diff'] = dict(prepared=diff)
|
self._result['diff'] = dict(prepared=diff)
|
||||||
|
|
||||||
|
@ -197,7 +212,7 @@ class NCConfiguration(ConfigBase):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.map_params_to_obj()
|
self.map_params_to_obj()
|
||||||
self.map_obj_to_commands()
|
self.map_obj_to_xml_rpc()
|
||||||
|
|
||||||
return self._result
|
return self._result
|
||||||
|
|
||||||
|
@ -219,8 +234,10 @@ def main():
|
||||||
required_if=required_if,
|
required_if=required_if,
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
config_object = None
|
||||||
if is_cliconf(module):
|
if is_cliconf(module):
|
||||||
module.deprecate(msg="cli support for 'iosxr_banner' is deprecated. Use transport netconf instead", version="4 releases from v2.5")
|
module.deprecate(msg="cli support for 'iosxr_banner' is deprecated. Use transport netconf instead",
|
||||||
|
version="4 releases from v2.5")
|
||||||
config_object = CliConfiguration(module)
|
config_object = CliConfiguration(module)
|
||||||
elif is_netconf(module):
|
elif is_netconf(module):
|
||||||
config_object = NCConfiguration(module)
|
config_object = NCConfiguration(module)
|
||||||
|
|
|
@ -17,34 +17,49 @@ DOCUMENTATION = """
|
||||||
---
|
---
|
||||||
module: iosxr_interface
|
module: iosxr_interface
|
||||||
version_added: "2.4"
|
version_added: "2.4"
|
||||||
author: "Ganesh Nalawade (@ganeshrn)"
|
author:
|
||||||
|
- "Ganesh Nalawade (@ganeshrn)"
|
||||||
|
- "Kedar Kekan (@kedarX)"
|
||||||
short_description: Manage Interface on Cisco IOS XR network devices
|
short_description: Manage Interface on Cisco IOS XR network devices
|
||||||
description:
|
description:
|
||||||
- This module provides declarative management of Interfaces
|
- This module provides declarative management of Interfaces
|
||||||
on Cisco IOS XR network devices.
|
on Cisco IOS XR network devices.
|
||||||
extends_documentation_fragment: iosxr
|
extends_documentation_fragment: iosxr
|
||||||
notes:
|
notes:
|
||||||
- Tested against IOS XR 6.1.2
|
- Tested against IOS XRv 6.1.2
|
||||||
|
- Preconfiguration of physical interfaces is not supported with C(netconf) transport.
|
||||||
options:
|
options:
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- Name of the Interface.
|
- Name of the interface to configure in C(type + path) format. e.g. C(GigabitEthernet0/0/0/0)
|
||||||
required: true
|
required: true
|
||||||
description:
|
description:
|
||||||
description:
|
description:
|
||||||
- Description of Interface.
|
- Description of Interface being configured.
|
||||||
enabled:
|
enabled:
|
||||||
description:
|
description:
|
||||||
- Interface link status.
|
- Removes the shutdown configuration, which removes the forced administrative down on the interface,
|
||||||
|
enabling it to move to an up or down state.
|
||||||
|
type: bool
|
||||||
|
default: True
|
||||||
|
active:
|
||||||
|
description:
|
||||||
|
- Whether the interface is C(active) or C(preconfigured). Preconfiguration allows you to configure modular
|
||||||
|
services cards before they are inserted into the router. When the cards are inserted, they are instantly
|
||||||
|
configured. Active cards are the ones already inserted.
|
||||||
|
choices: ['active', 'preconfigure']
|
||||||
|
default: active
|
||||||
|
version_added: 2.5
|
||||||
speed:
|
speed:
|
||||||
description:
|
description:
|
||||||
- Interface link speed.
|
- Configure the speed for an interface. Default is auto-negotiation when not configured.
|
||||||
|
choices: ['10', '100', '1000']
|
||||||
mtu:
|
mtu:
|
||||||
description:
|
description:
|
||||||
- Maximum size of transmit packet.
|
- Sets the MTU value for the interface. Range is between 64 and 65535'
|
||||||
duplex:
|
duplex:
|
||||||
description:
|
description:
|
||||||
- Interface link status
|
- Configures the interface duplex mode. Default is auto-negotiation when not configured.
|
||||||
choices: ['full', 'half']
|
choices: ['full', 'half']
|
||||||
tx_rate:
|
tx_rate:
|
||||||
description:
|
description:
|
||||||
|
@ -53,7 +68,9 @@ options:
|
||||||
description:
|
description:
|
||||||
- Receiver rate in bits per second (bps).
|
- Receiver rate in bits per second (bps).
|
||||||
aggregate:
|
aggregate:
|
||||||
description: List of Interfaces definitions.
|
description:
|
||||||
|
- List of Interface definitions. Include multiple interface configurations together,
|
||||||
|
one each on a seperate line
|
||||||
delay:
|
delay:
|
||||||
description:
|
description:
|
||||||
- Time in seconds to wait before checking for the operational state on remote
|
- Time in seconds to wait before checking for the operational state on remote
|
||||||
|
@ -102,6 +119,16 @@ EXAMPLES = """
|
||||||
mtu: 512
|
mtu: 512
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
- name: Create interface using aggregate along with additional params in aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- { name: GigabitEthernet0/0/0/3, description: test-interface 3 }
|
||||||
|
- { name: GigabitEthernet0/0/0/2, description: test-interface 2 }
|
||||||
|
speed: 100
|
||||||
|
duplex: full
|
||||||
|
mtu: 512
|
||||||
|
state: present
|
||||||
|
|
||||||
- name: Delete interface using aggregate
|
- name: Delete interface using aggregate
|
||||||
iosxr_interface:
|
iosxr_interface:
|
||||||
aggregate:
|
aggregate:
|
||||||
|
@ -125,240 +152,453 @@ EXAMPLES = """
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
commands:
|
commands:
|
||||||
description: The list of configuration mode commands to send to the device.
|
description: The list of configuration mode commands sent to device with transport C(cli)
|
||||||
returned: always, except for the platforms that use Netconf transport to manage the device.
|
returned: always (empty list when no commands to send)
|
||||||
type: list
|
type: list
|
||||||
sample:
|
sample:
|
||||||
- interface GigabitEthernet0/0/0/2
|
- interface GigabitEthernet0/0/0/2
|
||||||
- description test-interface
|
- description test-interface
|
||||||
- duplex half
|
- duplex half
|
||||||
- mtu 512
|
- mtu 512
|
||||||
|
|
||||||
|
xml:
|
||||||
|
description: NetConf rpc xml sent to device with transport C(netconf)
|
||||||
|
returned: always (empty list when no xml rpc to send)
|
||||||
|
type: list
|
||||||
|
version_added: 2.5
|
||||||
|
sample:
|
||||||
|
- '<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||||
|
<interface-configurations xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg">
|
||||||
|
<interface-configuration xc:operation="merge">
|
||||||
|
<active>act</active>
|
||||||
|
<interface-name>GigabitEthernet0/0/0/0</interface-name>
|
||||||
|
<description>test-interface-0</description>
|
||||||
|
<mtus><mtu>
|
||||||
|
<owner>GigabitEthernet</owner>
|
||||||
|
<mtu>512</mtu>
|
||||||
|
</mtu></mtus>
|
||||||
|
<ethernet xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-drivers-media-eth-cfg">
|
||||||
|
<speed>100</speed>
|
||||||
|
<duplex>half</duplex>
|
||||||
|
</ethernet>
|
||||||
|
</interface-configuration>
|
||||||
|
</interface-configurations></config>'
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
import collections
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.connection import exec_command
|
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, build_xml
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
from ansible.module_utils.network.iosxr.iosxr import run_command, iosxr_argument_spec, get_oper
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
from ansible.module_utils.network.iosxr.iosxr import is_netconf, is_cliconf, etree_findall, etree_find
|
||||||
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
|
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
|
||||||
|
|
||||||
|
|
||||||
def validate_mtu(value, module):
|
def validate_mtu(value):
|
||||||
if value and not 64 <= int(value) <= 65535:
|
if value and not 64 <= int(value) <= 65535:
|
||||||
module.fail_json(msg='mtu must be between 64 and 65535')
|
return False, 'mtu must be between 64 and 65535'
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def validate_param_values(module, obj, param=None):
|
class ConfigBase(object):
|
||||||
if param is None:
|
def __init__(self, module):
|
||||||
param = module.params
|
self._module = module
|
||||||
for key in obj:
|
self._result = {'changed': False, 'warnings': []}
|
||||||
# validate the param value (if validator func exists)
|
self._want = list()
|
||||||
validator = globals().get('validate_%s' % key)
|
self._have = list()
|
||||||
if callable(validator):
|
|
||||||
validator(param.get(key), module)
|
|
||||||
|
|
||||||
|
def validate_param_values(self, param=None):
|
||||||
|
for key, value in param.items():
|
||||||
|
# validate the param value (if validator func exists)
|
||||||
|
validator = globals().get('validate_%s' % key)
|
||||||
|
if callable(validator):
|
||||||
|
rc, msg = validator(value)
|
||||||
|
if not rc:
|
||||||
|
self._module.fail_json(msg=msg)
|
||||||
|
|
||||||
def parse_shutdown(intf_config):
|
def map_params_to_obj(self):
|
||||||
for cfg in intf_config:
|
aggregate = self._module.params.get('aggregate')
|
||||||
match = re.search(r'%s' % 'shutdown', cfg, re.M)
|
if aggregate:
|
||||||
if match:
|
for item in aggregate:
|
||||||
return True
|
for key in item:
|
||||||
return False
|
if item.get(key) is None:
|
||||||
|
item[key] = self._module.params[key]
|
||||||
|
|
||||||
|
self.validate_param_values(item)
|
||||||
|
d = item.copy()
|
||||||
|
|
||||||
def parse_config_argument(intf_config, arg):
|
match = re.match(r"(^[a-z]+)([0-9/]+$)", d['name'], re.I)
|
||||||
for cfg in intf_config:
|
if match:
|
||||||
match = re.search(r'%s (.+)$' % arg, cfg, re.M)
|
d['owner'] = match.groups()[0]
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
|
if d['active'] == 'preconfigure':
|
||||||
|
d['active'] = 'pre'
|
||||||
|
else:
|
||||||
|
d['active'] = 'act'
|
||||||
|
|
||||||
def search_obj_in_list(name, lst):
|
self._want.append(d)
|
||||||
for o in lst:
|
|
||||||
if o['name'] == name:
|
|
||||||
return o
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def map_params_to_obj(module):
|
|
||||||
obj = []
|
|
||||||
|
|
||||||
aggregate = module.params.get('aggregate')
|
|
||||||
if aggregate:
|
|
||||||
for item in aggregate:
|
|
||||||
for key in item:
|
|
||||||
if item.get(key) is None:
|
|
||||||
item[key] = module.params[key]
|
|
||||||
|
|
||||||
validate_param_values(module, item, item)
|
|
||||||
d = item.copy()
|
|
||||||
|
|
||||||
if d['enabled']:
|
|
||||||
d['disable'] = False
|
|
||||||
else:
|
|
||||||
d['disable'] = True
|
|
||||||
|
|
||||||
obj.append(d)
|
|
||||||
|
|
||||||
else:
|
|
||||||
validate_param_values(module, module.params)
|
|
||||||
params = {
|
|
||||||
'name': module.params['name'],
|
|
||||||
'description': module.params['description'],
|
|
||||||
'speed': module.params['speed'],
|
|
||||||
'mtu': module.params['mtu'],
|
|
||||||
'duplex': module.params['duplex'],
|
|
||||||
'state': module.params['state'],
|
|
||||||
'delay': module.params['delay'],
|
|
||||||
'tx_rate': module.params['tx_rate'],
|
|
||||||
'rx_rate': module.params['rx_rate']
|
|
||||||
}
|
|
||||||
|
|
||||||
if module.params['enabled']:
|
|
||||||
params.update({'disable': False})
|
|
||||||
else:
|
else:
|
||||||
params.update({'disable': True})
|
self.validate_param_values(self._module.params)
|
||||||
|
params = {
|
||||||
|
'name': self._module.params['name'],
|
||||||
|
'description': self._module.params['description'],
|
||||||
|
'speed': self._module.params['speed'],
|
||||||
|
'mtu': self._module.params['mtu'],
|
||||||
|
'duplex': self._module.params['duplex'],
|
||||||
|
'state': self._module.params['state'],
|
||||||
|
'delay': self._module.params['delay'],
|
||||||
|
'tx_rate': self._module.params['tx_rate'],
|
||||||
|
'rx_rate': self._module.params['rx_rate'],
|
||||||
|
'enabled': self._module.params['enabled'],
|
||||||
|
'active': self._module.params['active'],
|
||||||
|
}
|
||||||
|
|
||||||
obj.append(params)
|
match = re.match(r"(^[a-z]+)([0-9/]+$)", params['name'], re.I)
|
||||||
return obj
|
if match:
|
||||||
|
params['owner'] = match.groups()[0]
|
||||||
|
|
||||||
|
if params['active'] == 'preconfigure':
|
||||||
|
params['active'] = 'pre'
|
||||||
|
else:
|
||||||
|
params['active'] = 'act'
|
||||||
|
|
||||||
|
self._want.append(params)
|
||||||
|
|
||||||
|
|
||||||
def map_config_to_obj(module):
|
class CliConfiguration(ConfigBase):
|
||||||
data = get_config(module, config_filter='interface')
|
def __init__(self, module):
|
||||||
interfaces = data.strip().rstrip('!').split('!')
|
super(CliConfiguration, self).__init__(module)
|
||||||
|
|
||||||
if not interfaces:
|
def parse_shutdown(self, intf_config):
|
||||||
return list()
|
for cfg in intf_config:
|
||||||
|
match = re.search(r'%s' % 'shutdown', cfg, re.M)
|
||||||
|
if match:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
instances = list()
|
def parse_config_argument(self, intf_config, arg):
|
||||||
|
for cfg in intf_config:
|
||||||
|
match = re.search(r'%s (.+)$' % arg, cfg, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
for interface in interfaces:
|
def search_obj_in_list(self, name):
|
||||||
intf_config = interface.strip().splitlines()
|
for obj in self._have:
|
||||||
|
if obj['name'] == name:
|
||||||
|
return obj
|
||||||
|
return None
|
||||||
|
|
||||||
name = intf_config[0].strip().split()[1]
|
def map_config_to_obj(self):
|
||||||
|
data = get_config(self._module, config_filter='interface')
|
||||||
|
interfaces = data.strip().rstrip('!').split('!')
|
||||||
|
|
||||||
if name == 'preconfigure':
|
if not interfaces:
|
||||||
name = intf_config[0].strip().split()[2]
|
return list()
|
||||||
|
|
||||||
obj = {
|
for interface in interfaces:
|
||||||
'name': name,
|
intf_config = interface.strip().splitlines()
|
||||||
'description': parse_config_argument(intf_config, 'description'),
|
|
||||||
'speed': parse_config_argument(intf_config, 'speed'),
|
name = intf_config[0].strip().split()[1]
|
||||||
'duplex': parse_config_argument(intf_config, 'duplex'),
|
|
||||||
'mtu': parse_config_argument(intf_config, 'mtu'),
|
active = 'act'
|
||||||
'disable': True if parse_shutdown(intf_config) else False,
|
if name == 'preconfigure':
|
||||||
'state': 'present'
|
active = 'pre'
|
||||||
}
|
name = intf_config[0].strip().split()[2]
|
||||||
instances.append(obj)
|
|
||||||
return instances
|
obj = {
|
||||||
|
'name': name,
|
||||||
|
'description': self.parse_config_argument(intf_config, 'description'),
|
||||||
|
'speed': self.parse_config_argument(intf_config, 'speed'),
|
||||||
|
'duplex': self.parse_config_argument(intf_config, 'duplex'),
|
||||||
|
'mtu': self.parse_config_argument(intf_config, 'mtu'),
|
||||||
|
'enabled': True if not self.parse_shutdown(intf_config) else False,
|
||||||
|
'active': active,
|
||||||
|
'state': 'present'
|
||||||
|
}
|
||||||
|
self._have.append(obj)
|
||||||
|
|
||||||
|
def map_obj_to_commands(self):
|
||||||
|
commands = list()
|
||||||
|
|
||||||
|
args = ('speed', 'description', 'duplex', 'mtu')
|
||||||
|
for want_item in self._want:
|
||||||
|
name = want_item['name']
|
||||||
|
disable = not want_item['enabled']
|
||||||
|
state = want_item['state']
|
||||||
|
|
||||||
|
obj_in_have = self.search_obj_in_list(name)
|
||||||
|
interface = 'interface ' + name
|
||||||
|
|
||||||
|
if state == 'absent' and obj_in_have:
|
||||||
|
commands.append('no ' + interface)
|
||||||
|
|
||||||
|
elif state in ('present', 'up', 'down'):
|
||||||
|
if obj_in_have:
|
||||||
|
for item in args:
|
||||||
|
candidate = want_item.get(item)
|
||||||
|
running = obj_in_have.get(item)
|
||||||
|
if candidate != running:
|
||||||
|
if candidate:
|
||||||
|
cmd = interface + ' ' + item + ' ' + str(candidate)
|
||||||
|
commands.append(cmd)
|
||||||
|
|
||||||
|
if disable and obj_in_have.get('enabled', False):
|
||||||
|
commands.append(interface + ' shutdown')
|
||||||
|
elif not disable and not obj_in_have.get('enabled', False):
|
||||||
|
commands.append('no ' + interface + ' shutdown')
|
||||||
|
else:
|
||||||
|
for item in args:
|
||||||
|
value = want_item.get(item)
|
||||||
|
if value:
|
||||||
|
commands.append(interface + ' ' + item + ' ' + str(value))
|
||||||
|
if not disable:
|
||||||
|
commands.append('no ' + interface + ' shutdown')
|
||||||
|
self._result['commands'] = commands
|
||||||
|
|
||||||
|
if commands:
|
||||||
|
commit = not self._module.check_mode
|
||||||
|
diff = load_config(self._module, commands, commit=commit)
|
||||||
|
if diff:
|
||||||
|
self._result['diff'] = dict(prepared=diff)
|
||||||
|
self._result['changed'] = True
|
||||||
|
|
||||||
|
def check_declarative_intent_params(self):
|
||||||
|
failed_conditions = []
|
||||||
|
for want_item in self._want:
|
||||||
|
want_state = want_item.get('state')
|
||||||
|
want_tx_rate = want_item.get('tx_rate')
|
||||||
|
want_rx_rate = want_item.get('rx_rate')
|
||||||
|
if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self._result['changed']:
|
||||||
|
sleep(want_item['delay'])
|
||||||
|
|
||||||
|
command = 'show interfaces {!s}'.format(want_item['name'])
|
||||||
|
out = run_command(self._module, command)[0]
|
||||||
|
|
||||||
|
if want_state in ('up', 'down'):
|
||||||
|
match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M)
|
||||||
|
have_state = None
|
||||||
|
if match:
|
||||||
|
have_state = match.group(1)
|
||||||
|
if have_state.strip() == 'administratively':
|
||||||
|
match = re.search(r'%s (\w+)' % 'administratively', out, re.M)
|
||||||
|
if match:
|
||||||
|
have_state = match.group(1)
|
||||||
|
|
||||||
|
if have_state is None or not conditional(want_state, have_state.strip()):
|
||||||
|
failed_conditions.append('state ' + 'eq({!s})'.format(want_state))
|
||||||
|
|
||||||
|
if want_tx_rate:
|
||||||
|
match = re.search(r'%s (\d+)' % 'output rate', out, re.M)
|
||||||
|
have_tx_rate = None
|
||||||
|
if match:
|
||||||
|
have_tx_rate = match.group(1)
|
||||||
|
|
||||||
|
if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int):
|
||||||
|
failed_conditions.append('tx_rate ' + want_tx_rate)
|
||||||
|
|
||||||
|
if want_rx_rate:
|
||||||
|
match = re.search(r'%s (\d+)' % 'input rate', out, re.M)
|
||||||
|
have_rx_rate = None
|
||||||
|
if match:
|
||||||
|
have_rx_rate = match.group(1)
|
||||||
|
|
||||||
|
if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int):
|
||||||
|
failed_conditions.append('rx_rate ' + want_rx_rate)
|
||||||
|
|
||||||
|
if failed_conditions:
|
||||||
|
msg = 'One or more conditional statements have not been satisfied'
|
||||||
|
self._module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.map_params_to_obj()
|
||||||
|
self.map_config_to_obj()
|
||||||
|
self.map_obj_to_commands()
|
||||||
|
self.check_declarative_intent_params()
|
||||||
|
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
|
||||||
def map_obj_to_commands(updates):
|
class NCConfiguration(ConfigBase):
|
||||||
commands = list()
|
def __init__(self, module):
|
||||||
want, have = updates
|
super(NCConfiguration, self).__init__(module)
|
||||||
|
|
||||||
args = ('speed', 'description', 'duplex', 'mtu')
|
self._intf_meta = collections.OrderedDict()
|
||||||
for w in want:
|
self._shut_meta = collections.OrderedDict()
|
||||||
name = w['name']
|
self._data_rate_meta = collections.OrderedDict()
|
||||||
disable = w['disable']
|
self._line_state_meta = collections.OrderedDict()
|
||||||
state = w['state']
|
|
||||||
|
|
||||||
obj_in_have = search_obj_in_list(name, have)
|
def map_obj_to_xml_rpc(self):
|
||||||
interface = 'interface ' + name
|
self._intf_meta.update([
|
||||||
|
('interface-configuration', {'xpath': 'interface-configurations/interface-configuration', 'tag': True, 'attrib': 'operation'}),
|
||||||
|
('a:active', {'xpath': 'interface-configurations/interface-configuration/active', 'operation': 'edit'}),
|
||||||
|
('a:name', {'xpath': 'interface-configurations/interface-configuration/interface-name'}),
|
||||||
|
('a:description', {'xpath': 'interface-configurations/interface-configuration/description', 'operation': 'edit'}),
|
||||||
|
('mtus', {'xpath': 'interface-configurations/interface-configuration/mtus', 'tag': True, 'operation': 'edit'}),
|
||||||
|
('mtu', {'xpath': 'interface-configurations/interface-configuration/mtus/mtu', 'tag': True, 'operation': 'edit'}),
|
||||||
|
('a:owner', {'xpath': 'interface-configurations/interface-configuration/mtus/mtu/owner', 'operation': 'edit'}),
|
||||||
|
('a:mtu', {'xpath': 'interface-configurations/interface-configuration/mtus/mtu/mtu', 'operation': 'edit'}),
|
||||||
|
('CEthernet', {'xpath': 'interface-configurations/interface-configuration/ethernet', 'tag': True, 'operation': 'edit', 'ns': True}),
|
||||||
|
('a:speed', {'xpath': 'interface-configurations/interface-configuration/ethernet/speed', 'operation': 'edit'}),
|
||||||
|
('a:duplex', {'xpath': 'interface-configurations/interface-configuration/ethernet/duplex', 'operation': 'edit'}),
|
||||||
|
])
|
||||||
|
|
||||||
if state == 'absent' and obj_in_have:
|
self._shut_meta.update([
|
||||||
commands.append('no ' + interface)
|
('interface-configuration', {'xpath': 'interface-configurations/interface-configuration', 'tag': True}),
|
||||||
|
('a:active', {'xpath': 'interface-configurations/interface-configuration/active', 'operation': 'edit'}),
|
||||||
|
('a:name', {'xpath': 'interface-configurations/interface-configuration/interface-name'}),
|
||||||
|
('shutdown', {'xpath': 'interface-configurations/interface-configuration/shutdown', 'tag': True, 'operation': 'edit', 'attrib': 'operation'}),
|
||||||
|
])
|
||||||
|
state = self._module.params['state']
|
||||||
|
|
||||||
|
_get_filter = build_xml('interface-configurations', xmap=self._intf_meta, params=self._want, opcode="filter")
|
||||||
|
|
||||||
|
running = get_config(self._module, source='running', config_filter=_get_filter)
|
||||||
|
intfcfg_nodes = etree_findall(running, 'interface-configuration')
|
||||||
|
|
||||||
|
intf_list = set()
|
||||||
|
shut_list = set()
|
||||||
|
for item in intfcfg_nodes:
|
||||||
|
intf_name = etree_find(item, 'interface-name').text
|
||||||
|
if intf_name is not None:
|
||||||
|
intf_list.add(intf_name)
|
||||||
|
|
||||||
|
if etree_find(item, 'shutdown') is not None:
|
||||||
|
shut_list.add(intf_name)
|
||||||
|
|
||||||
|
intf_params = list()
|
||||||
|
shut_params = list()
|
||||||
|
noshut_params = list()
|
||||||
|
for index, item in enumerate(self._want):
|
||||||
|
if item['name'] in intf_list:
|
||||||
|
intf_params.append(item)
|
||||||
|
if not item['enabled']:
|
||||||
|
shut_params.append(item)
|
||||||
|
if item['name'] in shut_list and item['enabled']:
|
||||||
|
noshut_params.append(item)
|
||||||
|
|
||||||
|
opcode = None
|
||||||
|
if state == 'absent':
|
||||||
|
if intf_params:
|
||||||
|
opcode = "delete"
|
||||||
elif state in ('present', 'up', 'down'):
|
elif state in ('present', 'up', 'down'):
|
||||||
if obj_in_have:
|
intf_params = self._want
|
||||||
for item in args:
|
opcode = 'merge'
|
||||||
candidate = w.get(item)
|
|
||||||
running = obj_in_have.get(item)
|
|
||||||
if candidate != running:
|
|
||||||
if candidate:
|
|
||||||
cmd = interface + ' ' + item + ' ' + str(candidate)
|
|
||||||
commands.append(cmd)
|
|
||||||
|
|
||||||
if disable and not obj_in_have.get('disable', False):
|
self._result['xml'] = []
|
||||||
commands.append(interface + ' shutdown')
|
_edit_filter_list = list()
|
||||||
elif not disable and obj_in_have.get('disable', False):
|
if opcode:
|
||||||
commands.append('no ' + interface + ' shutdown')
|
_edit_filter_list.append(build_xml('interface-configurations', xmap=self._intf_meta,
|
||||||
else:
|
params=intf_params, opcode=opcode))
|
||||||
for item in args:
|
|
||||||
value = w.get(item)
|
|
||||||
if value:
|
|
||||||
commands.append(interface + ' ' + item + ' ' + str(value))
|
|
||||||
if disable:
|
|
||||||
commands.append('no ' + interface + ' shutdown')
|
|
||||||
return commands
|
|
||||||
|
|
||||||
|
if opcode == 'merge':
|
||||||
|
if len(shut_params):
|
||||||
|
_edit_filter_list.append(build_xml('interface-configurations', xmap=self._shut_meta,
|
||||||
|
params=shut_params, opcode='merge'))
|
||||||
|
if len(noshut_params):
|
||||||
|
_edit_filter_list.append(build_xml('interface-configurations', xmap=self._shut_meta,
|
||||||
|
params=noshut_params, opcode='delete'))
|
||||||
|
diff = None
|
||||||
|
if len(_edit_filter_list):
|
||||||
|
commit = not self._module.check_mode
|
||||||
|
diff = load_config(self._module, _edit_filter_list, commit=commit, running=running,
|
||||||
|
nc_get_filter=_get_filter)
|
||||||
|
|
||||||
def check_declarative_intent_params(module, want, result):
|
if diff:
|
||||||
failed_conditions = []
|
if self._module._diff:
|
||||||
for w in want:
|
self._result['diff'] = dict(prepared=diff)
|
||||||
want_state = w.get('state')
|
|
||||||
want_tx_rate = w.get('tx_rate')
|
|
||||||
want_rx_rate = w.get('rx_rate')
|
|
||||||
if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if result['changed']:
|
self._result['xml'] = _edit_filter_list
|
||||||
sleep(w['delay'])
|
self._result['changed'] = True
|
||||||
|
|
||||||
command = 'show interfaces %s' % w['name']
|
def check_declarative_intent_params(self):
|
||||||
rc, out, err = exec_command(module, command)
|
failed_conditions = []
|
||||||
if rc != 0:
|
|
||||||
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
|
|
||||||
|
|
||||||
if want_state in ('up', 'down'):
|
self._data_rate_meta.update([
|
||||||
match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M)
|
('interfaces', {'xpath': 'infra-statistics/interfaces', 'tag': True}),
|
||||||
have_state = None
|
('interface', {'xpath': 'infra-statistics/interfaces/interface', 'tag': True}),
|
||||||
if match:
|
('a:name', {'xpath': 'infra-statistics/interfaces/interface/interface-name'}),
|
||||||
have_state = match.group(1)
|
('cache', {'xpath': 'infra-statistics/interfaces/interface/cache', 'tag': True}),
|
||||||
if have_state.strip() == 'administratively':
|
('data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate', 'tag': True}),
|
||||||
match = re.search(r'%s (\w+)' % 'administratively', out, re.M)
|
('input-data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate/input-data-rate', 'tag': True}),
|
||||||
if match:
|
('output-data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate/output-data-rate', 'tag': True}),
|
||||||
have_state = match.group(1)
|
])
|
||||||
|
|
||||||
if have_state is None or not conditional(want_state, have_state.strip()):
|
self._line_state_meta.update([
|
||||||
failed_conditions.append('state ' + 'eq(%s)' % want_state)
|
('data-nodes', {'xpath': 'interface-properties/data-nodes', 'tag': True}),
|
||||||
|
('data-node', {'xpath': 'interface-properties/data-nodes/data-node', 'tag': True}),
|
||||||
|
('system-view', {'xpath': 'interface-properties/data-nodes/data-node/system-view', 'tag': True}),
|
||||||
|
('interfaces', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces', 'tag': True}),
|
||||||
|
('interface', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces/interface', 'tag': True}),
|
||||||
|
('a:name', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces/interface/interface-name'}),
|
||||||
|
('line-state', {'xpath': 'interface-properties/data-nodes/data-node/system-view/interfaces/interface/line-state', 'tag': True}),
|
||||||
|
])
|
||||||
|
|
||||||
if want_tx_rate:
|
_rate_filter = build_xml('infra-statistics', xmap=self._data_rate_meta, params=self._want, opcode="filter")
|
||||||
match = re.search(r'%s (\d+)' % 'output rate', out, re.M)
|
out = get_oper(self._module, filter=_rate_filter)
|
||||||
have_tx_rate = None
|
data_rate_list = etree_findall(out, 'interface')
|
||||||
if match:
|
data_rate_map = dict()
|
||||||
have_tx_rate = match.group(1)
|
for item in data_rate_list:
|
||||||
|
data_rate_map.update({etree_find(item, 'interface-name').text: dict()})
|
||||||
|
data_rate_map[etree_find(item, 'interface-name').text].update({'input-data-rate': etree_find(item, 'input-data-rate').text,
|
||||||
|
'output-data-rate': etree_find(item, 'output-data-rate').text})
|
||||||
|
|
||||||
if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int):
|
_line_state_filter = build_xml('interface-properties', xmap=self._line_state_meta, params=self._want, opcode="filter")
|
||||||
failed_conditions.append('tx_rate ' + want_tx_rate)
|
out = get_oper(self._module, filter=_line_state_filter)
|
||||||
|
line_state_list = etree_findall(out, 'interface')
|
||||||
|
line_state_map = dict()
|
||||||
|
for item in line_state_list:
|
||||||
|
line_state_map.update({etree_find(item, 'interface-name').text: etree_find(item, 'line-state').text})
|
||||||
|
|
||||||
if want_rx_rate:
|
for want_item in self._want:
|
||||||
match = re.search(r'%s (\d+)' % 'input rate', out, re.M)
|
want_state = want_item.get('state')
|
||||||
have_rx_rate = None
|
want_tx_rate = want_item.get('tx_rate')
|
||||||
if match:
|
want_rx_rate = want_item.get('rx_rate')
|
||||||
have_rx_rate = match.group(1)
|
if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate:
|
||||||
|
continue
|
||||||
|
|
||||||
if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int):
|
if self._result['changed']:
|
||||||
failed_conditions.append('rx_rate ' + want_rx_rate)
|
sleep(want_item['delay'])
|
||||||
|
|
||||||
return failed_conditions
|
if want_state in ('up', 'down'):
|
||||||
|
if want_state not in line_state_map[want_item['name']]:
|
||||||
|
failed_conditions.append('state ' + 'eq({!s})'.format(want_state))
|
||||||
|
|
||||||
|
if want_tx_rate:
|
||||||
|
if want_tx_rate != data_rate_map[want_item['name']]['output-data-rate']:
|
||||||
|
failed_conditions.append('tx_rate ' + want_tx_rate)
|
||||||
|
|
||||||
|
if want_rx_rate:
|
||||||
|
if want_rx_rate != data_rate_map[want_item['name']]['input-data-rate']:
|
||||||
|
failed_conditions.append('rx_rate ' + want_rx_rate)
|
||||||
|
|
||||||
|
if failed_conditions:
|
||||||
|
msg = 'One or more conditional statements have not been satisfied'
|
||||||
|
self._module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.map_params_to_obj()
|
||||||
|
self.map_obj_to_xml_rpc()
|
||||||
|
self.check_declarative_intent_params()
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" main entry point for module execution
|
""" main entry point for module execution
|
||||||
"""
|
"""
|
||||||
element_spec = dict(
|
element_spec = dict(
|
||||||
name=dict(),
|
name=dict(type='str'),
|
||||||
description=dict(),
|
description=dict(type='str'),
|
||||||
speed=dict(),
|
speed=dict(choices=['10', '100', '1000']),
|
||||||
mtu=dict(),
|
mtu=dict(),
|
||||||
duplex=dict(choices=['full', 'half']),
|
duplex=dict(choices=['full', 'half']),
|
||||||
enabled=dict(default=True, type='bool'),
|
enabled=dict(default=True, type='bool'),
|
||||||
|
active=dict(default='active', type='str', choices=['active', 'preconfigure']),
|
||||||
tx_rate=dict(),
|
tx_rate=dict(),
|
||||||
rx_rate=dict(),
|
rx_rate=dict(),
|
||||||
delay=dict(default=10, type='int'),
|
delay=dict(default=10, type='int'),
|
||||||
|
@ -387,32 +627,21 @@ def main():
|
||||||
mutually_exclusive=mutually_exclusive,
|
mutually_exclusive=mutually_exclusive,
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
warnings = list()
|
config_object = None
|
||||||
|
if is_cliconf(module):
|
||||||
result = {'changed': False}
|
module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
|
||||||
|
version='4 releases from v2.5')
|
||||||
want = map_params_to_obj(module)
|
config_object = CliConfiguration(module)
|
||||||
have = map_config_to_obj(module)
|
elif is_netconf(module):
|
||||||
|
if module.params['active'] == 'preconfigure':
|
||||||
commands = map_obj_to_commands((want, have))
|
module.fail_json(msg="Physical interface pre-configuration is not supported with transport 'netconf'")
|
||||||
|
config_object = NCConfiguration(module)
|
||||||
result['commands'] = commands
|
|
||||||
result['warnings'] = warnings
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
commit = not module.check_mode
|
|
||||||
diff = load_config(module, commands, commit=commit)
|
|
||||||
if diff:
|
|
||||||
result['diff'] = dict(prepared=diff)
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
failed_conditions = check_declarative_intent_params(module, want, result)
|
|
||||||
|
|
||||||
if failed_conditions:
|
|
||||||
msg = 'One or more conditional statements have not been satisfied'
|
|
||||||
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
if config_object:
|
||||||
|
result = config_object.run()
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- "result.changed == true"
|
||||||
- "'this is my login banner' in result.commands"
|
- "'this is my login banner' in result.xml"
|
||||||
- "'that has a multiline' in result.commands"
|
- "'that has a multiline' in result.xml"
|
||||||
|
|
||||||
- name: Set login again (idempotent)
|
- name: Set login again (idempotent)
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
|
@ -46,4 +46,4 @@
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == false"
|
- "result.changed == false"
|
||||||
- "result.commands | length == 0"
|
- "result.xml | length == 0"
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- "result.changed == true"
|
||||||
- "'this is my motd banner' in result.commands"
|
- "'this is my motd banner' in result.xml"
|
||||||
- "'that has a multiline' in result.commands"
|
- "'that has a multiline' in result.xml"
|
||||||
|
|
||||||
- name: Set motd again (idempotent)
|
- name: Set motd again (idempotent)
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
|
@ -46,4 +46,4 @@
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == false"
|
- "result.changed == false"
|
||||||
- "result.commands | length == 0"
|
- "result.xml | length == 0"
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- "result.changed == true"
|
||||||
- "'xc:operation=\"delete\"' in result.commands"
|
- "'xc:operation=\"delete\"' in result.xml"
|
||||||
|
|
||||||
- name: remove login (idempotent)
|
- name: remove login (idempotent)
|
||||||
iosxr_banner:
|
iosxr_banner:
|
||||||
|
@ -40,4 +40,4 @@
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == false"
|
- "result.changed == false"
|
||||||
- "result.commands | length == 0"
|
- "result.xml | length == 0"
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
---
|
---
|
||||||
- { include: cli.yaml, tags: ['cli'] }
|
- { include: cli.yaml, tags: ['cli'] }
|
||||||
|
- { include: netconf.yaml, tags: ['netconf'] }
|
||||||
|
|
16
test/integration/targets/iosxr_interface/tasks/netconf.yaml
Normal file
16
test/integration/targets/iosxr_interface/tasks/netconf.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
- name: collect all netconf test cases
|
||||||
|
find:
|
||||||
|
paths: "{{ role_path }}/tests/netconf"
|
||||||
|
patterns: "{{ testcase }}.yaml"
|
||||||
|
register: test_cases
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: set test_items
|
||||||
|
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||||
|
|
||||||
|
- name: run test case
|
||||||
|
include: "{{ test_case_to_run }}"
|
||||||
|
with_items: "{{ test_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: test_case_to_run
|
|
@ -0,0 +1,281 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START iosxr_interface netconf/basic.yaml"
|
||||||
|
|
||||||
|
- name: Enable Netconf service
|
||||||
|
iosxr_netconf:
|
||||||
|
netconf_port: 830
|
||||||
|
netconf_vrf: 'default'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Setup interface
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
|
||||||
|
- name: Confgure interface
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
description: test-interface-initial
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
- '"GigabitEthernet0/0/0/1" in result.xml[0]'
|
||||||
|
|
||||||
|
- name: Confgure interface (idempotent)
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
description: test-interface-initial
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == false'
|
||||||
|
|
||||||
|
- name: Confgure interface parameters
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
description: test-interface
|
||||||
|
speed: 100
|
||||||
|
duplex: half
|
||||||
|
mtu: 512
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
- '"GigabitEthernet0/0/0/1" in result.xml[0]'
|
||||||
|
- '"test-interface" in result.xml[0]'
|
||||||
|
- '"100" in result.xml[0]'
|
||||||
|
- '"512" in result.xml[0]'
|
||||||
|
|
||||||
|
- name: Change interface parameters
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
description: test-interface-1
|
||||||
|
speed: 10
|
||||||
|
duplex: full
|
||||||
|
mtu: 256
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
- '"GigabitEthernet0/0/0/1" in result.xml[0]'
|
||||||
|
- '"test-interface-1" in result.xml[0]'
|
||||||
|
- '"10" in result.xml[0]'
|
||||||
|
- '"256" in result.xml[0]'
|
||||||
|
|
||||||
|
- name: Change interface parameters (idempotent)
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
description: test-interface-1
|
||||||
|
speed: 10
|
||||||
|
duplex: full
|
||||||
|
mtu: 256
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == false'
|
||||||
|
|
||||||
|
- name: Disable interface
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: False
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
|
||||||
|
- name: Enable interface
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: True
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
|
||||||
|
- name: Confgure second interface (setup)
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/0
|
||||||
|
description: test-interface-initial
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
- '"GigabitEthernet0/0/0/0" in result.xml[0]'
|
||||||
|
|
||||||
|
- name: Delete interface aggregate (setup)
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
|
||||||
|
- name: Add interface aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- { name: GigabitEthernet0/0/0/0, mtu: 256, description: test-interface-1 }
|
||||||
|
- { name: GigabitEthernet0/0/0/1, mtu: 516, description: test-interface-2 }
|
||||||
|
speed: 100
|
||||||
|
duplex: full
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
- '"GigabitEthernet0/0/0/1" in result.xml[0]'
|
||||||
|
- '"GigabitEthernet0/0/0/0" in result.xml[0]'
|
||||||
|
|
||||||
|
- name: Add interface aggregate (idempotent)
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- { name: GigabitEthernet0/0/0/0, mtu: 256, description: test-interface-1 }
|
||||||
|
- { name: GigabitEthernet0/0/0/1, mtu: 516, description: test-interface-2 }
|
||||||
|
speed: 100
|
||||||
|
duplex: full
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == false'
|
||||||
|
|
||||||
|
- name: Disable interface aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: False
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
|
||||||
|
- name: Disable interface aggregate (idempotent)
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: False
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == false'
|
||||||
|
|
||||||
|
- name: Enable interface aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: True
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
|
||||||
|
- name: Enable interface aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: True
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == false'
|
||||||
|
|
||||||
|
- name: interface aggregate (setup)
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
description: test-interface-initial
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Create interface aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
description: test_interface_1
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
description: test_interface_2
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
- '"GigabitEthernet0/0/0/0" in result.xml[0]'
|
||||||
|
- '"GigabitEthernet0/0/0/1" in result.xml[0]'
|
||||||
|
- '"test_interface_1" in result.xml[0]'
|
||||||
|
- '"test_interface_2" in result.xml[0]'
|
||||||
|
|
||||||
|
- name: Delete interface aggregate
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == true'
|
||||||
|
|
||||||
|
- name: Delete interface aggregate (idempotent)
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/0
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
state: absent
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed == false'
|
||||||
|
|
||||||
|
- debug: msg="END iosxr_interface netconf/basic.yaml"
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START iosxr_interface netconf/intent.yaml"
|
||||||
|
|
||||||
|
- name: Setup (interface is up)
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
description: test_interface_1
|
||||||
|
enabled: True
|
||||||
|
state: present
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Check intent arguments
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
state: up
|
||||||
|
delay: 10
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == false"
|
||||||
|
|
||||||
|
- name: Check intent arguments (failed condition)
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
state: down
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == true"
|
||||||
|
- "'state eq(down)' in result.failed_conditions"
|
||||||
|
|
||||||
|
- name: Config + intent
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: False
|
||||||
|
state: down
|
||||||
|
delay: 10
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == false"
|
||||||
|
|
||||||
|
- name: Config + intent (fail)
|
||||||
|
iosxr_interface:
|
||||||
|
name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: False
|
||||||
|
state: up
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == true"
|
||||||
|
- "'state eq(up)' in result.failed_conditions"
|
||||||
|
|
||||||
|
- name: Aggregate config + intent (pass)
|
||||||
|
iosxr_interface:
|
||||||
|
aggregate:
|
||||||
|
- name: GigabitEthernet0/0/0/1
|
||||||
|
enabled: True
|
||||||
|
state: up
|
||||||
|
delay: 10
|
||||||
|
provider: "{{ netconf }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == false"
|
Loading…
Reference in a new issue