From 78a14d79669535fb34e260810401fe6e4360ce76 Mon Sep 17 00:00:00 2001
From: Kedar Kekan <4506537+kedarX@users.noreply.github.com>
Date: Wed, 20 Dec 2017 13:06:07 +0530
Subject: [PATCH] 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
---
.../module_utils/network/common/netconf.py | 6 +-
.../module_utils/network/iosxr/iosxr.py | 30 +-
.../modules/network/iosxr/iosxr_banner.py | 37 +-
.../modules/network/iosxr/iosxr_interface.py | 637 ++++++++++++------
.../tests/netconf/basic-login.yaml | 6 +-
.../tests/netconf/basic-motd.yaml | 6 +-
.../tests/netconf/basic-no-login.yaml | 4 +-
.../targets/iosxr_interface/tasks/main.yaml | 1 +
.../iosxr_interface/tasks/netconf.yaml | 16 +
.../iosxr_interface/tests/netconf/basic.yaml | 281 ++++++++
.../iosxr_interface/tests/netconf/intent.yaml | 78 +++
11 files changed, 875 insertions(+), 227 deletions(-)
create mode 100644 test/integration/targets/iosxr_interface/tasks/netconf.yaml
create mode 100644 test/integration/targets/iosxr_interface/tests/netconf/basic.yaml
create mode 100644 test/integration/targets/iosxr_interface/tests/netconf/intent.yaml
diff --git a/lib/ansible/module_utils/network/common/netconf.py b/lib/ansible/module_utils/network/common/netconf.py
index 218bedc79f9..234d125830e 100644
--- a/lib/ansible/module_utils/network/common/netconf.py
+++ b/lib/ansible/module_utils/network/common/netconf.py
@@ -77,7 +77,11 @@ class NetconfConnection(Connection):
warnings = []
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
if severity == 'warning' and self.ignore_warning:
diff --git a/lib/ansible/module_utils/network/iosxr/iosxr.py b/lib/ansible/module_utils/network/iosxr/iosxr.py
index 805acf85129..3946c07c07c 100644
--- a/lib/ansible/module_utils/network/iosxr/iosxr.py
+++ b/lib/ansible/module_utils/network/iosxr/iosxr.py
@@ -62,7 +62,9 @@ NS_DICT = {
'M:TYPE_NSMAP': {"idx": "urn:ietf:params:xml:ns:yang:iana-if-type"},
'ETHERNET_NSMAP': {None: "http://openconfig.net/yang/interfaces/ethernet"},
'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 = {
@@ -253,7 +255,11 @@ def build_xml(container, xmap=None, params=None, opcode=None):
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:
return element
@@ -261,7 +267,11 @@ def etree_find(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:
return element
@@ -336,6 +346,17 @@ def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, p
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'):
global _DEVICE_CONFIGS
@@ -370,7 +391,8 @@ def load_config(module, command_filter, commit=False, replace=False,
# conn.discard_changes()
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)
diff = get_config_diff(module, running, candidate)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_banner.py b/lib/ansible/modules/network/iosxr/iosxr_banner.py
index 108e2b66efb..8e9f81999b8 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_banner.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_banner.py
@@ -68,14 +68,29 @@ EXAMPLES = """
RETURN = """
commands:
- description: The list of configuration mode commands to send to the device
- returned: always
+ description: The list of configuration mode commands sent to device with transport C(cli)
+ returned: always (empty list when no commands to send)
type: list
sample:
- banner login
- this is my login banner
- that contains a multiline
- 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:
+ - '
+
+
+ motd
+ Ansible banner example
+
+
+ '
"""
import re
@@ -83,9 +98,9 @@ import collections
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 iosxr_argument_spec, discard_config
-from ansible.module_utils.network.iosxr.iosxr import build_xml, is_cliconf, is_netconf
-from ansible.module_utils.network.iosxr.iosxr import etree_find
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
+from ansible.module_utils.network.iosxr.iosxr import build_xml, is_cliconf
+from ansible.module_utils.network.iosxr.iosxr import etree_find, is_netconf
class ConfigBase(object):
@@ -162,7 +177,7 @@ class NCConfiguration(ConfigBase):
('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']
_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':
opcode = 'merge'
- self._result['commands'] = []
+ self._result['xml'] = []
if 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)
if diff:
- self._result['commands'] = _edit_filter
+ self._result['xml'] = _edit_filter
if self._module._diff:
self._result['diff'] = dict(prepared=diff)
@@ -197,7 +212,7 @@ class NCConfiguration(ConfigBase):
def run(self):
self.map_params_to_obj()
- self.map_obj_to_commands()
+ self.map_obj_to_xml_rpc()
return self._result
@@ -219,8 +234,10 @@ def main():
required_if=required_if,
supports_check_mode=True)
+ config_object = None
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)
elif is_netconf(module):
config_object = NCConfiguration(module)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_interface.py b/lib/ansible/modules/network/iosxr/iosxr_interface.py
index 14832fe3c24..4fdcae00b94 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_interface.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_interface.py
@@ -17,34 +17,49 @@ DOCUMENTATION = """
---
module: iosxr_interface
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
description:
- This module provides declarative management of Interfaces
on Cisco IOS XR network devices.
extends_documentation_fragment: iosxr
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:
name:
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
description:
description:
- - Description of Interface.
+ - Description of Interface being configured.
enabled:
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:
description:
- - Interface link speed.
+ - Configure the speed for an interface. Default is auto-negotiation when not configured.
+ choices: ['10', '100', '1000']
mtu:
description:
- - Maximum size of transmit packet.
+ - Sets the MTU value for the interface. Range is between 64 and 65535'
duplex:
description:
- - Interface link status
+ - Configures the interface duplex mode. Default is auto-negotiation when not configured.
choices: ['full', 'half']
tx_rate:
description:
@@ -53,7 +68,9 @@ options:
description:
- Receiver rate in bits per second (bps).
aggregate:
- description: List of Interfaces definitions.
+ description:
+ - List of Interface definitions. Include multiple interface configurations together,
+ one each on a seperate line
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
@@ -102,6 +119,16 @@ EXAMPLES = """
mtu: 512
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
iosxr_interface:
aggregate:
@@ -125,240 +152,453 @@ EXAMPLES = """
RETURN = """
commands:
- description: The list of configuration mode commands to send to the device.
- returned: always, except for the platforms that use Netconf transport to manage the device.
+ description: The list of configuration mode commands sent to device with transport C(cli)
+ returned: always (empty list when no commands to send)
type: list
sample:
- interface GigabitEthernet0/0/0/2
- description test-interface
- duplex half
- 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:
+ - '
+
+
+ act
+ GigabitEthernet0/0/0/0
+ test-interface-0
+
+ GigabitEthernet
+ 512
+
+
+ 100
+ half
+
+
+ '
"""
import re
-
from time import sleep
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.connection import exec_command
-from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
+from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, build_xml
+from ansible.module_utils.network.iosxr.iosxr import run_command, iosxr_argument_spec, get_oper
+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
-def validate_mtu(value, module):
+def validate_mtu(value):
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):
- if param is None:
- param = module.params
- for key in obj:
- # validate the param value (if validator func exists)
- validator = globals().get('validate_%s' % key)
- if callable(validator):
- validator(param.get(key), module)
+class ConfigBase(object):
+ def __init__(self, module):
+ self._module = module
+ self._result = {'changed': False, 'warnings': []}
+ self._want = list()
+ self._have = list()
+ 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):
- for cfg in intf_config:
- match = re.search(r'%s' % 'shutdown', cfg, re.M)
- if match:
- return True
- return False
+ def map_params_to_obj(self):
+ aggregate = self._module.params.get('aggregate')
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ 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):
- for cfg in intf_config:
- match = re.search(r'%s (.+)$' % arg, cfg, re.M)
- if match:
- return match.group(1)
+ match = re.match(r"(^[a-z]+)([0-9/]+$)", d['name'], re.I)
+ if match:
+ d['owner'] = match.groups()[0]
+ if d['active'] == 'preconfigure':
+ d['active'] = 'pre'
+ else:
+ d['active'] = 'act'
-def search_obj_in_list(name, lst):
- for o in lst:
- if o['name'] == name:
- return o
+ self._want.append(d)
- 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:
- 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)
- return obj
+ match = re.match(r"(^[a-z]+)([0-9/]+$)", params['name'], re.I)
+ 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):
- data = get_config(module, config_filter='interface')
- interfaces = data.strip().rstrip('!').split('!')
+class CliConfiguration(ConfigBase):
+ def __init__(self, module):
+ super(CliConfiguration, self).__init__(module)
- if not interfaces:
- return list()
+ def parse_shutdown(self, intf_config):
+ 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:
- intf_config = interface.strip().splitlines()
+ def search_obj_in_list(self, name):
+ 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':
- name = intf_config[0].strip().split()[2]
+ if not interfaces:
+ return list()
- obj = {
- 'name': name,
- 'description': parse_config_argument(intf_config, 'description'),
- 'speed': parse_config_argument(intf_config, 'speed'),
- 'duplex': parse_config_argument(intf_config, 'duplex'),
- 'mtu': parse_config_argument(intf_config, 'mtu'),
- 'disable': True if parse_shutdown(intf_config) else False,
- 'state': 'present'
- }
- instances.append(obj)
- return instances
+ for interface in interfaces:
+ intf_config = interface.strip().splitlines()
+
+ name = intf_config[0].strip().split()[1]
+
+ active = 'act'
+ if name == 'preconfigure':
+ active = 'pre'
+ name = intf_config[0].strip().split()[2]
+
+ 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):
- commands = list()
- want, have = updates
+class NCConfiguration(ConfigBase):
+ def __init__(self, module):
+ super(NCConfiguration, self).__init__(module)
- args = ('speed', 'description', 'duplex', 'mtu')
- for w in want:
- name = w['name']
- disable = w['disable']
- state = w['state']
+ self._intf_meta = collections.OrderedDict()
+ self._shut_meta = collections.OrderedDict()
+ self._data_rate_meta = collections.OrderedDict()
+ self._line_state_meta = collections.OrderedDict()
- obj_in_have = search_obj_in_list(name, have)
- interface = 'interface ' + name
+ def map_obj_to_xml_rpc(self):
+ 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:
- commands.append('no ' + interface)
+ self._shut_meta.update([
+ ('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'):
- if obj_in_have:
- for item in args:
- candidate = w.get(item)
- running = obj_in_have.get(item)
- if candidate != running:
- if candidate:
- cmd = interface + ' ' + item + ' ' + str(candidate)
- commands.append(cmd)
+ intf_params = self._want
+ opcode = 'merge'
- if disable and not obj_in_have.get('disable', False):
- commands.append(interface + ' shutdown')
- elif not disable and obj_in_have.get('disable', False):
- commands.append('no ' + interface + ' shutdown')
- else:
- 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
+ self._result['xml'] = []
+ _edit_filter_list = list()
+ if opcode:
+ _edit_filter_list.append(build_xml('interface-configurations', xmap=self._intf_meta,
+ params=intf_params, opcode=opcode))
+ 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):
- failed_conditions = []
- for w in want:
- 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 diff:
+ if self._module._diff:
+ self._result['diff'] = dict(prepared=diff)
- if result['changed']:
- sleep(w['delay'])
+ self._result['xml'] = _edit_filter_list
+ self._result['changed'] = True
- command = 'show interfaces %s' % w['name']
- rc, out, err = exec_command(module, command)
- if rc != 0:
- module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
+ def check_declarative_intent_params(self):
+ failed_conditions = []
- 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)
+ self._data_rate_meta.update([
+ ('interfaces', {'xpath': 'infra-statistics/interfaces', 'tag': True}),
+ ('interface', {'xpath': 'infra-statistics/interfaces/interface', 'tag': True}),
+ ('a:name', {'xpath': 'infra-statistics/interfaces/interface/interface-name'}),
+ ('cache', {'xpath': 'infra-statistics/interfaces/interface/cache', 'tag': True}),
+ ('data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate', 'tag': True}),
+ ('input-data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate/input-data-rate', 'tag': True}),
+ ('output-data-rate', {'xpath': 'infra-statistics/interfaces/interface/cache/data-rate/output-data-rate', 'tag': True}),
+ ])
- if have_state is None or not conditional(want_state, have_state.strip()):
- failed_conditions.append('state ' + 'eq(%s)' % want_state)
+ self._line_state_meta.update([
+ ('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:
- match = re.search(r'%s (\d+)' % 'output rate', out, re.M)
- have_tx_rate = None
- if match:
- have_tx_rate = match.group(1)
+ _rate_filter = build_xml('infra-statistics', xmap=self._data_rate_meta, params=self._want, opcode="filter")
+ out = get_oper(self._module, filter=_rate_filter)
+ data_rate_list = etree_findall(out, 'interface')
+ data_rate_map = dict()
+ 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):
- failed_conditions.append('tx_rate ' + want_tx_rate)
+ _line_state_filter = build_xml('interface-properties', xmap=self._line_state_meta, params=self._want, opcode="filter")
+ 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:
- match = re.search(r'%s (\d+)' % 'input rate', out, re.M)
- have_rx_rate = None
- if match:
- have_rx_rate = match.group(1)
+ 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 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 self._result['changed']:
+ 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():
""" main entry point for module execution
"""
element_spec = dict(
- name=dict(),
- description=dict(),
- speed=dict(),
+ name=dict(type='str'),
+ description=dict(type='str'),
+ speed=dict(choices=['10', '100', '1000']),
mtu=dict(),
duplex=dict(choices=['full', 'half']),
enabled=dict(default=True, type='bool'),
+ active=dict(default='active', type='str', choices=['active', 'preconfigure']),
tx_rate=dict(),
rx_rate=dict(),
delay=dict(default=10, type='int'),
@@ -387,32 +627,21 @@ def main():
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
- warnings = list()
-
- result = {'changed': False}
-
- want = map_params_to_obj(module)
- have = map_config_to_obj(module)
-
- commands = map_obj_to_commands((want, have))
-
- 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)
+ config_object = None
+ if is_cliconf(module):
+ module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
+ version='4 releases from v2.5')
+ config_object = CliConfiguration(module)
+ elif is_netconf(module):
+ if module.params['active'] == 'preconfigure':
+ module.fail_json(msg="Physical interface pre-configuration is not supported with transport 'netconf'")
+ config_object = NCConfiguration(module)
+ result = {}
+ if config_object:
+ result = config_object.run()
module.exit_json(**result)
+
if __name__ == '__main__':
main()
diff --git a/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml b/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml
index cb78654e9dc..3c0518ee388 100644
--- a/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml
+++ b/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml
@@ -29,8 +29,8 @@
- assert:
that:
- "result.changed == true"
- - "'this is my login banner' in result.commands"
- - "'that has a multiline' in result.commands"
+ - "'this is my login banner' in result.xml"
+ - "'that has a multiline' in result.xml"
- name: Set login again (idempotent)
iosxr_banner:
@@ -46,4 +46,4 @@
- assert:
that:
- "result.changed == false"
- - "result.commands | length == 0"
+ - "result.xml | length == 0"
diff --git a/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml b/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml
index b50147c9750..241910ad6bc 100644
--- a/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml
+++ b/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml
@@ -29,8 +29,8 @@
- assert:
that:
- "result.changed == true"
- - "'this is my motd banner' in result.commands"
- - "'that has a multiline' in result.commands"
+ - "'this is my motd banner' in result.xml"
+ - "'that has a multiline' in result.xml"
- name: Set motd again (idempotent)
iosxr_banner:
@@ -46,4 +46,4 @@
- assert:
that:
- "result.changed == false"
- - "result.commands | length == 0"
+ - "result.xml | length == 0"
diff --git a/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml b/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml
index e74a0e628d4..2601bf1f445 100644
--- a/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml
+++ b/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml
@@ -28,7 +28,7 @@
- assert:
that:
- "result.changed == true"
- - "'xc:operation=\"delete\"' in result.commands"
+ - "'xc:operation=\"delete\"' in result.xml"
- name: remove login (idempotent)
iosxr_banner:
@@ -40,4 +40,4 @@
- assert:
that:
- "result.changed == false"
- - "result.commands | length == 0"
+ - "result.xml | length == 0"
diff --git a/test/integration/targets/iosxr_interface/tasks/main.yaml b/test/integration/targets/iosxr_interface/tasks/main.yaml
index 415c99d8b12..af08869c922 100644
--- a/test/integration/targets/iosxr_interface/tasks/main.yaml
+++ b/test/integration/targets/iosxr_interface/tasks/main.yaml
@@ -1,2 +1,3 @@
---
- { include: cli.yaml, tags: ['cli'] }
+- { include: netconf.yaml, tags: ['netconf'] }
diff --git a/test/integration/targets/iosxr_interface/tasks/netconf.yaml b/test/integration/targets/iosxr_interface/tasks/netconf.yaml
new file mode 100644
index 00000000000..1286b354228
--- /dev/null
+++ b/test/integration/targets/iosxr_interface/tasks/netconf.yaml
@@ -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
diff --git a/test/integration/targets/iosxr_interface/tests/netconf/basic.yaml b/test/integration/targets/iosxr_interface/tests/netconf/basic.yaml
new file mode 100644
index 00000000000..cbda5e16162
--- /dev/null
+++ b/test/integration/targets/iosxr_interface/tests/netconf/basic.yaml
@@ -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"
diff --git a/test/integration/targets/iosxr_interface/tests/netconf/intent.yaml b/test/integration/targets/iosxr_interface/tests/netconf/intent.yaml
new file mode 100644
index 00000000000..5d8d07f8889
--- /dev/null
+++ b/test/integration/targets/iosxr_interface/tests/netconf/intent.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"