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 = [] 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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +1,3 @@
--- ---
- { include: cli.yaml, tags: ['cli'] } - { 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"