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:
Kedar Kekan 2017-12-20 13:06:07 +05:30 committed by GitHub
parent 8a9865cb10
commit 78a14d7966
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 875 additions and 227 deletions

View file

@ -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:

View file

@ -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)

View file

@ -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:
- '<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
@ -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)

View file

@ -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:
- '<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
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()

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -1,2 +1,3 @@
---
- { include: cli.yaml, tags: ['cli'] }
- { include: netconf.yaml, tags: ['netconf'] }

View 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

View file

@ -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"

View file

@ -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"