mirror of https://github.com/ansible/ansible.git
Migrated to cisco.iosxr
parent
7e04b5ba8b
commit
5aa37733c3
@ -1,88 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_acl_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class Acl_interfacesArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_acl_interfaces module
|
|
||||||
"""
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'running_config': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'config': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'access_groups': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'acls': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'direction': {
|
|
||||||
'choices': ['in', 'out'],
|
|
||||||
'type': 'str',
|
|
||||||
'required': True
|
|
||||||
},
|
|
||||||
'name': {
|
|
||||||
'type': 'str',
|
|
||||||
'required': True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'afi': {
|
|
||||||
'choices': ['ipv4', 'ipv6'],
|
|
||||||
'type': 'str',
|
|
||||||
'required': True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'name': {
|
|
||||||
'type': 'str',
|
|
||||||
'required': True
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': [
|
|
||||||
'merged', 'replaced', 'overridden', 'deleted', 'gathered',
|
|
||||||
'parsed', 'rendered'
|
|
||||||
],
|
|
||||||
'default':
|
|
||||||
'merged',
|
|
||||||
'type':
|
|
||||||
'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,644 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_acls module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class AclsArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_acls module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'running_config': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'config': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'acls': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'name': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'aces': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'mutually_exclusive': [['grant', 'remark', 'line']],
|
|
||||||
'options': {
|
|
||||||
'destination': {
|
|
||||||
'mutually_exclusive': [['address', 'any', 'host', 'prefix'], ['wildcard_bits', 'any', 'host', 'prefix']],
|
|
||||||
'options': {
|
|
||||||
'host': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'address': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'any': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'prefix': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'port_protocol': {
|
|
||||||
'mutually_exclusive': [['eq', 'gt', 'lt', 'neq', 'range']],
|
|
||||||
'options': {
|
|
||||||
'eq': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'gt': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'lt': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'neq': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'range': {
|
|
||||||
'options': {
|
|
||||||
'end': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'required_together': [['start', 'end']],
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'wildcard_bits': {
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'required_together': [['address', 'wildcard_bits']],
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'dscp': {
|
|
||||||
'mutually_exclusive': [['eq', 'gt', 'lt', 'neq', 'range']],
|
|
||||||
'type': 'dict',
|
|
||||||
'options': {
|
|
||||||
'eq': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'gt': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'lt': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'neq': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'range': {
|
|
||||||
'options': {
|
|
||||||
'end': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'required_together': [['start', 'end']],
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'fragments': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'capture': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'destopts': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'authen': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'routing': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'hop_by_hop': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'grant': {
|
|
||||||
'type': 'str',
|
|
||||||
'choices': ['permit', 'deny'],
|
|
||||||
},
|
|
||||||
'icmp_off': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'log': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'log_input': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'line': {
|
|
||||||
'type': 'str',
|
|
||||||
'aliases': ['ace']
|
|
||||||
},
|
|
||||||
'packet_length': {
|
|
||||||
'mutually_exclusive': [['eq', 'lt', 'neq', 'range'], ['eq', 'gt', 'neq', 'range']],
|
|
||||||
'options': {
|
|
||||||
'eq': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'gt': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'lt': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'neq': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'range': {
|
|
||||||
'options': {
|
|
||||||
'end': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'type': 'int'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type':
|
|
||||||
'dict'
|
|
||||||
},
|
|
||||||
'precedence': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'protocol': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'protocol_options': {
|
|
||||||
'mutually_exclusive': [['icmp', 'tcp', 'igmp', 'icmpv6']],
|
|
||||||
'options': {
|
|
||||||
'icmpv6': {
|
|
||||||
'type': 'dict',
|
|
||||||
'options': {
|
|
||||||
'address_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'administratively_prohibited':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'beyond_scope_of_source_address':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'destination_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'echo': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'echo_reply': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'erroneous_header_field': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'group_membership_query': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'group_membership_report': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'group_membership_termination':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'nd_na': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'nd_ns': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'neighbor_redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'no_route_to_destination': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'node_information_request_is_refused':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'node_information_successful_reply':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'packet_too_big': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'parameter_problem': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'port_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'query_subject_is_IPv4address':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'query_subject_is_IPv6address':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'query_subject_is_domainname': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'reassembly_timeout': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'router_advertisement': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'router_renumbering': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'router_solicitation': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'rr_command': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'rr_result': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'rr_seqnum_reset': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'time_exceeded': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'ttl_exceeded': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'unknown_query_type': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'unrecognized_next_header': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'unrecognized_option': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'whoareyou_reply': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'whoareyou_request': {
|
|
||||||
'type': 'bool'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'icmp': {
|
|
||||||
'options': {
|
|
||||||
'administratively_prohibited':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'alternate_address': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'conversion_error': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'dod_host_prohibited': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'dod_net_prohibited': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'echo': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'echo_reply': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'general_parameter_problem': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_isolated': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_precedence_unreachable':
|
|
||||||
{
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_tos_redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_tos_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_unknown': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'information_reply': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'information_request': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'mask_reply': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'mask_request': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'mobile_redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'net_redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'net_tos_redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'net_tos_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'net_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'network_unknown': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'no_room_for_option': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'option_missing': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'packet_too_big': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'parameter_problem': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'port_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'precedence_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'protocol_unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'reassembly_timeout': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'redirect': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'router_advertisement': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'router_solicitation': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'source_quench': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'source_route_failed': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'time_exceeded': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'timestamp_reply': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'timestamp_request': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'traceroute': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'ttl_exceeded': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'unreachable': {
|
|
||||||
'type': 'bool'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'igmp': {
|
|
||||||
'options': {
|
|
||||||
'dvmrp': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_query': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'host_report': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'mtrace': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'mtrace_response': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'pim': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'trace': {
|
|
||||||
'type': 'bool'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'tcp': {
|
|
||||||
'options': {
|
|
||||||
'ack': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'established': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'fin': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'psh': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'rst': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'syn': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'urg': {
|
|
||||||
'type': 'bool'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'remark': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'sequence': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'source': {
|
|
||||||
'mutually_exclusive': [['address', 'any', 'host', 'prefix'], ['wildcard_bits', 'any', 'host', 'prefix']],
|
|
||||||
'options': {
|
|
||||||
'host': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'address': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'any': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'prefix': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'port_protocol': {
|
|
||||||
'mutually_exclusive': [['eq', 'gt', 'lt', 'neq', 'range']],
|
|
||||||
'options': {
|
|
||||||
'eq': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'gt': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'lt': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'neq': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'range': {
|
|
||||||
'options': {
|
|
||||||
'end': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'required_together': [['start', 'end']],
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'wildcard_bits': {
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'required_together': [['address', 'wildcard_bits']],
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'ttl': {
|
|
||||||
'mutually_exclusive': [['eq', 'gt', 'lt', 'neq', 'range']],
|
|
||||||
'options': {
|
|
||||||
'eq': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'gt': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'lt': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'neq': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'range': {
|
|
||||||
'options': {
|
|
||||||
'end': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'start': {
|
|
||||||
'type': 'int'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'afi': {
|
|
||||||
'choices': ['ipv4', 'ipv6'],
|
|
||||||
'required': True,
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': [
|
|
||||||
'merged', 'replaced', 'overridden', 'deleted', 'gathered',
|
|
||||||
'rendered', 'parsed'
|
|
||||||
],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,24 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr facts module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class FactsArgs(object): # pylint: disable=R0903
|
|
||||||
""" The arg spec for the iosxr facts module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'gather_subset': dict(default=['!config'], type='list'),
|
|
||||||
'gather_network_resources': dict(type='list'),
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the ios_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class InterfacesArgs(object):
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {'config': {'elements': 'dict',
|
|
||||||
'options': {'name': {'type': 'str', 'required': True},
|
|
||||||
'description': {'type': 'str'},
|
|
||||||
'enabled': {'default': True, 'type': 'bool'},
|
|
||||||
'speed': {'type': 'int'},
|
|
||||||
'mtu': {'type': 'int'},
|
|
||||||
'duplex': {'type': 'str', 'choices': ['full', 'half']}},
|
|
||||||
'type': 'list'},
|
|
||||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'}}
|
|
@ -1,57 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_l2_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class L2_InterfacesArgs(object):
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {'config': {'elements': 'dict',
|
|
||||||
'options': {'name': {'type': 'str', 'required': True},
|
|
||||||
'native_vlan': {'type': 'int'},
|
|
||||||
'l2transport': {'type': 'bool'},
|
|
||||||
'l2protocol': {'element': 'dict',
|
|
||||||
'type': 'list',
|
|
||||||
'options': {'cdp': {'type': 'str',
|
|
||||||
'choices': ['drop', 'forward', 'tunnel']},
|
|
||||||
'pvst': {'type': 'str',
|
|
||||||
'choices': ['drop', 'forward', 'tunnel']},
|
|
||||||
'stp': {'type': 'str',
|
|
||||||
'choices': ['drop', 'forward', 'tunnel']},
|
|
||||||
'vtp': {'type': 'str',
|
|
||||||
'choices': ['drop', 'forward', 'tunnel']},
|
|
||||||
}},
|
|
||||||
'q_vlan': {'type': 'list'},
|
|
||||||
'propagate': {'type': 'bool'}},
|
|
||||||
'type': 'list'},
|
|
||||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'}}
|
|
@ -1,51 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the ios_l3_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class L3_InterfacesArgs(object):
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {'config': {'elements': 'dict',
|
|
||||||
'options': {'name': {'type': 'str', 'required': True},
|
|
||||||
'ipv4': {'element': 'dict',
|
|
||||||
'type': 'list',
|
|
||||||
'options': {'address': {'type': 'str'},
|
|
||||||
'secondary': {'type': 'bool'}}},
|
|
||||||
'ipv6': {'element': 'dict',
|
|
||||||
'type': 'list',
|
|
||||||
'options': {'address': {'type': 'str'}}}
|
|
||||||
},
|
|
||||||
'type': 'list'},
|
|
||||||
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'}}
|
|
@ -1,67 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_lacp module
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class LacpArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_lacp module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': {
|
|
||||||
'options': {
|
|
||||||
'system': {
|
|
||||||
'options': {
|
|
||||||
'mac': {
|
|
||||||
'type': 'dict',
|
|
||||||
'options': {
|
|
||||||
'address': {
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'priority': {
|
|
||||||
'type': 'int'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': ['merged', 'replaced', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,79 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_lacp_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class Lacp_interfacesArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_lacp_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'churn_logging': {
|
|
||||||
'choices': ['actor', 'partner', 'both'],
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'collector_max_delay': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'name': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'period': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'switchover_suppress_flaps': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'system': {
|
|
||||||
'options': {
|
|
||||||
'mac': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'priority': {
|
|
||||||
'type': 'int'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,87 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_lag_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class Lag_interfacesArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_lag_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'links': {
|
|
||||||
'options': {
|
|
||||||
'max_active': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'min_active': {
|
|
||||||
'type': 'int'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'load_balancing_hash': {
|
|
||||||
'choices': ['dst-ip', 'src-ip'],
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'members': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'member': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'mode': {
|
|
||||||
'choices': ['on', 'active', 'passive', 'inherit'],
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'mode': {
|
|
||||||
'choices': ['on', 'active', 'passive'],
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'name': {
|
|
||||||
'required': True,
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_lldp_global module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class Lldp_globalArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_lldp module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': {
|
|
||||||
'options': {
|
|
||||||
'holdtime': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'reinit': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'subinterfaces': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'timer': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'tlv_select': {
|
|
||||||
'options': {
|
|
||||||
'management_address': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'port_description': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'system_capabilities': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'system_description': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'system_name': {
|
|
||||||
'type': 'bool'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'dict'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': ['merged', 'replaced', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,70 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_lldp_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class Lldp_interfacesArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_lldp_interfaces module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'destination': {
|
|
||||||
'type': 'dict',
|
|
||||||
'options': {
|
|
||||||
'mac_address': {
|
|
||||||
'type': 'str',
|
|
||||||
'choices': ['ieee-nearest-bridge', 'ieee-nearest-non-tmpr-bridge'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'name': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'receive': {
|
|
||||||
'type': 'bool'
|
|
||||||
},
|
|
||||||
'transmit': {
|
|
||||||
'type': 'bool'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,121 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
"""
|
|
||||||
The arg spec for the iosxr_static_routes module
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
class Static_routesArgs(object): # pylint: disable=R0903
|
|
||||||
"""The arg spec for the iosxr_static_routes module
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'vrf': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'address_families': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'afi': {
|
|
||||||
'choices': ['ipv4', 'ipv6'],
|
|
||||||
'required': True,
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'safi': {
|
|
||||||
'choices': ['unicast', 'multicast'],
|
|
||||||
'required': True,
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'routes': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'dest': {
|
|
||||||
'required': True,
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'next_hops': {
|
|
||||||
'elements': 'dict',
|
|
||||||
'options': {
|
|
||||||
'admin_distance': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'description': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'dest_vrf': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'forward_router_address': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'interface': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'metric': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'tag': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'track': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'tunnel_id': {
|
|
||||||
'type': 'int'
|
|
||||||
},
|
|
||||||
'vrflabel': {
|
|
||||||
'type': 'int'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'type': 'list'
|
|
||||||
},
|
|
||||||
'running_config': {
|
|
||||||
'type': 'str'
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
'choices': [
|
|
||||||
'merged', 'replaced', 'overridden', 'deleted', 'gathered', 'rendered', 'parsed'
|
|
||||||
],
|
|
||||||
'default': 'merged',
|
|
||||||
'type': 'str'
|
|
||||||
}
|
|
||||||
} # pylint: disable=C0301
|
|
@ -1,317 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_acl_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import normalize_interface, diff_list_of_dicts, pad_commands
|
|
||||||
from ansible.module_utils.network.common.utils \
|
|
||||||
import (
|
|
||||||
to_list,
|
|
||||||
search_obj_in_list,
|
|
||||||
remove_empties
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Acl_interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_acl_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'acl_interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Acl_interfaces, self).__init__(module)
|
|
||||||
|
|
||||||
def get_acl_interfaces_facts(self, data=None):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(
|
|
||||||
self.gather_subset, self.gather_network_resources, data=data)
|
|
||||||
acl_interfaces_facts = facts['ansible_network_resources'].get(
|
|
||||||
'acl_interfaces')
|
|
||||||
if not acl_interfaces_facts:
|
|
||||||
return []
|
|
||||||
return acl_interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
warnings = list()
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
existing_acl_interfaces_facts = self.get_acl_interfaces_facts()
|
|
||||||
else:
|
|
||||||
existing_acl_interfaces_facts = []
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES or self.state == "rendered":
|
|
||||||
commands.extend(self.set_config(existing_acl_interfaces_facts))
|
|
||||||
|
|
||||||
if commands and self.state in self.ACTION_STATES:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result["changed"] = True
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
result["commands"] = commands
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES or self.state == "gathered":
|
|
||||||
changed_acl_interfaces_facts = self.get_acl_interfaces_facts()
|
|
||||||
|
|
||||||
elif self.state == "rendered":
|
|
||||||
result["rendered"] = commands
|
|
||||||
|
|
||||||
elif self.state == "parsed":
|
|
||||||
running_config = self._module.params["running_config"]
|
|
||||||
if not running_config:
|
|
||||||
self._module.fail_json(msg="value of running_config parameter must not be empty for state parsed")
|
|
||||||
result["parsed"] = self.get_acl_interfaces_facts(
|
|
||||||
data=running_config)
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
result["before"] = existing_acl_interfaces_facts
|
|
||||||
if result["changed"]:
|
|
||||||
result["after"] = changed_acl_interfaces_facts
|
|
||||||
|
|
||||||
elif self.state == "gathered":
|
|
||||||
result["gathered"] = changed_acl_interfaces_facts
|
|
||||||
|
|
||||||
result["warnings"] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_acl_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
if want:
|
|
||||||
for item in want:
|
|
||||||
item['name'] = normalize_interface(item['name'])
|
|
||||||
if 'members' in want and want['members']:
|
|
||||||
for item in want['members']:
|
|
||||||
item.update({
|
|
||||||
'member':
|
|
||||||
normalize_interface(item['member']),
|
|
||||||
'mode':
|
|
||||||
item['mode']
|
|
||||||
})
|
|
||||||
have = existing_acl_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params['state']
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands.extend(self._state_overridden(want, have))
|
|
||||||
|
|
||||||
elif state == 'deleted':
|
|
||||||
if not want:
|
|
||||||
for intf in have:
|
|
||||||
commands.extend(self._state_deleted({}, intf))
|
|
||||||
else:
|
|
||||||
for item in want:
|
|
||||||
obj_in_have = search_obj_in_list(item['name'], have) or {}
|
|
||||||
commands.extend(
|
|
||||||
self._state_deleted(remove_empties(item), obj_in_have))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Instead of passing entire want and have
|
|
||||||
# list of dictionaries to the respective
|
|
||||||
# _state_* methods we are passing the want
|
|
||||||
# and have dictionaries per interface
|
|
||||||
for item in want:
|
|
||||||
name = item['name']
|
|
||||||
obj_in_have = search_obj_in_list(name, have) or {}
|
|
||||||
|
|
||||||
if state == 'merged' or state == 'rendered':
|
|
||||||
commands.extend(self._state_merged(item, obj_in_have))
|
|
||||||
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands.extend(self._state_replaced(item, obj_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
want = remove_empties(want)
|
|
||||||
|
|
||||||
delete_commands = []
|
|
||||||
for have_afi in have.get('access_groups', []):
|
|
||||||
want_afi = search_obj_in_list(have_afi['afi'],
|
|
||||||
want.get('access_groups', []),
|
|
||||||
key='afi') or {}
|
|
||||||
afi = have_afi.get('afi')
|
|
||||||
|
|
||||||
for acl in have_afi.get('acls', []):
|
|
||||||
if acl not in want_afi.get('acls', []):
|
|
||||||
delete_commands.extend(
|
|
||||||
self._compute_commands(afi, [acl], remove=True))
|
|
||||||
|
|
||||||
if delete_commands:
|
|
||||||
pad_commands(delete_commands, want['name'])
|
|
||||||
commands.extend(delete_commands)
|
|
||||||
|
|
||||||
merged_commands = self._state_merged(want, have)
|
|
||||||
if merged_commands and delete_commands:
|
|
||||||
del merged_commands[0]
|
|
||||||
|
|
||||||
commands.extend(merged_commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for have_intf in have:
|
|
||||||
want_intf = search_obj_in_list(have_intf['name'], want) or {}
|
|
||||||
if not want_intf:
|
|
||||||
commands.extend(self._state_deleted(want_intf, have_intf))
|
|
||||||
|
|
||||||
for want_intf in want:
|
|
||||||
have_intf = search_obj_in_list(want_intf['name'], have) or {}
|
|
||||||
commands.extend(self._state_replaced(want_intf, have_intf))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
want = remove_empties(want)
|
|
||||||
|
|
||||||
for want_afi in want.get('access_groups', []):
|
|
||||||
have_afi = search_obj_in_list(want_afi['afi'],
|
|
||||||
have.get('access_groups', []),
|
|
||||||
key='afi') or {}
|
|
||||||
delta = diff_list_of_dicts(want_afi['acls'],
|
|
||||||
have_afi.get('acls', []),
|
|
||||||
key='direction')
|
|
||||||
commands.extend(self._compute_commands(want_afi['afi'], delta))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, want['name'])
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
# This handles deletion for both empty/no config
|
|
||||||
# and just interface name provided.
|
|
||||||
if 'access_groups' not in want:
|
|
||||||
for x in have.get('access_groups', []):
|
|
||||||
afi = x.get('afi')
|
|
||||||
for have_acl in x.get('acls', []):
|
|
||||||
commands.extend(
|
|
||||||
self._compute_commands(afi, [have_acl], remove=True))
|
|
||||||
|
|
||||||
else:
|
|
||||||
for want_afi in want['access_groups']:
|
|
||||||
have_afi = search_obj_in_list(want_afi['afi'],
|
|
||||||
have.get('access_groups', []),
|
|
||||||
key='afi') or {}
|
|
||||||
afi = have_afi.get('afi')
|
|
||||||
|
|
||||||
# If only the AFI has be specified, we
|
|
||||||
# delete all the access-groups for that AFI
|
|
||||||
if 'acls' not in want_afi:
|
|
||||||
for have_acl in have_afi.get('acls', []):
|
|
||||||
commands.extend(
|
|
||||||
self._compute_commands(afi, [have_acl],
|
|
||||||
remove=True))
|
|
||||||
|
|
||||||
# If one or more acl has been explicitly specified, we
|
|
||||||
# delete that and leave the rest untouched
|
|
||||||
else:
|
|
||||||
for acl in want_afi['acls']:
|
|
||||||
if acl in have_afi.get('acls', []):
|
|
||||||
commands.extend(
|
|
||||||
self._compute_commands(afi, [acl],
|
|
||||||
remove=True))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, have['name'])
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _compute_commands(self, afi, delta, remove=False):
|
|
||||||
updates = []
|
|
||||||
map_dir = {'in': 'ingress', 'out': 'egress'}
|
|
||||||
|
|
||||||
for x in delta:
|
|
||||||
cmd = "{0} access-group {1} {2}".format(afi, x['name'],
|
|
||||||
map_dir[x['direction']])
|
|
||||||
if remove:
|
|
||||||
cmd = "no " + cmd
|
|
||||||
updates.append(cmd)
|
|
||||||
|
|
||||||
return updates
|
|
@ -1,440 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_acls class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils \
|
|
||||||
import (
|
|
||||||
flatten_dict,
|
|
||||||
prefix_to_address_wildcard,
|
|
||||||
is_ipv4_address
|
|
||||||
)
|
|
||||||
from ansible.module_utils.network.common.utils \
|
|
||||||
import (
|
|
||||||
to_list,
|
|
||||||
search_obj_in_list,
|
|
||||||
dict_diff,
|
|
||||||
remove_empties,
|
|
||||||
)
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
|
|
||||||
|
|
||||||
class Acls(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_acls class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'acls',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Acls, self).__init__(module)
|
|
||||||
|
|
||||||
def get_acls_facts(self, data=None):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(
|
|
||||||
self.gather_subset, self.gather_network_resources, data=data)
|
|
||||||
acls_facts = facts["ansible_network_resources"].get("acls")
|
|
||||||
if not acls_facts:
|
|
||||||
return []
|
|
||||||
return acls_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
warnings = list()
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
existing_acls_facts = self.get_acls_facts()
|
|
||||||
else:
|
|
||||||
existing_acls_facts = []
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES or self.state == "rendered":
|
|
||||||
commands.extend(self.set_config(existing_acls_facts))
|
|
||||||
|
|
||||||
if commands and self.state in self.ACTION_STATES:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result["changed"] = True
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
result["commands"] = commands
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES or self.state == "gathered":
|
|
||||||
changed_acls_facts = self.get_acls_facts()
|
|
||||||
|
|
||||||
elif self.state == "rendered":
|
|
||||||
result["rendered"] = commands
|
|
||||||
|
|
||||||
elif self.state == "parsed":
|
|
||||||
running_config = self._module.params["running_config"]
|
|
||||||
if not running_config:
|
|
||||||
self._module.fail_json(
|
|
||||||
msg="value of running_config parameter must not be empty for state parsed"
|
|
||||||
)
|
|
||||||
result["parsed"] = self.get_acls_facts(data=running_config)
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
result["before"] = existing_acls_facts
|
|
||||||
if result["changed"]:
|
|
||||||
result["after"] = changed_acls_facts
|
|
||||||
|
|
||||||
elif self.state == "gathered":
|
|
||||||
result["gathered"] = changed_acls_facts
|
|
||||||
|
|
||||||
result["warnings"] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_acls_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
have = existing_acls_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params['state']
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if state in ('overridden', 'merged', 'replaced',
|
|
||||||
'rendered') and not want:
|
|
||||||
self._module.fail_json(
|
|
||||||
msg='value of config parameter must not be empty for state {0}'
|
|
||||||
.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands.extend(self._state_overridden(want, have))
|
|
||||||
|
|
||||||
elif state == 'deleted':
|
|
||||||
commands.extend(self._state_deleted(want, have))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Instead of passing entire want and have
|
|
||||||
# list of dictionaries to the respective
|
|
||||||
# _state_* methods we are passing the want
|
|
||||||
# and have dictionaries per AFI
|
|
||||||
for item in want:
|
|
||||||
afi = item['afi']
|
|
||||||
obj_in_have = search_obj_in_list(afi, have, key='afi')
|
|
||||||
|
|
||||||
if state == 'merged' or self.state == 'rendered':
|
|
||||||
commands.extend(
|
|
||||||
self._state_merged(remove_empties(item), obj_in_have))
|
|
||||||
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands.extend(
|
|
||||||
self._state_replaced(remove_empties(item),
|
|
||||||
obj_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for want_acl in want['acls']:
|
|
||||||
have_acl = search_obj_in_list(want_acl['name'], have['acls']) or {}
|
|
||||||
acl_updates = []
|
|
||||||
|
|
||||||
for have_ace in have_acl.get('aces', []):
|
|
||||||
want_ace = search_obj_in_list(have_ace['sequence'], want_acl['aces'], key='sequence') or {}
|
|
||||||
if not want_ace:
|
|
||||||
acl_updates.append('no {0}'.format(have_ace['sequence']))
|
|
||||||
|
|
||||||
for want_ace in want_acl.get('aces', []):
|
|
||||||
have_ace = search_obj_in_list(want_ace.get('sequence'), have_acl.get('aces', []), key='sequence') or {}
|
|
||||||
set_cmd = self._set_commands(want_ace, have_ace)
|
|
||||||
if set_cmd:
|
|
||||||
acl_updates.append(set_cmd)
|
|
||||||
|
|
||||||
if acl_updates:
|
|
||||||
acl_updates.insert(0, '{0} access-list {1}'.format(want['afi'], want_acl['name']))
|
|
||||||
commands.extend(acl_updates)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
# Remove extraneous AFI that are present in config but not
|
|
||||||
# specified in `want`
|
|
||||||
for have_afi in have:
|
|
||||||
want_afi = search_obj_in_list(have_afi['afi'], want, key='afi') or {}
|
|
||||||
if not want_afi:
|
|
||||||
for acl in have_afi.get('acls', []):
|
|
||||||
commands.append('no {0} access-list {1}'.format(have_afi['afi'], acl['name']))
|
|
||||||
|
|
||||||
# First we remove the extraneous ACLs from the AFIs that
|
|
||||||
# are present both in `want` and in `have` and then
|
|
||||||
# we call `_state_replaced` to update the ACEs within those ACLs
|
|
||||||
for want_afi in want:
|
|
||||||
want_afi = remove_empties(want_afi)
|
|
||||||
have_afi = search_obj_in_list(want_afi['afi'], have, key='afi') or {}
|
|
||||||
if have_afi:
|
|
||||||
for have_acl in have_afi.get('acls', []):
|
|
||||||
want_acl = search_obj_in_list(have_acl['name'], want_afi.get('acls', [])) or {}
|
|
||||||
if not want_acl:
|
|
||||||
commands.append('no {0} access-list {1}'.format(have_afi['afi'], have_acl['name']))
|
|
||||||
|
|
||||||
commands.extend(self._state_replaced(want_afi, have_afi))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if not have:
|
|
||||||
have = {}
|
|
||||||
|
|
||||||
for want_acl in want['acls']:
|
|
||||||
have_acl = search_obj_in_list(want_acl['name'], have.get('acls', {})) or {}
|
|
||||||
|
|
||||||
acl_updates = []
|
|
||||||
for want_ace in want_acl['aces']:
|
|
||||||
have_ace = search_obj_in_list(want_ace.get('sequence'), have_acl.get('aces', []), key='sequence') or {}
|
|
||||||
set_cmd = self._set_commands(want_ace, have_ace)
|
|
||||||
if set_cmd:
|
|
||||||
acl_updates.append(set_cmd)
|
|
||||||
|
|
||||||
if acl_updates:
|
|
||||||
acl_updates.insert(0, '{0} access-list {1}'.format(want['afi'], want_acl['name']))
|
|
||||||
commands.extend(acl_updates)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if not want:
|
|
||||||
want = [{'afi': 'ipv4'}, {'afi': 'ipv6'}]
|
|
||||||
|
|
||||||
for item in want:
|
|
||||||
item = remove_empties(item)
|
|
||||||
have_item = search_obj_in_list(item['afi'], have, key='afi') or {}
|
|
||||||
if 'acls' not in item:
|
|
||||||
if have_item:
|
|
||||||
for acl in have_item['acls']:
|
|
||||||
commands.append('no {0} access-list {1}'.format(have_item['afi'], acl['name']))
|
|
||||||
else:
|
|
||||||
for want_acl in item['acls']:
|
|
||||||
have_acl = search_obj_in_list(want_acl['name'], have_item.get('acls', [])) or {}
|
|
||||||
if have_acl:
|
|
||||||
if 'aces' not in want_acl:
|
|
||||||
commands.append('no {0} access-list {1}'.format(have_item['afi'], have_acl['name']))
|
|
||||||
else:
|
|
||||||
acl_updates = []
|
|
||||||
for want_ace in want_acl['aces']:
|
|
||||||
have_ace = search_obj_in_list(want_ace.get('sequence'), have_acl.get('aces', []), key='sequence') or {}
|
|
||||||
if have_ace:
|
|
||||||
acl_updates.append('no {0}'.format(have_ace['sequence']))
|
|
||||||
|
|
||||||
if acl_updates:
|
|
||||||
acl_updates.insert(0, '{0} access-list {1}'.format(have_item['afi'], have_acl['name']))
|
|
||||||
commands.extend(acl_updates)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _compute_commands(self, want_ace):
|
|
||||||
"""This command creates an ACE line from an ACE dictionary
|
|
||||||
|
|
||||||
:rtype: A string
|
|
||||||
:returns: An ACE generated from a structured ACE dictionary
|
|
||||||
"""
|
|
||||||
def __compute_src_dest(dir_dict):
|
|
||||||
cmd = ""
|
|
||||||
if 'any' in dir_dict:
|
|
||||||
cmd += 'any '
|
|
||||||
elif 'host' in dir_dict:
|
|
||||||
cmd += 'host {0} '.format(dir_dict['host'])
|
|
||||||
elif 'prefix' in dir_dict:
|
|
||||||
cmd += '{0} '.format(dir_dict['prefix'])
|
|
||||||
else:
|
|
||||||
cmd += '{0} {1} '.format(dir_dict['address'],
|
|
||||||
dir_dict['wildcard_bits'])
|
|
||||||
|
|
||||||
if 'port_protocol' in dir_dict:
|
|
||||||
protocol_range = dir_dict['port_protocol'].get('range')
|
|
||||||
if protocol_range:
|
|
||||||
cmd += 'range {0} {1} '.format(protocol_range['start'],
|
|
||||||
protocol_range['end'])
|
|
||||||
else:
|
|
||||||
for key, value in iteritems(dir_dict['port_protocol']):
|
|
||||||
cmd += '{0} {1} '.format(key, value)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def __compute_protocol_options(protocol_dict):
|
|
||||||
cmd = ""
|
|
||||||
for value in protocol_options.values():
|
|
||||||
for subkey, subvalue in iteritems(value):
|
|
||||||
if subvalue:
|
|
||||||
cmd += '{0} '.format(subkey.replace('_', '-'))
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def __compute_match_options(want_ace):
|
|
||||||
cmd = ""
|
|
||||||
|
|
||||||
if 'precedence' in want_ace:
|
|
||||||
cmd += 'precedence {0} '.format(want_ace['precedence'])
|
|
||||||
|
|
||||||
for x in ['dscp', 'packet_length', 'ttl']:
|
|
||||||
if x in want_ace:
|
|
||||||
opt_range = want_ace[x].get('range')
|
|
||||||
if opt_range:
|
|
||||||
cmd += '{0} range {1} {2} '.format(
|
|
||||||
x.replace('_', '-'), opt_range['start'],
|
|
||||||
opt_range['end'])
|
|
||||||
else:
|
|
||||||
for key, value in iteritems(want_ace[x]):
|
|
||||||
cmd += '{0} {1} {2} '.format(
|
|
||||||
x.replace('_', '-'), key, value)
|
|
||||||
|
|
||||||
for x in ('authen', 'capture', 'fragments', 'routing', 'log',
|
|
||||||
'log_input', 'icmp_off', 'destopts', 'hop_by_hop'):
|
|
||||||
if x in want_ace:
|
|
||||||
cmd += '{0} '.format(x.replace('_', '-'))
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
cmd = ""
|
|
||||||
if 'sequence' in want_ace:
|
|
||||||
cmd += '{0} '.format(want_ace['sequence'])
|
|
||||||
|
|
||||||
if 'remark' in want_ace:
|
|
||||||
cmd += 'remark {0}'.format(want_ace['remark'])
|
|
||||||
|
|
||||||
elif 'line' in want_ace:
|
|
||||||
cmd += want_ace['line']
|
|
||||||
|
|
||||||
else:
|
|
||||||
cmd += '{0} '.format(want_ace['grant'])
|
|
||||||
if 'protocol' in want_ace:
|
|
||||||
cmd += '{0} '.format(want_ace['protocol'])
|
|
||||||
|
|
||||||
cmd += __compute_src_dest(want_ace['source'])
|
|
||||||
cmd += __compute_src_dest(want_ace['destination'])
|
|
||||||
|
|
||||||
protocol_options = want_ace.get('protocol_options', {})
|
|
||||||
if protocol_options:
|
|
||||||
cmd += __compute_protocol_options(protocol_options)
|
|
||||||
|
|
||||||
cmd += __compute_match_options(want_ace)
|
|
||||||
|
|
||||||
return cmd.strip()
|
|
||||||
|
|
||||||
def _set_commands(self, want_ace, have_ace):
|
|
||||||
"""A helped method that checks if there is
|
|
||||||
a delta between the `have_ace` and `want_ace`.
|
|
||||||
If there is a delta then it calls `_compute_commands`
|
|
||||||
to create the ACE line.
|
|
||||||
|
|
||||||
:rtype: A string
|
|
||||||
:returns: An ACE generated from a structured ACE dictionary
|
|
||||||
via a call to `_compute_commands`
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 'line' in want_ace:
|
|
||||||
if want_ace['line'] != have_ace.get('line'):
|
|
||||||
return self._compute_commands(want_ace)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if ('prefix' in want_ace.get('source', {})) or ('prefix' in want_ace.get('destination', {})):
|
|
||||||
self._prepare_for_diff(want_ace)
|
|
||||||
|
|
||||||
protocol_opt_delta = {}
|
|
||||||
delta = dict_diff(have_ace, want_ace)
|
|
||||||
|
|
||||||
# `dict_diff` doesn't work properly for `protocol_options` diff,
|
|
||||||
# so we need to handle it separately
|
|
||||||
if want_ace.get('protocol_options', {}):
|
|
||||||
protocol_opt_delta = set(flatten_dict(have_ace.get('protocol_options', {}))) ^ \
|
|
||||||
set(flatten_dict(want_ace.get('protocol_options', {})))
|
|
||||||
|
|
||||||
if delta or protocol_opt_delta:
|
|
||||||
want_ace = self._dict_merge(have_ace, want_ace)
|
|
||||||
return self._compute_commands(want_ace)
|
|
||||||
|
|
||||||
def _prepare_for_diff(self, ace):
|
|
||||||
"""This method prepares the want ace dict
|
|
||||||
for diff calculation against the have ace dict.
|
|
||||||
|
|
||||||
:param ace: The want ace to prepare for diff calculation
|
|
||||||
"""
|
|
||||||
# Convert prefixes to "address wildcard bits" format for IPv4 addresses
|
|
||||||
# Not valid for IPv6 addresses because those can only be specified as prefixes
|
|
||||||
# and are always rendered in running-config as prefixes too
|
|
||||||
for x in ['source', 'destination']:
|
|
||||||
prefix = ace.get(x, {}).get('prefix')
|
|
||||||
if prefix and is_ipv4_address(prefix):
|
|
||||||
del ace[x]['prefix']
|
|
||||||
ace[x]['address'], ace[x]['wildcard_bits'] = prefix_to_address_wildcard(prefix)
|
|
||||||
|
|
||||||
def _dict_merge(self, have_ace, want_ace):
|
|
||||||
for x in want_ace:
|
|
||||||
have_ace[x] = want_ace[x]
|
|
||||||
return have_ace
|
|
@ -1,265 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat Inc.
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type, dict_to_set
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
|
||||||
|
|
||||||
|
|
||||||
class Interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
params = ('description', 'mtu', 'speed', 'duplex')
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Interfaces, self).__init__(module)
|
|
||||||
|
|
||||||
def get_interfaces_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
interfaces_facts = facts['ansible_network_resources'].get('interfaces')
|
|
||||||
if not interfaces_facts:
|
|
||||||
return []
|
|
||||||
return interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
commands = list()
|
|
||||||
warnings = list()
|
|
||||||
|
|
||||||
existing_interfaces_facts = self.get_interfaces_facts()
|
|
||||||
commands.extend(self.set_config(existing_interfaces_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_interfaces_facts = self.get_interfaces_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_interfaces_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_interfaces_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
have = existing_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
state = self._module.params['state']
|
|
||||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands = self._state_overridden(want, have)
|
|
||||||
elif state == 'deleted':
|
|
||||||
commands = self._state_deleted(want, have)
|
|
||||||
elif state == 'merged':
|
|
||||||
commands = self._state_merged(want, have)
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands = self._state_replaced(want, have)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for interface in want:
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
have_dict = filter_dict_having_none_value(interface, each)
|
|
||||||
want = dict()
|
|
||||||
commands.extend(self._clear_config(want, have_dict))
|
|
||||||
commands.extend(self._set_config(interface, each))
|
|
||||||
# Remove the duplicate interface call
|
|
||||||
commands = remove_duplicate_interface(commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for each in have:
|
|
||||||
for interface in want:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# We didn't find a matching desired state, which means we can
|
|
||||||
# pretend we received an empty desired state.
|
|
||||||
interface = dict(name=each['name'])
|
|
||||||
commands.extend(self._clear_config(interface, each))
|
|
||||||
continue
|
|
||||||
have_dict = filter_dict_having_none_value(interface, each)
|
|
||||||
want = dict()
|
|
||||||
commands.extend(self._clear_config(want, have_dict))
|
|
||||||
commands.extend(self._set_config(interface, each))
|
|
||||||
# Remove the duplicate interface call
|
|
||||||
commands = remove_duplicate_interface(commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for interface in want:
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
commands.extend(self._set_config(interface, each))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if want:
|
|
||||||
for interface in want:
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
interface = dict(name=interface['name'])
|
|
||||||
commands.extend(self._clear_config(interface, each))
|
|
||||||
else:
|
|
||||||
for each in have:
|
|
||||||
want = dict()
|
|
||||||
commands.extend(self._clear_config(want, each))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _set_config(self, want, have):
|
|
||||||
# Set the interface config based on the want and have config
|
|
||||||
commands = []
|
|
||||||
interface = 'interface ' + want['name']
|
|
||||||
|
|
||||||
# Get the diff b/w want and have
|
|
||||||
want_dict = dict_to_set(want)
|
|
||||||
have_dict = dict_to_set(have)
|
|
||||||
diff = want_dict - have_dict
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
diff = dict(diff)
|
|
||||||
for item in self.params:
|
|
||||||
if diff.get(item):
|
|
||||||
cmd = item + ' ' + str(want.get(item))
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
if diff.get('enabled'):
|
|
||||||
add_command_to_config_list(interface, 'no shutdown', commands)
|
|
||||||
elif diff.get('enabled') is False:
|
|
||||||
add_command_to_config_list(interface, 'shutdown', commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _clear_config(self, want, have):
|
|
||||||
# Delete the interface config based on the want and have config
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if want.get('name'):
|
|
||||||
interface_type = get_interface_type(want['name'])
|
|
||||||
interface = 'interface ' + want['name']
|
|
||||||
else:
|
|
||||||
interface_type = get_interface_type(have['name'])
|
|
||||||
interface = 'interface ' + have['name']
|
|
||||||
|
|
||||||
if have.get('description') and want.get('description') != have.get('description'):
|
|
||||||
remove_command_from_config_list(interface, 'description', commands)
|
|
||||||
if not have.get('enabled') and want.get('enabled') != have.get('enabled'):
|
|
||||||
# if enable is False set enable as True which is the default behavior
|
|
||||||
remove_command_from_config_list(interface, 'shutdown', commands)
|
|
||||||
|
|
||||||
if interface_type.lower() == 'gigabitethernet':
|
|
||||||
if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'):
|
|
||||||
remove_command_from_config_list(interface, 'speed', commands)
|
|
||||||
if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'):
|
|
||||||
remove_command_from_config_list(interface, 'duplex', commands)
|
|
||||||
if have.get('mtu') and want.get('mtu') != have.get('mtu'):
|
|
||||||
remove_command_from_config_list(interface, 'mtu', commands)
|
|
||||||
|
|
||||||
return commands
|
|
@ -1,305 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat Inc.
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_l2_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import normalize_interface, dict_to_set
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
|
||||||
|
|
||||||
|
|
||||||
class L2_Interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'l2_interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_l2_interfaces_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces')
|
|
||||||
|
|
||||||
if not l2_interfaces_facts:
|
|
||||||
return []
|
|
||||||
return l2_interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
commands = list()
|
|
||||||
warnings = list()
|
|
||||||
|
|
||||||
existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
|
|
||||||
commands.extend(self.set_config(existing_l2_interfaces_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_l2_interfaces_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_l2_interfaces_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_l2_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
have = existing_l2_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
state = self._module.params['state']
|
|
||||||
|
|
||||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands = self._state_overridden(want, have, self._module)
|
|
||||||
elif state == 'deleted':
|
|
||||||
commands = self._state_deleted(want, have)
|
|
||||||
elif state == 'merged':
|
|
||||||
commands = self._state_merged(want, have, self._module)
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands = self._state_replaced(want, have, self._module)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have, module):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
commands.extend(self._set_config(interface, {}, module))
|
|
||||||
continue
|
|
||||||
have_dict = filter_dict_having_none_value(interface, each)
|
|
||||||
commands.extend(self._clear_config(dict(), have_dict))
|
|
||||||
commands.extend(self._set_config(interface, each, module))
|
|
||||||
# Remove the duplicate interface call
|
|
||||||
commands = remove_duplicate_interface(commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have, module):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
not_in_have = set()
|
|
||||||
in_have = set()
|
|
||||||
for each in have:
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
in_have.add(interface['name'])
|
|
||||||
break
|
|
||||||
elif interface['name'] != each['name']:
|
|
||||||
not_in_have.add(interface['name'])
|
|
||||||
else:
|
|
||||||
# We didn't find a matching desired state, which means we can
|
|
||||||
# pretend we received an empty desired state.
|
|
||||||
interface = dict(name=each['name'])
|
|
||||||
commands.extend(self._clear_config(interface, each))
|
|
||||||
continue
|
|
||||||
have_dict = filter_dict_having_none_value(interface, each)
|
|
||||||
commands.extend(self._clear_config(dict(), have_dict))
|
|
||||||
commands.extend(self._set_config(interface, each, module))
|
|
||||||
# Add the want interface that's not already configured in have interface
|
|
||||||
for each in (not_in_have - in_have):
|
|
||||||
for every in want:
|
|
||||||
interface = 'interface {0}'.format(every['name'])
|
|
||||||
if each and interface not in commands:
|
|
||||||
commands.extend(self._set_config(every, {}, module))
|
|
||||||
|
|
||||||
# Remove the duplicate interface call
|
|
||||||
commands = remove_duplicate_interface(commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have, module):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
commands.extend(self._set_config(interface, {}, module))
|
|
||||||
continue
|
|
||||||
commands.extend(self._set_config(interface, each, module))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if want:
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
interface = dict(name=interface['name'])
|
|
||||||
commands.extend(self._clear_config(interface, each))
|
|
||||||
else:
|
|
||||||
for each in have:
|
|
||||||
want = dict()
|
|
||||||
commands.extend(self._clear_config(want, each))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _set_config(self, want, have, module):
|
|
||||||
# Set the interface config based on the want and have config
|
|
||||||
commands = []
|
|
||||||
interface = 'interface ' + want['name']
|
|
||||||
l2_protocol_bool = False
|
|
||||||
|
|
||||||
# Get the diff b/w want and have
|
|
||||||
want_dict = dict_to_set(want)
|
|
||||||
have_dict = dict_to_set(have)
|
|
||||||
diff = want_dict - have_dict
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
# For merging with already configured l2protocol
|
|
||||||
if have.get('l2protocol') and len(have.get('l2protocol')) > 1:
|
|
||||||
l2_protocol_diff = []
|
|
||||||
for each in want.get('l2protocol'):
|
|
||||||
for every in have.get('l2protocol'):
|
|
||||||
if every == each:
|
|
||||||
break
|
|
||||||
if each not in have.get('l2protocol'):
|
|
||||||
l2_protocol_diff.append(each)
|
|
||||||
l2_protocol_bool = True
|
|
||||||
l2protocol = tuple(l2_protocol_diff)
|
|
||||||
else:
|
|
||||||
l2protocol = {}
|
|
||||||
|
|
||||||
diff = dict(diff)
|
|
||||||
wants_native = diff.get('native_vlan')
|
|
||||||
l2transport = diff.get('l2transport')
|
|
||||||
q_vlan = diff.get('q_vlan')
|
|
||||||
propagate = diff.get('propagate')
|
|
||||||
if l2_protocol_bool is False:
|
|
||||||
l2protocol = diff.get('l2protocol')
|
|
||||||
|
|
||||||
if wants_native:
|
|
||||||
cmd = 'dot1q native vlan {0}'.format(wants_native)
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
|
|
||||||
if l2transport or l2protocol:
|
|
||||||
for each in l2protocol:
|
|
||||||
each = dict(each)
|
|
||||||
if isinstance(each, dict):
|
|
||||||
cmd = 'l2transport l2protocol {0} {1}'.format(list(each.keys())[0], list(each.values())[0])
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
if propagate and not have.get('propagate'):
|
|
||||||
cmd = 'l2transport propagate remote-status'
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
elif want.get('l2transport') is False and (want.get('l2protocol') or want.get('propagate')):
|
|
||||||
module.fail_json(msg='L2transport L2protocol or Propagate can only be configured when '
|
|
||||||
'L2transport set to True!')
|
|
||||||
|
|
||||||
if q_vlan and '.' in interface:
|
|
||||||
q_vlans = (" ".join(map(str, want.get('q_vlan'))))
|
|
||||||
if q_vlans != have.get('q_vlan'):
|
|
||||||
cmd = 'dot1q vlan {0}'.format(q_vlans)
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _clear_config(self, want, have):
|
|
||||||
# Delete the interface config based on the want and have config
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if want.get('name'):
|
|
||||||
interface = 'interface ' + want['name']
|
|
||||||
else:
|
|
||||||
interface = 'interface ' + have['name']
|
|
||||||
if have.get('native_vlan'):
|
|
||||||
remove_command_from_config_list(interface, 'dot1q native vlan', commands)
|
|
||||||
|
|
||||||
if have.get('q_vlan'):
|
|
||||||
remove_command_from_config_list(interface, 'encapsulation dot1q', commands)
|
|
||||||
|
|
||||||
if have.get('l2protocol') and (want.get('l2protocol') is None or want.get('propagate') is None):
|
|
||||||
if 'no l2transport' not in commands:
|
|
||||||
remove_command_from_config_list(interface, 'l2transport', commands)
|
|
||||||
elif have.get('l2transport') and have.get('l2transport') != want.get('l2transport'):
|
|
||||||
if 'no l2transport' not in commands:
|
|
||||||
remove_command_from_config_list(interface, 'l2transport', commands)
|
|
||||||
|
|
||||||
return commands
|
|
@ -1,323 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat Inc.
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_l3_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import normalize_interface, dict_to_set
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_config_list, add_command_to_config_list
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import validate_n_expand_ipv4, validate_ipv6
|
|
||||||
|
|
||||||
|
|
||||||
class L3_Interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_l3_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'l3_interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_l3_interfaces_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
l3_interfaces_facts = facts['ansible_network_resources'].get('l3_interfaces')
|
|
||||||
if not l3_interfaces_facts:
|
|
||||||
return []
|
|
||||||
|
|
||||||
return l3_interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
commands = list()
|
|
||||||
warnings = list()
|
|
||||||
|
|
||||||
existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
|
|
||||||
commands.extend(self.set_config(existing_l3_interfaces_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_l3_interfaces_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_l3_interfaces_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_l3_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
have = existing_l3_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
state = self._module.params['state']
|
|
||||||
|
|
||||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands = self._state_overridden(want, have, self._module)
|
|
||||||
elif state == 'deleted':
|
|
||||||
commands = self._state_deleted(want, have)
|
|
||||||
elif state == 'merged':
|
|
||||||
commands = self._state_merged(want, have, self._module)
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands = self._state_replaced(want, have, self._module)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have, module):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
commands.extend(self._set_config(interface, dict(), module))
|
|
||||||
continue
|
|
||||||
have_dict = filter_dict_having_none_value(interface, each)
|
|
||||||
commands.extend(self._clear_config(dict(), have_dict))
|
|
||||||
commands.extend(self._set_config(interface, each, module))
|
|
||||||
# Remove the duplicate interface call
|
|
||||||
commands = remove_duplicate_interface(commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have, module):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
not_in_have = set()
|
|
||||||
in_have = set()
|
|
||||||
|
|
||||||
for each in have:
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
in_have.add(interface['name'])
|
|
||||||
break
|
|
||||||
elif interface['name'] != each['name']:
|
|
||||||
not_in_have.add(interface['name'])
|
|
||||||
else:
|
|
||||||
# We didn't find a matching desired state, which means we can
|
|
||||||
# pretend we received an empty desired state.
|
|
||||||
interface = dict(name=each['name'])
|
|
||||||
kwargs = {'want': interface, 'have': each}
|
|
||||||
commands.extend(self._clear_config(**kwargs))
|
|
||||||
continue
|
|
||||||
have_dict = filter_dict_having_none_value(interface, each)
|
|
||||||
commands.extend(self._clear_config(dict(), have_dict))
|
|
||||||
commands.extend(self._set_config(interface, each, module))
|
|
||||||
# Add the want interface that's not already configured in have interface
|
|
||||||
for each in (not_in_have - in_have):
|
|
||||||
for every in want:
|
|
||||||
interface = 'interface {0}'.format(every['name'])
|
|
||||||
if each and interface not in commands:
|
|
||||||
commands.extend(self._set_config(every, {}, module))
|
|
||||||
# Remove the duplicate interface call
|
|
||||||
commands = remove_duplicate_interface(commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have, module):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
commands.extend(self._set_config(interface, dict(), module))
|
|
||||||
continue
|
|
||||||
commands.extend(self._set_config(interface, each, module))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if want:
|
|
||||||
for interface in want:
|
|
||||||
interface['name'] = normalize_interface(interface['name'])
|
|
||||||
for each in have:
|
|
||||||
if each['name'] == interface['name']:
|
|
||||||
break
|
|
||||||
elif interface['name'] in each['name']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
interface = dict(name=interface['name'])
|
|
||||||
commands.extend(self._clear_config(interface, each))
|
|
||||||
else:
|
|
||||||
for each in have:
|
|
||||||
want = dict()
|
|
||||||
commands.extend(self._clear_config(want, each))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def verify_diff_again(self, want, have):
|
|
||||||
"""
|
|
||||||
Verify the IPV4 difference again as sometimes due to
|
|
||||||
change in order of set, set difference may result into change,
|
|
||||||
when there's actually no difference between want and have
|
|
||||||
:param want: want_dict IPV4
|
|
||||||
:param have: have_dict IPV4
|
|
||||||
:return: diff
|
|
||||||
"""
|
|
||||||
diff = False
|
|
||||||
for each in want:
|
|
||||||
each_want = dict(each)
|
|
||||||
for every in have:
|
|
||||||
every_have = dict(every)
|
|
||||||
if each_want.get('address') != every_have.get('address') and \
|
|
||||||
each_want.get('secondary') != every_have.get('secondary') and \
|
|
||||||
len(each_want.keys()) == len(every_have.keys()):
|
|
||||||
diff = True
|
|
||||||
break
|
|
||||||
elif each_want.get('address') != every_have.get('address') and len(each_want.keys()) == len(
|
|
||||||
every_have.keys()):
|
|
||||||
diff = True
|
|
||||||
break
|
|
||||||
if diff:
|
|
||||||
break
|
|
||||||
|
|
||||||
return diff
|
|
||||||
|
|
||||||
def _set_config(self, want, have, module):
|
|
||||||
# Set the interface config based on the want and have config
|
|
||||||
commands = []
|
|
||||||
interface = 'interface ' + want['name']
|
|
||||||
|
|
||||||
# To handle L3 IPV4 configuration
|
|
||||||
if want.get("ipv4"):
|
|
||||||
for each in want.get("ipv4"):
|
|
||||||
if each.get('address') != 'dhcp':
|
|
||||||
ip_addr_want = validate_n_expand_ipv4(module, each)
|
|
||||||
each['address'] = ip_addr_want
|
|
||||||
|
|
||||||
# Get the diff b/w want and have
|
|
||||||
want_dict = dict_to_set(want)
|
|
||||||
have_dict = dict_to_set(have)
|
|
||||||
|
|
||||||
# To handle L3 IPV4 configuration
|
|
||||||
want_ipv4 = dict(want_dict).get('ipv4')
|
|
||||||
have_ipv4 = dict(have_dict).get('ipv4')
|
|
||||||
if want_ipv4:
|
|
||||||
if have_ipv4:
|
|
||||||
diff_ipv4 = set(want_ipv4) - set(dict(have_dict).get('ipv4'))
|
|
||||||
if diff_ipv4:
|
|
||||||
diff_ipv4 = diff_ipv4 if self.verify_diff_again(want_ipv4, have_ipv4) else ()
|
|
||||||
else:
|
|
||||||
diff_ipv4 = set(want_ipv4)
|
|
||||||
for each in diff_ipv4:
|
|
||||||
ipv4_dict = dict(each)
|
|
||||||
if ipv4_dict.get('address') != 'dhcp':
|
|
||||||
cmd = "ipv4 address {0}".format(ipv4_dict['address'])
|
|
||||||
if ipv4_dict.get("secondary"):
|
|
||||||
cmd += " secondary"
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
|
|
||||||
# To handle L3 IPV6 configuration
|
|
||||||
want_ipv6 = dict(want_dict).get('ipv6')
|
|
||||||
have_ipv6 = dict(have_dict).get('ipv6')
|
|
||||||
if want_ipv6:
|
|
||||||
if have_ipv6:
|
|
||||||
diff_ipv6 = set(want_ipv6) - set(have_ipv6)
|
|
||||||
else:
|
|
||||||
diff_ipv6 = set(want_ipv6)
|
|
||||||
for each in diff_ipv6:
|
|
||||||
ipv6_dict = dict(each)
|
|
||||||
validate_ipv6(ipv6_dict.get('address'), module)
|
|
||||||
cmd = "ipv6 address {0}".format(ipv6_dict.get('address'))
|
|
||||||
add_command_to_config_list(interface, cmd, commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _clear_config(self, want, have):
|
|
||||||
# Delete the interface config based on the want and have config
|
|
||||||
count = 0
|
|
||||||
commands = []
|
|
||||||
if want.get('name'):
|
|
||||||
interface = 'interface ' + want['name']
|
|
||||||
else:
|
|
||||||
interface = 'interface ' + have['name']
|
|
||||||
|
|
||||||
if have.get('ipv4') and want.get('ipv4'):
|
|
||||||
for each in have.get('ipv4'):
|
|
||||||
if each.get('secondary') and not (want.get('ipv4')[count].get('secondary')):
|
|
||||||
cmd = 'ipv4 address {0} secondary'.format(each.get('address'))
|
|
||||||
remove_command_from_config_list(interface, cmd, commands)
|
|
||||||
count += 1
|
|
||||||
if have.get('ipv4') and not (want.get('ipv4')):
|
|
||||||
remove_command_from_config_list(interface, 'ipv4 address', commands)
|
|
||||||
if have.get('ipv6') and not (want.get('ipv6')):
|
|
||||||
remove_command_from_config_list(interface, 'ipv6 address', commands)
|
|
||||||
|
|
||||||
return commands
|
|
@ -1,172 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_lacp class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.common.utils import dict_diff
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import remove_empties
|
|
||||||
from ansible.module_utils.network.iosxr. \
|
|
||||||
utils.utils import flatten_dict
|
|
||||||
|
|
||||||
|
|
||||||
class Lacp(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_lacp class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'lacp',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Lacp, self).__init__(module)
|
|
||||||
|
|
||||||
def get_lacp_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
lacp_facts = facts['ansible_network_resources'].get('lacp')
|
|
||||||
if not lacp_facts:
|
|
||||||
return {}
|
|
||||||
return lacp_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
commands = list()
|
|
||||||
warnings = list()
|
|
||||||
|
|
||||||
existing_lacp_facts = self.get_lacp_facts()
|
|
||||||
commands.extend(self.set_config(existing_lacp_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_lacp_facts = self.get_lacp_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_lacp_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_lacp_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_lacp_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params.get('config')
|
|
||||||
if not want:
|
|
||||||
want = {}
|
|
||||||
have = existing_lacp_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params['state']
|
|
||||||
if state in ('merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'deleted':
|
|
||||||
commands = self._state_deleted(want, have)
|
|
||||||
elif state == 'merged':
|
|
||||||
commands = self._state_merged(want, have)
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands = self._state_replaced(want, have)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_replaced(want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
commands.extend(
|
|
||||||
Lacp._state_deleted(want, have)
|
|
||||||
)
|
|
||||||
|
|
||||||
commands.extend(
|
|
||||||
Lacp._state_merged(want, have)
|
|
||||||
)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_merged(want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
updates = dict_diff(have, want)
|
|
||||||
if updates:
|
|
||||||
for key, value in iteritems(flatten_dict(remove_empties(updates['system']))):
|
|
||||||
commands.append('lacp system {0} {1}'.format(key.replace('address', 'mac'), value))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_deleted(want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for x in [k for k in have.get('system', {}) if k not in remove_empties(want.get('system', {}))]:
|
|
||||||
commands.append('no lacp system {0}'.format(x))
|
|
||||||
|
|
||||||
return commands
|
|
@ -1,264 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_lacp_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.common.utils import dict_diff, remove_empties
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import search_obj_in_list
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import dict_delete, pad_commands, flatten_dict
|
|
||||||
|
|
||||||
|
|
||||||
class Lacp_interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_lacp_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'lacp_interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Lacp_interfaces, self).__init__(module)
|
|
||||||
|
|
||||||
def get_lacp_interfaces_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
lacp_interfaces_facts = facts['ansible_network_resources'].get('lacp_interfaces')
|
|
||||||
if not lacp_interfaces_facts:
|
|
||||||
return []
|
|
||||||
return lacp_interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
commands = list()
|
|
||||||
warnings = list()
|
|
||||||
|
|
||||||
existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
|
|
||||||
commands.extend(self.set_config(existing_lacp_interfaces_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_lacp_interfaces_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_lacp_interfaces_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_lacp_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
have = existing_lacp_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
state = self._module.params['state']
|
|
||||||
|
|
||||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands.extend(
|
|
||||||
Lacp_interfaces._state_overridden(
|
|
||||||
want, have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif state == 'deleted':
|
|
||||||
if not want:
|
|
||||||
for intf in have:
|
|
||||||
commands.extend(
|
|
||||||
Lacp_interfaces._state_deleted(
|
|
||||||
{'name': intf['name']},
|
|
||||||
intf
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for item in want:
|
|
||||||
obj_in_have = search_obj_in_list(item['name'], have)
|
|
||||||
commands.extend(
|
|
||||||
Lacp_interfaces._state_deleted(
|
|
||||||
item, obj_in_have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
for item in want:
|
|
||||||
name = item['name']
|
|
||||||
obj_in_have = search_obj_in_list(name, have)
|
|
||||||
|
|
||||||
if state == 'merged':
|
|
||||||
commands.extend(
|
|
||||||
Lacp_interfaces._state_merged(
|
|
||||||
item, obj_in_have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands.extend(
|
|
||||||
Lacp_interfaces._state_replaced(
|
|
||||||
item, obj_in_have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_replaced(want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
replaced_commands = []
|
|
||||||
merged_commands = []
|
|
||||||
|
|
||||||
if have:
|
|
||||||
replaced_commands = Lacp_interfaces._state_deleted(want, have)
|
|
||||||
|
|
||||||
merged_commands = Lacp_interfaces._state_merged(want, have)
|
|
||||||
|
|
||||||
if merged_commands and replaced_commands:
|
|
||||||
del merged_commands[0]
|
|
||||||
|
|
||||||
commands.extend(replaced_commands)
|
|
||||||
commands.extend(merged_commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_overridden(want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
for intf in have:
|
|
||||||
intf_in_want = search_obj_in_list(intf['name'], want)
|
|
||||||
if not intf_in_want:
|
|
||||||
commands.extend(Lacp_interfaces._state_deleted({'name': intf['name']}, intf))
|
|
||||||
|
|
||||||
for intf in want:
|
|
||||||
intf_in_have = search_obj_in_list(intf['name'], have)
|
|
||||||
commands.extend(Lacp_interfaces._state_replaced(intf, intf_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_merged(want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if not have:
|
|
||||||
have = {'name': want['name']}
|
|
||||||
|
|
||||||
for key, value in iteritems(flatten_dict(remove_empties(dict_diff(have, want)))):
|
|
||||||
commands.append(Lacp_interfaces._compute_commands(key, value))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, want['name'])
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _state_deleted(want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for key, value in iteritems(flatten_dict(dict_delete(have, remove_empties(want)))):
|
|
||||||
commands.append(Lacp_interfaces._compute_commands(key, value, remove=True))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, have['name'])
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _compute_commands(key, value, remove=False):
|
|
||||||
if key == "churn_logging":
|
|
||||||
cmd = "lacp churn logging {0}".format(value)
|
|
||||||
|
|
||||||
elif key == "collector_max_delay":
|
|
||||||
cmd = "lacp collector-max-delay {0}".format(value)
|
|
||||||
|
|
||||||
elif key == "period":
|
|
||||||
cmd = "lacp period {0}".format(value)
|
|
||||||
|
|
||||||
elif key == "switchover_suppress_flaps":
|
|
||||||
cmd = "lacp switchover suppress-flaps {0}".format(value)
|
|
||||||
|
|
||||||
elif key == 'mac':
|
|
||||||
cmd = "lacp system mac {0}".format(value)
|
|
||||||
|
|
||||||
elif key == 'priority':
|
|
||||||
cmd = "lacp system priority {0}".format(value)
|
|
||||||
|
|
||||||
if remove:
|
|
||||||
cmd = "no " + cmd
|
|
||||||
|
|
||||||
return cmd
|
|
@ -1,386 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_lag_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.network.common.utils \
|
|
||||||
import (
|
|
||||||
to_list,
|
|
||||||
dict_diff,
|
|
||||||
remove_empties,
|
|
||||||
search_obj_in_list,
|
|
||||||
param_list_to_dict
|
|
||||||
)
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils \
|
|
||||||
import (
|
|
||||||
diff_list_of_dicts,
|
|
||||||
pad_commands,
|
|
||||||
flatten_dict,
|
|
||||||
dict_delete,
|
|
||||||
normalize_interface
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Lag_interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_lag_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'lag_interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Lag_interfaces, self).__init__(module)
|
|
||||||
|
|
||||||
def get_lag_interfaces_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(
|
|
||||||
self.gather_subset, self.gather_network_resources)
|
|
||||||
lag_interfaces_facts = facts['ansible_network_resources'].get(
|
|
||||||
'lag_interfaces')
|
|
||||||
if not lag_interfaces_facts:
|
|
||||||
return []
|
|
||||||
return lag_interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
warnings = list()
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
existing_lag_interfaces_facts = self.get_lag_interfaces_facts()
|
|
||||||
commands.extend(self.set_config(existing_lag_interfaces_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_lag_interfaces_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_lag_interfaces_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_lag_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
if want:
|
|
||||||
for item in want:
|
|
||||||
item['name'] = normalize_interface(item['name'])
|
|
||||||
if 'members' in want and want['members']:
|
|
||||||
for item in want['members']:
|
|
||||||
item.update({
|
|
||||||
'member': normalize_interface(item['member']),
|
|
||||||
'mode': item['mode']
|
|
||||||
})
|
|
||||||
have = existing_lag_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params['state']
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands.extend(self._state_overridden(want, have))
|
|
||||||
|
|
||||||
elif state == 'deleted':
|
|
||||||
commands.extend(self._state_deleted(want, have))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Instead of passing entire want and have
|
|
||||||
# list of dictionaries to the respective
|
|
||||||
# _state_* methods we are passing the want
|
|
||||||
# and have dictionaries per interface
|
|
||||||
for item in want:
|
|
||||||
name = item['name']
|
|
||||||
obj_in_have = search_obj_in_list(name, have)
|
|
||||||
|
|
||||||
if state == 'merged':
|
|
||||||
commands.extend(self._state_merged(item, obj_in_have))
|
|
||||||
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands.extend(self._state_replaced(item, obj_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if have:
|
|
||||||
commands.extend(self._render_bundle_del_commands(want, have))
|
|
||||||
commands.extend(self._render_bundle_updates(want, have))
|
|
||||||
|
|
||||||
if commands or have == {}:
|
|
||||||
pad_commands(commands, want['name'])
|
|
||||||
|
|
||||||
if have:
|
|
||||||
commands.extend(self._render_interface_del_commands(want, have))
|
|
||||||
commands.extend(self._render_interface_updates(want, have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
for have_intf in have:
|
|
||||||
intf_in_want = search_obj_in_list(have_intf['name'], want)
|
|
||||||
if not intf_in_want:
|
|
||||||
commands.extend(self._purge_attribs(have_intf))
|
|
||||||
|
|
||||||
for intf in want:
|
|
||||||
intf_in_have = search_obj_in_list(intf['name'], have)
|
|
||||||
commands.extend(self._state_replaced(intf, intf_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
commands.extend(self._render_bundle_updates(want, have))
|
|
||||||
|
|
||||||
if commands or have == {}:
|
|
||||||
pad_commands(commands, want['name'])
|
|
||||||
|
|
||||||
commands.extend(self._render_interface_updates(want, have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if not want:
|
|
||||||
for item in have:
|
|
||||||
commands.extend(self._purge_attribs(intf=item))
|
|
||||||
else:
|
|
||||||
for item in want:
|
|
||||||
name = item['name']
|
|
||||||
obj_in_have = search_obj_in_list(name, have)
|
|
||||||
if not obj_in_have:
|
|
||||||
self._module.fail_json(
|
|
||||||
msg=('interface {0} does not exist'.format(name)))
|
|
||||||
commands.extend(self._purge_attribs(intf=obj_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_bundle_updates(self, want, have):
|
|
||||||
""" The command generator for updates to bundles
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to update bundles
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if not have:
|
|
||||||
have = {'name': want['name']}
|
|
||||||
|
|
||||||
want_copy = deepcopy(want)
|
|
||||||
have_copy = deepcopy(have)
|
|
||||||
|
|
||||||
want_copy.pop('members', [])
|
|
||||||
have_copy.pop('members', [])
|
|
||||||
|
|
||||||
bundle_updates = dict_diff(have_copy, want_copy)
|
|
||||||
|
|
||||||
if bundle_updates:
|
|
||||||
for key, value in iteritems(
|
|
||||||
flatten_dict(remove_empties(bundle_updates))):
|
|
||||||
commands.append(self._compute_commands(key=key, value=value))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_interface_updates(self, want, have):
|
|
||||||
""" The command generator for updates to member
|
|
||||||
interfaces
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to update member
|
|
||||||
interfaces
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if not have:
|
|
||||||
have = {'name': want['name']}
|
|
||||||
|
|
||||||
member_diff = diff_list_of_dicts(want['members'],
|
|
||||||
have.get('members', []))
|
|
||||||
|
|
||||||
for diff in member_diff:
|
|
||||||
diff_cmd = []
|
|
||||||
bundle_cmd = 'bundle id {0}'.format(
|
|
||||||
want['name'].split('Bundle-Ether')[1])
|
|
||||||
if diff.get('mode'):
|
|
||||||
bundle_cmd += ' mode {0}'.format(diff.get('mode'))
|
|
||||||
diff_cmd.append(bundle_cmd)
|
|
||||||
pad_commands(diff_cmd, diff['member'])
|
|
||||||
commands.extend(diff_cmd)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_bundle_del_commands(self, want, have):
|
|
||||||
""" The command generator for delete commands
|
|
||||||
w.r.t bundles
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to update member
|
|
||||||
interfaces
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if not want:
|
|
||||||
want = {'name': have['name']}
|
|
||||||
|
|
||||||
want_copy = deepcopy(want)
|
|
||||||
have_copy = deepcopy(have)
|
|
||||||
want_copy.pop('members', [])
|
|
||||||
have_copy.pop('members', [])
|
|
||||||
|
|
||||||
to_delete = dict_delete(have_copy, remove_empties(want_copy))
|
|
||||||
if to_delete:
|
|
||||||
for key, value in iteritems(flatten_dict(
|
|
||||||
remove_empties(to_delete))):
|
|
||||||
commands.append(
|
|
||||||
self._compute_commands(key=key, value=value, remove=True))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_interface_del_commands(self, want, have):
|
|
||||||
""" The command generator for delete commands
|
|
||||||
w.r.t member interfaces
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to update member
|
|
||||||
interfaces
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if not want:
|
|
||||||
want = {}
|
|
||||||
have_members = have.get('members')
|
|
||||||
|
|
||||||
if have_members:
|
|
||||||
have_members = param_list_to_dict(deepcopy(have_members), unique_key='member')
|
|
||||||
want_members = param_list_to_dict(deepcopy(want).get('members', []), unique_key='member')
|
|
||||||
|
|
||||||
for key in have_members:
|
|
||||||
if key not in want_members:
|
|
||||||
member_cmd = ['no bundle id']
|
|
||||||
pad_commands(member_cmd, key)
|
|
||||||
commands.extend(member_cmd)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _purge_attribs(self, intf):
|
|
||||||
""" The command generator for purging attributes
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to purge attributes
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
have_copy = deepcopy(intf)
|
|
||||||
members = have_copy.pop('members', [])
|
|
||||||
|
|
||||||
to_delete = dict_delete(have_copy, remove_empties({'name': have_copy['name']}))
|
|
||||||
if to_delete:
|
|
||||||
for key, value in iteritems(flatten_dict(remove_empties(to_delete))):
|
|
||||||
commands.append(self._compute_commands(key=key, value=value, remove=True))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, intf['name'])
|
|
||||||
|
|
||||||
if members:
|
|
||||||
members = param_list_to_dict(deepcopy(members), unique_key='member')
|
|
||||||
for key in members:
|
|
||||||
member_cmd = ['no bundle id']
|
|
||||||
pad_commands(member_cmd, key)
|
|
||||||
commands.extend(member_cmd)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _compute_commands(self, key, value, remove=False):
|
|
||||||
""" The method generates LAG commands based on the
|
|
||||||
key, value passed. When remove is set to True,
|
|
||||||
the command is negated.
|
|
||||||
:rtype: str
|
|
||||||
:returns: a command based on the `key`, `value` pair
|
|
||||||
passed and the value of `remove`
|
|
||||||
"""
|
|
||||||
if key == "mode":
|
|
||||||
cmd = "lacp mode {0}".format(value)
|
|
||||||
|
|
||||||
elif key == "load_balancing_hash":
|
|
||||||
cmd = "bundle load-balancing hash {0}".format(value)
|
|
||||||
|
|
||||||
elif key == "max_active":
|
|
||||||
cmd = "bundle maximum-active links {0}".format(value)
|
|
||||||
|
|
||||||
elif key == "min_active":
|
|
||||||
cmd = "bundle minimum-active links {0}".format(value)
|
|
||||||
|
|
||||||
if remove:
|
|
||||||
cmd = "no {0}".format(cmd)
|
|
||||||
|
|
||||||
return cmd
|
|
@ -1,188 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_lldp_global class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list, dict_diff, remove_empties
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.iosxr. \
|
|
||||||
utils.utils import flatten_dict, dict_delete
|
|
||||||
|
|
||||||
|
|
||||||
class Lldp_global(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_lldp class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'lldp_global',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Lldp_global, self).__init__(module)
|
|
||||||
|
|
||||||
def get_lldp_global_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
lldp_facts = facts['ansible_network_resources'].get('lldp_global')
|
|
||||||
if not lldp_facts:
|
|
||||||
return {}
|
|
||||||
return lldp_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
warnings = list()
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
existing_lldp_global_facts = self.get_lldp_global_facts()
|
|
||||||
commands.extend(self.set_config(existing_lldp_global_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_lldp_global_facts = self.get_lldp_global_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_lldp_global_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_lldp_global_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_lldp_global_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
if not want and self._module.params['state'] == 'deleted':
|
|
||||||
want = {}
|
|
||||||
have = existing_lldp_global_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params['state']
|
|
||||||
if state in ('merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'deleted':
|
|
||||||
commands = self._state_deleted(want, have)
|
|
||||||
elif state == 'merged':
|
|
||||||
commands = self._state_merged(want, have)
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands = self._state_replaced(want, have)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
commands.extend(
|
|
||||||
self._state_deleted(want, have)
|
|
||||||
)
|
|
||||||
|
|
||||||
commands.extend(
|
|
||||||
self._state_merged(want, have)
|
|
||||||
)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
updates = dict_diff(have, want)
|
|
||||||
if updates:
|
|
||||||
for key, value in iteritems(flatten_dict(remove_empties(updates))):
|
|
||||||
commands.append(self._compute_commands(key, value))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
for key, value in iteritems(flatten_dict(dict_delete(have, remove_empties(want)))):
|
|
||||||
cmd = self._compute_commands(key, value, remove=True)
|
|
||||||
if cmd:
|
|
||||||
commands.append(cmd)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _compute_commands(self, key, value=None, remove=False):
|
|
||||||
if key in ['holdtime', 'reinit', 'timer']:
|
|
||||||
cmd = 'lldp {0} {1}'.format(key, value)
|
|
||||||
if remove:
|
|
||||||
return 'no {0}'.format(cmd)
|
|
||||||
else:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
elif key == 'subinterfaces':
|
|
||||||
cmd = 'lldp subinterfaces enable'
|
|
||||||
if (value and not remove):
|
|
||||||
return cmd
|
|
||||||
elif (not value and not remove) or (value and remove):
|
|
||||||
return 'no {0}'.format(cmd)
|
|
||||||
|
|
||||||
else:
|
|
||||||
cmd = 'lldp tlv-select {0} disable'.format(key.replace('_', '-'))
|
|
||||||
if (not value and not remove):
|
|
||||||
return cmd
|
|
||||||
elif (value and not remove) or (not value and remove):
|
|
||||||
return 'no {0}'.format(cmd)
|
|
@ -1,247 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_lldp_interfaces class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list, search_obj_in_list, dict_diff, remove_empties
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import dict_delete, pad_commands, flatten_dict
|
|
||||||
|
|
||||||
|
|
||||||
class Lldp_interfaces(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_lldp_interfaces class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
'!all',
|
|
||||||
'!min',
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
'lldp_interfaces',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Lldp_interfaces, self).__init__(module)
|
|
||||||
|
|
||||||
def get_lldp_interfaces_facts(self):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
|
|
||||||
lldp_interfaces_facts = facts['ansible_network_resources'].get('lldp_interfaces')
|
|
||||||
if not lldp_interfaces_facts:
|
|
||||||
return []
|
|
||||||
return lldp_interfaces_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {'changed': False}
|
|
||||||
warnings = list()
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
|
|
||||||
commands.extend(self.set_config(existing_lldp_interfaces_facts))
|
|
||||||
if commands:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result['changed'] = True
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
|
|
||||||
|
|
||||||
result['before'] = existing_lldp_interfaces_facts
|
|
||||||
if result['changed']:
|
|
||||||
result['after'] = changed_lldp_interfaces_facts
|
|
||||||
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_lldp_interfaces_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params['config']
|
|
||||||
have = existing_lldp_interfaces_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params['state']
|
|
||||||
commands = []
|
|
||||||
if state in ('overridden', 'merged', 'replaced') and not want:
|
|
||||||
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
|
|
||||||
|
|
||||||
if state == 'overridden':
|
|
||||||
commands.extend(
|
|
||||||
self._state_overridden(
|
|
||||||
want, have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif state == 'deleted':
|
|
||||||
if not want:
|
|
||||||
for intf in have:
|
|
||||||
commands.extend(
|
|
||||||
self._state_deleted(
|
|
||||||
{'name': intf['name']},
|
|
||||||
intf
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for item in want:
|
|
||||||
obj_in_have = search_obj_in_list(item['name'], have)
|
|
||||||
commands.extend(
|
|
||||||
self._state_deleted(
|
|
||||||
item, obj_in_have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
for item in want:
|
|
||||||
name = item['name']
|
|
||||||
obj_in_have = search_obj_in_list(name, have)
|
|
||||||
|
|
||||||
if state == 'merged':
|
|
||||||
commands.extend(
|
|
||||||
self._state_merged(
|
|
||||||
item, obj_in_have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif state == 'replaced':
|
|
||||||
commands.extend(
|
|
||||||
self._state_replaced(
|
|
||||||
item, obj_in_have
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
replaced_commands = []
|
|
||||||
merged_commands = []
|
|
||||||
|
|
||||||
if have:
|
|
||||||
replaced_commands = self._state_deleted(want, have)
|
|
||||||
|
|
||||||
merged_commands = self._state_merged(want, have)
|
|
||||||
|
|
||||||
if merged_commands and replaced_commands:
|
|
||||||
del merged_commands[0]
|
|
||||||
|
|
||||||
commands.extend(replaced_commands)
|
|
||||||
commands.extend(merged_commands)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for intf in have:
|
|
||||||
intf_in_want = search_obj_in_list(intf['name'], want)
|
|
||||||
if not intf_in_want:
|
|
||||||
commands.extend(self._state_deleted({'name': intf['name']}, intf))
|
|
||||||
|
|
||||||
for intf in want:
|
|
||||||
intf_in_have = search_obj_in_list(intf['name'], have)
|
|
||||||
commands.extend(self._state_replaced(intf, intf_in_have))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if not have:
|
|
||||||
have = {'name': want['name']}
|
|
||||||
|
|
||||||
for key, value in iteritems(flatten_dict(remove_empties(dict_diff(have, want)))):
|
|
||||||
commands.append(self._compute_commands(key, value))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, want['name'])
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for key, value in iteritems(flatten_dict(dict_delete(have, remove_empties(want)))):
|
|
||||||
commands.append(self._compute_commands(key, value, remove=True))
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
pad_commands(commands, have['name'])
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _compute_commands(self, key, value=None, remove=False):
|
|
||||||
if key == 'mac_address':
|
|
||||||
cmd = 'lldp destination mac-address {0}'.format(value)
|
|
||||||
if remove:
|
|
||||||
return 'no {0}'.format(cmd)
|
|
||||||
else:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
else:
|
|
||||||
cmd = 'lldp {0} disable'.format(key)
|
|
||||||
if (not value and not remove):
|
|
||||||
return cmd
|
|
||||||
elif (value and not remove) or (not value and remove):
|
|
||||||
return 'no {0}'.format(cmd)
|
|
@ -1,560 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_static_routes class
|
|
||||||
It is in this file where the current configuration (as dict)
|
|
||||||
is compared to the provided configuration (as dict) and the command set
|
|
||||||
necessary to bring the current configuration to it's desired end-state is
|
|
||||||
created
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import (
|
|
||||||
search_obj_in_list,
|
|
||||||
remove_empties,
|
|
||||||
dict_diff,
|
|
||||||
dict_merge,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Static_routes(ConfigBase):
|
|
||||||
"""
|
|
||||||
The iosxr_static_routes class
|
|
||||||
"""
|
|
||||||
|
|
||||||
gather_subset = [
|
|
||||||
"!all",
|
|
||||||
"!min",
|
|
||||||
]
|
|
||||||
|
|
||||||
gather_network_resources = [
|
|
||||||
"static_routes",
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Static_routes, self).__init__(module)
|
|
||||||
|
|
||||||
def get_static_routes_facts(self, data=None):
|
|
||||||
""" Get the 'facts' (the current configuration)
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The current configuration as a dictionary
|
|
||||||
"""
|
|
||||||
facts, _warnings = Facts(self._module).get_facts(
|
|
||||||
self.gather_subset, self.gather_network_resources, data=data
|
|
||||||
)
|
|
||||||
static_routes_facts = facts["ansible_network_resources"].get("static_routes")
|
|
||||||
if not static_routes_facts:
|
|
||||||
return []
|
|
||||||
return static_routes_facts
|
|
||||||
|
|
||||||
def execute_module(self):
|
|
||||||
""" Execute the module
|
|
||||||
|
|
||||||
:rtype: A dictionary
|
|
||||||
:returns: The result from module execution
|
|
||||||
"""
|
|
||||||
result = {"changed": False}
|
|
||||||
warnings = list()
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
existing_static_routes_facts = self.get_static_routes_facts()
|
|
||||||
else:
|
|
||||||
existing_static_routes_facts = []
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES or self.state == "rendered":
|
|
||||||
commands.extend(self.set_config(existing_static_routes_facts))
|
|
||||||
|
|
||||||
if commands and self.state in self.ACTION_STATES:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
self._connection.edit_config(commands)
|
|
||||||
result["changed"] = True
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
result["commands"] = commands
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES or self.state == "gathered":
|
|
||||||
changed_static_routes_facts = self.get_static_routes_facts()
|
|
||||||
|
|
||||||
elif self.state == "rendered":
|
|
||||||
result["rendered"] = commands
|
|
||||||
|
|
||||||
elif self.state == "parsed":
|
|
||||||
running_config = self._module.params["running_config"]
|
|
||||||
if not running_config:
|
|
||||||
self._module.fail_json(
|
|
||||||
msg="value of running_config parameter must not be empty for state parsed"
|
|
||||||
)
|
|
||||||
result["parsed"] = self.get_static_routes_facts(data=running_config)
|
|
||||||
|
|
||||||
if self.state in self.ACTION_STATES:
|
|
||||||
result["before"] = existing_static_routes_facts
|
|
||||||
if result["changed"]:
|
|
||||||
result["after"] = changed_static_routes_facts
|
|
||||||
|
|
||||||
elif self.state == "gathered":
|
|
||||||
result["gathered"] = changed_static_routes_facts
|
|
||||||
|
|
||||||
result["warnings"] = warnings
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_config(self, existing_static_routes_facts):
|
|
||||||
""" Collect the configuration from the args passed to the module,
|
|
||||||
collect the current configuration (as a dict from facts)
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
want = self._module.params["config"]
|
|
||||||
have = existing_static_routes_facts
|
|
||||||
resp = self.set_state(want, have)
|
|
||||||
return to_list(resp)
|
|
||||||
|
|
||||||
def set_state(self, want, have):
|
|
||||||
""" Select the appropriate function based on the state provided
|
|
||||||
|
|
||||||
:param want: the desired configuration as a dictionary
|
|
||||||
:param have: the current configuration as a dictionary
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
state = self._module.params["state"]
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
if state in ("overridden", "merged", "replaced", "rendered") and not want:
|
|
||||||
self._module.fail_json(
|
|
||||||
msg="value of config parameter must not be empty for state {0}".format(
|
|
||||||
state
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if state == "overridden":
|
|
||||||
commands.extend(self._state_overridden(want, have))
|
|
||||||
|
|
||||||
elif state == "deleted":
|
|
||||||
if not want:
|
|
||||||
if len(have) >= 1:
|
|
||||||
return "no router static"
|
|
||||||
|
|
||||||
else:
|
|
||||||
for w_item in want:
|
|
||||||
obj_in_have = self._find_vrf(w_item, have)
|
|
||||||
if obj_in_have:
|
|
||||||
commands.extend(
|
|
||||||
self._state_deleted(remove_empties(w_item), obj_in_have)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
for w_item in want:
|
|
||||||
obj_in_have = self._find_vrf(w_item, have)
|
|
||||||
if state == "merged" or self.state == "rendered":
|
|
||||||
commands.extend(
|
|
||||||
self._state_merged(remove_empties(w_item), obj_in_have)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif state == "replaced":
|
|
||||||
commands.extend(
|
|
||||||
self._state_replaced(remove_empties(w_item), obj_in_have)
|
|
||||||
)
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
commands.insert(0, "router static")
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_replaced(self, want, have):
|
|
||||||
""" The command generator when state is replaced
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for want_afi in want.get("address_families", []):
|
|
||||||
have_afi = (
|
|
||||||
self.find_af_context(want_afi, have.get("address_families", [])) or {}
|
|
||||||
)
|
|
||||||
update_commands = []
|
|
||||||
for want_route in want_afi.get("routes", []):
|
|
||||||
have_route = (
|
|
||||||
search_obj_in_list(
|
|
||||||
want_route["dest"], have_afi.get("routes", []), key="dest"
|
|
||||||
)
|
|
||||||
or {}
|
|
||||||
)
|
|
||||||
|
|
||||||
rotated_have_next_hops = self.rotate_next_hops(
|
|
||||||
have_route.get("next_hops", {})
|
|
||||||
)
|
|
||||||
rotated_want_next_hops = self.rotate_next_hops(
|
|
||||||
want_route.get("next_hops", {})
|
|
||||||
)
|
|
||||||
|
|
||||||
for key in rotated_have_next_hops.keys():
|
|
||||||
if key not in rotated_want_next_hops:
|
|
||||||
cmd = "no {0}".format(want_route["dest"])
|
|
||||||
for item in key:
|
|
||||||
if "." in item or ":" in item or "/" in item:
|
|
||||||
cmd += " {0}".format(item)
|
|
||||||
else:
|
|
||||||
cmd += " vrf {0}".format(item)
|
|
||||||
update_commands.append(cmd)
|
|
||||||
|
|
||||||
for key, value in iteritems(rotated_want_next_hops):
|
|
||||||
if key in rotated_have_next_hops:
|
|
||||||
existing = True
|
|
||||||
have_exit_point_attribs = rotated_have_next_hops[key]
|
|
||||||
|
|
||||||
else:
|
|
||||||
existing = False
|
|
||||||
have_exit_point_attribs = {}
|
|
||||||
|
|
||||||
updates = dict_diff(have_exit_point_attribs, value)
|
|
||||||
|
|
||||||
if updates or not existing:
|
|
||||||
update_commands.append(
|
|
||||||
self._compute_commands(
|
|
||||||
dest=want_route["dest"], next_hop=key, updates=updates
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if update_commands:
|
|
||||||
update_commands.insert(
|
|
||||||
0,
|
|
||||||
"address-family {0} {1}".format(want_afi["afi"], want_afi["safi"]),
|
|
||||||
)
|
|
||||||
commands.extend(update_commands)
|
|
||||||
|
|
||||||
if "vrf" in want and update_commands:
|
|
||||||
commands.insert(0, "vrf {0}".format(want["vrf"]))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_overridden(self, want, have):
|
|
||||||
""" The command generator when state is overridden
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to migrate the current configuration
|
|
||||||
to the desired configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
# Iterate through all the entries, i.e., VRFs and Global entry in have
|
|
||||||
# and fully remove the ones that are not present in want and then call
|
|
||||||
# replaced
|
|
||||||
|
|
||||||
for h_item in have:
|
|
||||||
w_item = self._find_vrf(h_item, want)
|
|
||||||
|
|
||||||
# Delete all the top-level keys (VRFs/Global Route Entry) that are
|
|
||||||
# not specified in want.
|
|
||||||
if not w_item:
|
|
||||||
if "vrf" in h_item:
|
|
||||||
commands.append("no vrf {0}".format(h_item["vrf"]))
|
|
||||||
else:
|
|
||||||
for have_afi in h_item.get("address_families", []):
|
|
||||||
commands.append(
|
|
||||||
"no address-family {0} {1}".format(
|
|
||||||
have_afi["afi"], have_afi["safi"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# For VRFs/Global Entry present in want, we also need to delete extraneous routes
|
|
||||||
# from them. We cannot reuse `_state_replaced` for this purpose since its scope is
|
|
||||||
# limited to replacing a single `dest`.
|
|
||||||
else:
|
|
||||||
del_cmds = []
|
|
||||||
for have_afi in h_item.get("address_families", []):
|
|
||||||
want_afi = (
|
|
||||||
self.find_af_context(
|
|
||||||
have_afi, w_item.get("address_families", [])
|
|
||||||
)
|
|
||||||
or {}
|
|
||||||
)
|
|
||||||
update_commands = []
|
|
||||||
for h_route in have_afi.get("routes", []):
|
|
||||||
w_route = (
|
|
||||||
search_obj_in_list(
|
|
||||||
h_route["dest"], want_afi.get("routes", []), key="dest"
|
|
||||||
)
|
|
||||||
or {}
|
|
||||||
)
|
|
||||||
if not w_route:
|
|
||||||
update_commands.append("no {0}".format(h_route["dest"]))
|
|
||||||
|
|
||||||
if update_commands:
|
|
||||||
update_commands.insert(
|
|
||||||
0,
|
|
||||||
"address-family {0} {1}".format(
|
|
||||||
want_afi["afi"], want_afi["safi"]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
del_cmds.extend(update_commands)
|
|
||||||
|
|
||||||
if "vrf" in want and update_commands:
|
|
||||||
del_cmds.insert(0, "vrf {0}".format(want["vrf"]))
|
|
||||||
|
|
||||||
commands.extend(del_cmds)
|
|
||||||
|
|
||||||
# We finally call `_state_replaced` to replace exiting `dest` entries
|
|
||||||
# or add new ones as specified in want.
|
|
||||||
for w_item in want:
|
|
||||||
h_item = self._find_vrf(w_item, have)
|
|
||||||
commands.extend(self._state_replaced(remove_empties(w_item), h_item))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_merged(self, want, have):
|
|
||||||
""" The command generator when state is merged
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to merge the provided into
|
|
||||||
the current configuration
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
for want_afi in want.get("address_families", []):
|
|
||||||
have_afi = (
|
|
||||||
self.find_af_context(want_afi, have.get("address_families", [])) or {}
|
|
||||||
)
|
|
||||||
|
|
||||||
update_commands = []
|
|
||||||
for want_route in want_afi.get("routes", []):
|
|
||||||
have_route = (
|
|
||||||
search_obj_in_list(
|
|
||||||
want_route["dest"], have_afi.get("routes", []), key="dest"
|
|
||||||
)
|
|
||||||
or {}
|
|
||||||
)
|
|
||||||
|
|
||||||
# convert the next_hops list of dictionaries to dictionary of
|
|
||||||
# dictionaries with (`dest_vrf`, `forward_router_address`, `interface`) tuple
|
|
||||||
# being the key for each dictionary.
|
|
||||||
# a combination of these 3 attributes uniquely identifies a route entry.
|
|
||||||
# in case `dest_vrf` is not specified, `forward_router_address` and `interface`
|
|
||||||
# become the unique identifier
|
|
||||||
rotated_have_next_hops = self.rotate_next_hops(
|
|
||||||
have_route.get("next_hops", {})
|
|
||||||
)
|
|
||||||
rotated_want_next_hops = self.rotate_next_hops(
|
|
||||||
want_route.get("next_hops", {})
|
|
||||||
)
|
|
||||||
|
|
||||||
# for every dict in the want next_hops dictionaries, if the key
|
|
||||||
# is present in `rotated_have_next_hops`, we set `existing` to True,
|
|
||||||
# which means the the given want exit point exists and we run dict_diff
|
|
||||||
# on `value` which is basically all the other attributes of the exit point
|
|
||||||
# if the key is not present, it means that this is a new exit point
|
|
||||||
for key, value in iteritems(rotated_want_next_hops):
|
|
||||||
if key in rotated_have_next_hops:
|
|
||||||
existing = True
|
|
||||||
have_exit_point_attribs = rotated_have_next_hops[key]
|
|
||||||
|
|
||||||
else:
|
|
||||||
existing = False
|
|
||||||
have_exit_point_attribs = {}
|
|
||||||
|
|
||||||
updates = dict_diff(have_exit_point_attribs, value)
|
|
||||||
if updates or not existing:
|
|
||||||
update_commands.append(
|
|
||||||
self._compute_commands(
|
|
||||||
dest=want_route["dest"],
|
|
||||||
next_hop=key,
|
|
||||||
# dict_merge() is necessary to make sure that we
|
|
||||||
# don't end up overridding the entry and also to
|
|
||||||
# allow incremental updates
|
|
||||||
updates=dict_merge(
|
|
||||||
rotated_have_next_hops.get(key, {}), updates
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if update_commands:
|
|
||||||
update_commands.insert(
|
|
||||||
0,
|
|
||||||
"address-family {0} {1}".format(want_afi["afi"], want_afi["safi"]),
|
|
||||||
)
|
|
||||||
commands.extend(update_commands)
|
|
||||||
|
|
||||||
if "vrf" in want and update_commands:
|
|
||||||
commands.insert(0, "vrf {0}".format(want["vrf"]))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _state_deleted(self, want, have):
|
|
||||||
""" The command generator when state is deleted
|
|
||||||
|
|
||||||
:rtype: A list
|
|
||||||
:returns: the commands necessary to remove the current configuration
|
|
||||||
of the provided objects
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
if "address_families" not in want:
|
|
||||||
return ["no vrf {0}".format(want["vrf"])]
|
|
||||||
|
|
||||||
else:
|
|
||||||
for want_afi in want.get("address_families", []):
|
|
||||||
update_commands = []
|
|
||||||
have_afi = (
|
|
||||||
self.find_af_context(want_afi, have.get("address_families", []))
|
|
||||||
or {}
|
|
||||||
)
|
|
||||||
if have_afi:
|
|
||||||
if "routes" not in want_afi:
|
|
||||||
commands.append(
|
|
||||||
"no address-family {0} {1}".format(
|
|
||||||
have_afi["afi"], have_afi["safi"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for want_route in want_afi.get("routes", []):
|
|
||||||
have_route = (
|
|
||||||
search_obj_in_list(
|
|
||||||
want_route["dest"],
|
|
||||||
have_afi.get("routes", []),
|
|
||||||
key="dest",
|
|
||||||
)
|
|
||||||
or {}
|
|
||||||
)
|
|
||||||
if have_route:
|
|
||||||
if "next_hops" not in want_route:
|
|
||||||
update_commands.append(
|
|
||||||
"no {0}".format(want_route["dest"])
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rotated_have_next_hops = self.rotate_next_hops(
|
|
||||||
have_route.get("next_hops", {})
|
|
||||||
)
|
|
||||||
rotated_want_next_hops = self.rotate_next_hops(
|
|
||||||
want_route.get("next_hops", {})
|
|
||||||
)
|
|
||||||
|
|
||||||
for key in rotated_want_next_hops.keys():
|
|
||||||
if key in rotated_have_next_hops:
|
|
||||||
cmd = "no {0}".format(want_route["dest"])
|
|
||||||
for item in key:
|
|
||||||
if (
|
|
||||||
"." in item
|
|
||||||
or ":" in item
|
|
||||||
or "/" in item
|
|
||||||
):
|
|
||||||
cmd += " {0}".format(item)
|
|
||||||
else:
|
|
||||||
cmd += " vrf {0}".format(item)
|
|
||||||
update_commands.append(cmd)
|
|
||||||
|
|
||||||
if update_commands:
|
|
||||||
update_commands.insert(
|
|
||||||
0,
|
|
||||||
"address-family {0} {1}".format(
|
|
||||||
want_afi["afi"], want_afi["safi"]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
commands.extend(update_commands)
|
|
||||||
|
|
||||||
if "vrf" in want and commands:
|
|
||||||
commands.insert(0, "vrf {0}".format(want["vrf"]))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _find_vrf(self, item, entries):
|
|
||||||
""" This method iterates through the items
|
|
||||||
in `entries` and returns the object that
|
|
||||||
matches `item`.
|
|
||||||
|
|
||||||
:rtype: A dict
|
|
||||||
:returns: the obj in `entries` that matches `item`
|
|
||||||
"""
|
|
||||||
obj = {}
|
|
||||||
afi = item.get("vrf")
|
|
||||||
|
|
||||||
if afi:
|
|
||||||
obj = search_obj_in_list(afi, entries, key="vrf") or {}
|
|
||||||
else:
|
|
||||||
for x in entries:
|
|
||||||
if "vrf" not in remove_empties(x):
|
|
||||||
obj = x
|
|
||||||
break
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def find_af_context(self, want_af_context, have_address_families):
|
|
||||||
""" This method iterates through the have AFs
|
|
||||||
and returns the one that matches the want AF
|
|
||||||
|
|
||||||
:rtype: A dict
|
|
||||||
:returns: the corresponding AF in have AFs
|
|
||||||
that matches the want AF
|
|
||||||
"""
|
|
||||||
for have_af in have_address_families:
|
|
||||||
if (
|
|
||||||
have_af["afi"] == want_af_context["afi"]
|
|
||||||
and have_af["safi"] == want_af_context["safi"]
|
|
||||||
):
|
|
||||||
return have_af
|
|
||||||
|
|
||||||
def rotate_next_hops(self, next_hops):
|
|
||||||
""" This method iterates through the list of
|
|
||||||
next hops for a given destination network
|
|
||||||
and converts it to a dictionary of dictionaries.
|
|
||||||
Each dictionary has a primary key indicated by the
|
|
||||||
tuple of `dest_vrf`, `forward_router_address` and
|
|
||||||
`interface` and the value of this key is a dictionary
|
|
||||||
that contains all the other attributes of the next hop.
|
|
||||||
|
|
||||||
:rtype: A dict
|
|
||||||
:returns: A next_hops list in a dictionary of dictionaries format
|
|
||||||
"""
|
|
||||||
next_hops_dict = {}
|
|
||||||
|
|
||||||
for entry in next_hops:
|
|
||||||
entry = entry.copy()
|
|
||||||
key_list = []
|
|
||||||
|
|
||||||
for x in ["dest_vrf", "forward_router_address", "interface"]:
|
|
||||||
if entry.get(x):
|
|
||||||
key_list.append(entry.pop(x))
|
|
||||||
|
|
||||||
key = tuple(key_list)
|
|
||||||
next_hops_dict[key] = entry
|
|
||||||
|
|
||||||
return next_hops_dict
|
|
||||||
|
|
||||||
def _compute_commands(self, dest, next_hop, updates=None):
|
|
||||||
""" This method computes a static route entry command
|
|
||||||
from the specified `dest`, `next_hop` and `updates`
|
|
||||||
|
|
||||||
:rtype: A str
|
|
||||||
:returns: A platform specific static routes command
|
|
||||||
"""
|
|
||||||
if not updates:
|
|
||||||
updates = {}
|
|
||||||
|
|
||||||
command = dest
|
|
||||||
|
|
||||||
for x in next_hop:
|
|
||||||
if "." in x or ":" in x or "/" in x:
|
|
||||||
command += " {0}".format(x)
|
|
||||||
else:
|
|
||||||
command += " vrf {0}".format(x)
|
|
||||||
|
|
||||||
for key in sorted(updates):
|
|
||||||
if key == "admin_distance":
|
|
||||||
command += " {0}".format(updates[key])
|
|
||||||
else:
|
|
||||||
command += " {0} {1}".format(key, updates[key])
|
|
||||||
|
|
||||||
return command
|
|
@ -1,104 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr acl_interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class Acl_interfacesFacts(object):
|
|
||||||
""" The iosxr acl_interfaces fact class
|
|
||||||
"""
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = Acl_interfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for acl_interfaces
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
data = connection.get_config(flags='interface')
|
|
||||||
|
|
||||||
interfaces = data.split('interface ')
|
|
||||||
|
|
||||||
objs = []
|
|
||||||
for interface in interfaces:
|
|
||||||
obj = self.render_config(self.generated_spec, interface)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('acl_interfaces', None)
|
|
||||||
facts = {}
|
|
||||||
facts['acl_interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['acl_interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
config['access_groups'] = []
|
|
||||||
map_dir = {'ingress': 'in', 'egress': 'out'}
|
|
||||||
|
|
||||||
match = re.search(r'(?:preconfigure)*(?:\s*)(\S+)', conf, re.M)
|
|
||||||
if match:
|
|
||||||
config['name'] = match.group(1)
|
|
||||||
acls = {'ipv4': [], 'ipv6': []}
|
|
||||||
for item in conf.split('\n'):
|
|
||||||
item = item.strip()
|
|
||||||
if item.startswith('ipv4 access-group'):
|
|
||||||
acls['ipv4'].append(item)
|
|
||||||
elif item.startswith('ipv6 access-group'):
|
|
||||||
acls['ipv6'].append(item)
|
|
||||||
|
|
||||||
for key, value in iteritems(acls):
|
|
||||||
if value:
|
|
||||||
entry = {'afi': key, 'acls': []}
|
|
||||||
for item in value:
|
|
||||||
entry['acls'].append({'name': item.split()[2], 'direction': map_dir[item.split()[3]]})
|
|
||||||
config['access_groups'].append(entry)
|
|
||||||
|
|
||||||
config['access_groups'] = sorted(config['access_groups'], key=lambda i: i['afi'])
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
@ -1,380 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr acls fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from collections import deque
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.acls.acls import AclsArgs
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import isipaddress
|
|
||||||
|
|
||||||
PROTOCOL_OPTIONS = {
|
|
||||||
'tcp': (
|
|
||||||
'ack',
|
|
||||||
'fin',
|
|
||||||
'psh',
|
|
||||||
'rst',
|
|
||||||
'syn',
|
|
||||||
'urg',
|
|
||||||
'established',
|
|
||||||
),
|
|
||||||
'igmp': ('dvmrp', 'host_query', 'host_report', 'mtrace', 'mtrace_response',
|
|
||||||
'pim', 'trace', 'v2_leave', 'v2_report', 'v3_report'),
|
|
||||||
'icmp':
|
|
||||||
('administratively_prohibited', 'alternate_address', 'conversion_error',
|
|
||||||
'dod_host_prohibited', 'dod_net_prohibited', 'echo', 'echo_reply',
|
|
||||||
'general_parameter_problem', 'host_isolated',
|
|
||||||
'host_precedence_unreachable', 'host_redirect', 'host_tos_redirect',
|
|
||||||
'host_tos_unreachable', 'host_unknown', 'host_unreachable',
|
|
||||||
'information_reply', 'information_request', 'mask_reply', 'mask_request',
|
|
||||||
'mobile_redirect', 'net_redirect', 'net_tos_redirect',
|
|
||||||
'net_tos_unreachable', 'net_unreachable', 'network_unknown',
|
|
||||||
'no_room_for_option', 'option_missing', 'packet_too_big',
|
|
||||||
'parameter_problem', 'port_unreachable', 'precedence_unreachable',
|
|
||||||
'protocol_unreachable', 'reassembly_timeout', 'redirect',
|
|
||||||
'router_advertisement', 'router_solicitation', 'source_quench',
|
|
||||||
'source_route_failed', 'time_exceeded', 'timestamp_reply',
|
|
||||||
'timestamp_request', 'traceroute', 'ttl_exceeded', 'unreachable'),
|
|
||||||
'icmpv6':
|
|
||||||
('address_unreachable', 'administratively_prohibited',
|
|
||||||
'beyond_scope_of_source_address', 'destination_unreachable', 'echo',
|
|
||||||
'echo_reply', 'erroneous_header_field', 'group_membership_query',
|
|
||||||
'group_membership_report', 'group_membership_termination',
|
|
||||||
'host_unreachable', 'nd_na', 'nd_ns', 'neighbor_redirect',
|
|
||||||
'no_route_to_destination', 'node_information_request_is_refused',
|
|
||||||
'node_information_successful_reply', 'packet_too_big',
|
|
||||||
'parameter_problem', 'port_unreachable', 'query_subject_is_IPv4address',
|
|
||||||
'query_subject_is_IPv6address', 'query_subject_is_domainname',
|
|
||||||
'reassembly_timeout', 'redirect', 'router_advertisement',
|
|
||||||
'router_renumbering', 'router_solicitation', 'rr_command', 'rr_result',
|
|
||||||
'rr_seqnum_reset', 'time_exceeded', 'ttl_exceeded', 'unknown_query_type',
|
|
||||||
'unreachable', 'unrecognized_next_header', 'unrecognized_option',
|
|
||||||
'whoareyou_reply', 'whoareyou_request')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AclsFacts(object):
|
|
||||||
""" The iosxr acls fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = AclsArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def get_device_data(self, connection):
|
|
||||||
return connection.get('show access-lists afi-all')
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for acls
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
if not data:
|
|
||||||
data = self.get_device_data(connection)
|
|
||||||
|
|
||||||
objs = []
|
|
||||||
|
|
||||||
acl_lines = data.splitlines()
|
|
||||||
|
|
||||||
# We iterate through the data and create a list of ACLs
|
|
||||||
# where each ACL is a dictionary in the following format:
|
|
||||||
# {'afi': 'ipv4', 'name': 'acl_1', 'aces': ['10 permit 172.16.0.0 0.0.255.255', '20 deny 192.168.34.0 0.0.0.255']}
|
|
||||||
if acl_lines:
|
|
||||||
acl, acls = {}, []
|
|
||||||
for line in acl_lines:
|
|
||||||
if line.startswith('ip'):
|
|
||||||
if acl:
|
|
||||||
acls.append(acl)
|
|
||||||
acl = {'aces': []}
|
|
||||||
acl['afi'], acl['name'] = line.split()[0], line.split()[2]
|
|
||||||
else:
|
|
||||||
acl['aces'].append(line.strip())
|
|
||||||
acls.append(acl)
|
|
||||||
|
|
||||||
# Here we group the ACLs based on AFI
|
|
||||||
# {
|
|
||||||
# 'ipv6': [{'aces': ['10 permit ipv6 2000::/12 any'], 'name': 'acl_2'}],
|
|
||||||
# 'ipv4': [{'aces': ['10 permit 172.16.0.0 0.0.255.255', '20 deny 192.168.34.0 0.0.0.255'], 'name': 'acl_1'},
|
|
||||||
# {'aces': ['20 deny 10.0.0.0/8 log'], 'name': 'acl_3'}]
|
|
||||||
# }
|
|
||||||
|
|
||||||
grouped_acls = {'ipv4': [], 'ipv6': []}
|
|
||||||
for acl in acls:
|
|
||||||
acl_copy = deepcopy(acl)
|
|
||||||
del acl_copy['afi']
|
|
||||||
grouped_acls[acl['afi']].append(acl_copy)
|
|
||||||
|
|
||||||
# Now that we have the ACLs in a fairly structured format,
|
|
||||||
# we pass it on to render_config to convert it to model spec
|
|
||||||
for key, value in iteritems(grouped_acls):
|
|
||||||
obj = self.render_config(self.generated_spec, value)
|
|
||||||
if obj:
|
|
||||||
obj['afi'] = key
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('acls', None)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
facts['acls'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['acls'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
config['acls'] = []
|
|
||||||
|
|
||||||
for item in conf:
|
|
||||||
acl = {'name': item['name']}
|
|
||||||
aces = item.get('aces', [])
|
|
||||||
if aces:
|
|
||||||
acl['aces'] = []
|
|
||||||
for ace in aces:
|
|
||||||
acl['aces'].append(self._render_ace(ace))
|
|
||||||
config['acls'].append(acl)
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
||||||
|
|
||||||
def _render_ace(self, ace):
|
|
||||||
"""
|
|
||||||
Parses an Access Control Entry (ACE) and converts it
|
|
||||||
into model spec
|
|
||||||
|
|
||||||
:param ace: An ACE in device specific format
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The ACE in structured format
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __parse_src_dest(rendered_ace, ace_queue, direction):
|
|
||||||
"""
|
|
||||||
Parses the ACE queue and populates address, wildcard_bits,
|
|
||||||
host or any keys in the source/destination dictionary of
|
|
||||||
ace dictionary, i.e., `rendered_ace`.
|
|
||||||
|
|
||||||
:param rendered_ace: The dictionary containing the ACE in structured format
|
|
||||||
:param ace_queue: The ACE queue
|
|
||||||
:param direction: Specifies whether to populate `source` or `destination`
|
|
||||||
dictionary
|
|
||||||
"""
|
|
||||||
element = ace_queue.popleft()
|
|
||||||
if element == 'host':
|
|
||||||
rendered_ace[direction] = {'host': ace_queue.popleft()}
|
|
||||||
|
|
||||||
elif element == 'any':
|
|
||||||
rendered_ace[direction] = {'any': True}
|
|
||||||
|
|
||||||
elif '/' in element:
|
|
||||||
rendered_ace[direction] = {
|
|
||||||
'prefix': element
|
|
||||||
}
|
|
||||||
|
|
||||||
elif isipaddress(element):
|
|
||||||
rendered_ace[direction] = {
|
|
||||||
'address': element,
|
|
||||||
'wildcard_bits': ace_queue.popleft()
|
|
||||||
}
|
|
||||||
|
|
||||||
def __parse_port_protocol(rendered_ace, ace_queue, direction):
|
|
||||||
"""
|
|
||||||
Parses the ACE queue and populates `port_protocol` dictionary in the
|
|
||||||
ACE dictionary, i.e., `rendered_ace`.
|
|
||||||
|
|
||||||
:param rendered_ace: The dictionary containing the ACE in structured format
|
|
||||||
:param ace_queue: The ACE queue
|
|
||||||
:param direction: Specifies whether to populate `source` or `destination`
|
|
||||||
dictionary
|
|
||||||
"""
|
|
||||||
if len(ace_queue) > 0 and ace_queue[0] in ('eq', 'gt', 'lt', 'neq',
|
|
||||||
'range'):
|
|
||||||
element = ace_queue.popleft()
|
|
||||||
port_protocol = {}
|
|
||||||
|
|
||||||
if element == 'range':
|
|
||||||
port_protocol['range'] = {
|
|
||||||
'start': ace_queue.popleft(),
|
|
||||||
'end': ace_queue.popleft()
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
port_protocol[element] = ace_queue.popleft()
|
|
||||||
|
|
||||||
rendered_ace[direction]['port_protocol'] = port_protocol
|
|
||||||
|
|
||||||
def __parse_protocol_options(rendered_ace, ace_queue, protocol):
|
|
||||||
"""
|
|
||||||
Parses the ACE queue and populates protocol specific options
|
|
||||||
of the required dictionary and updates the ACE dictionary, i.e.,
|
|
||||||
`rendered_ace`.
|
|
||||||
|
|
||||||
:param rendered_ace: The dictionary containing the ACE in structured format
|
|
||||||
:param ace_queue: The ACE queue
|
|
||||||
:param protocol: Specifies the protocol that will be populated under
|
|
||||||
`protocol_options` dictionary
|
|
||||||
"""
|
|
||||||
if len(ace_queue) > 0:
|
|
||||||
protocol_options = {protocol: {}}
|
|
||||||
|
|
||||||
for match_bit in PROTOCOL_OPTIONS.get(protocol, ()):
|
|
||||||
if match_bit.replace('_', '-') in ace_queue:
|
|
||||||
protocol_options[protocol][match_bit] = True
|
|
||||||
ace_queue.remove(match_bit.replace('_', '-'))
|
|
||||||
|
|
||||||
rendered_ace['protocol_options'] = protocol_options
|
|
||||||
|
|
||||||
def __parse_match_options(rendered_ace, ace_queue):
|
|
||||||
"""
|
|
||||||
Parses the ACE queue and populates remaining options in the ACE dictionary,
|
|
||||||
i.e., `rendered_ace`
|
|
||||||
|
|
||||||
:param rendered_ace: The dictionary containing the ACE in structured format
|
|
||||||
:param ace_queue: The ACE queue
|
|
||||||
"""
|
|
||||||
if len(ace_queue) > 0:
|
|
||||||
# We deepcopy the actual queue and iterate through the
|
|
||||||
# copied queue. However, we pop off the elements from
|
|
||||||
# the actual queue. Then, in every pass we update the copied
|
|
||||||
# queue with the current state of the original queue.
|
|
||||||
# This is done because a queue cannot be mutated during iteration.
|
|
||||||
copy_ace_queue = deepcopy(ace_queue)
|
|
||||||
|
|
||||||
for element in copy_ace_queue:
|
|
||||||
if element == 'precedence':
|
|
||||||
ace_queue.popleft()
|
|
||||||
rendered_ace['precedence'] = ace_queue.popleft()
|
|
||||||
|
|
||||||
elif element == 'dscp':
|
|
||||||
ace_queue.popleft()
|
|
||||||
dscp = {}
|
|
||||||
operation = ace_queue.popleft()
|
|
||||||
|
|
||||||
if operation in ('eq', 'gt', 'neq', 'lt', 'range'):
|
|
||||||
if operation == 'range':
|
|
||||||
dscp['range'] = {
|
|
||||||
'start': ace_queue.popleft(),
|
|
||||||
'end': ace_queue.popleft()
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
dscp[operation] = ace_queue.popleft()
|
|
||||||
else:
|
|
||||||
# `dscp` can be followed by either the dscp value itself or
|
|
||||||
# the same thing can be represented using "dscp eq <dscp_value>".
|
|
||||||
# In both cases, it would show up as {'dscp': {'eq': "dscp_value"}}.
|
|
||||||
dscp['eq'] = operation
|
|
||||||
|
|
||||||
rendered_ace['dscp'] = dscp
|
|
||||||
|
|
||||||
elif element in ('packet-length', 'ttl'):
|
|
||||||
ace_queue.popleft()
|
|
||||||
element_dict = {}
|
|
||||||
operation = ace_queue.popleft()
|
|
||||||
|
|
||||||
if operation == 'range':
|
|
||||||
element_dict['range'] = {
|
|
||||||
'start': ace_queue.popleft(),
|
|
||||||
'end': ace_queue.popleft()
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
element_dict[operation] = ace_queue.popleft()
|
|
||||||
|
|
||||||
rendered_ace[element.replace('-', '_')] = element_dict
|
|
||||||
|
|
||||||
elif element in ('log', 'log-input', 'fragments',
|
|
||||||
'icmp-off', 'capture', 'destopts',
|
|
||||||
'authen', 'routing', 'hop-by-hop'):
|
|
||||||
rendered_ace[element.replace('-', '_')] = True
|
|
||||||
ace_queue.remove(element)
|
|
||||||
|
|
||||||
copy_ace_queue = deepcopy(ace_queue)
|
|
||||||
|
|
||||||
rendered_ace = {}
|
|
||||||
split_ace = ace.split()
|
|
||||||
|
|
||||||
# Create a queue with each word in the ace
|
|
||||||
# We parse each element and pop it off the queue
|
|
||||||
ace_queue = deque(split_ace)
|
|
||||||
|
|
||||||
# An ACE will always have a sequence number, even if
|
|
||||||
# it is not explicitly provided while configuring
|
|
||||||
sequence = int(ace_queue.popleft())
|
|
||||||
rendered_ace['sequence'] = sequence
|
|
||||||
operation = ace_queue.popleft()
|
|
||||||
|
|
||||||
if operation == 'remark':
|
|
||||||
rendered_ace['remark'] = ' '.join(split_ace[2:])
|
|
||||||
|
|
||||||
else:
|
|
||||||
rendered_ace['grant'] = operation
|
|
||||||
|
|
||||||
# If the entry is a non-remark entry, the third element
|
|
||||||
# will always be the protocol specified. By default, it's
|
|
||||||
# the AFI.
|
|
||||||
rendered_ace['protocol'] = ace_queue.popleft()
|
|
||||||
|
|
||||||
# Populate source dictionary
|
|
||||||
__parse_src_dest(rendered_ace, ace_queue, direction='source')
|
|
||||||
# Populate port_protocol key in source dictionary
|
|
||||||
__parse_port_protocol(rendered_ace, ace_queue, direction='source')
|
|
||||||
# Populate destination dictionary
|
|
||||||
__parse_src_dest(rendered_ace, ace_queue, direction='destination')
|
|
||||||
# Populate port_protocol key in destination dictionary
|
|
||||||
__parse_port_protocol(rendered_ace,
|
|
||||||
ace_queue,
|
|
||||||
direction='destination')
|
|
||||||
# Populate protocol_options dictionary
|
|
||||||
__parse_protocol_options(rendered_ace,
|
|
||||||
ace_queue,
|
|
||||||
protocol=rendered_ace['protocol'])
|
|
||||||
# Populate remaining match options' dictionaries
|
|
||||||
__parse_match_options(rendered_ace, ace_queue)
|
|
||||||
|
|
||||||
# At this stage the queue should be empty
|
|
||||||
# If the queue is not empty, it means that
|
|
||||||
# we haven't been able to parse the entire ACE
|
|
||||||
# In this case, we add the whole unprocessed ACE
|
|
||||||
# to a key called `line` and send it back
|
|
||||||
if len(ace_queue) > 0:
|
|
||||||
rendered_ace = {
|
|
||||||
'sequence': sequence,
|
|
||||||
'line': ' '.join(split_ace[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils.remove_empties(rendered_ace)
|
|
@ -1,77 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The facts class for iosxr
|
|
||||||
this file validates each subset of facts and selectively
|
|
||||||
calls the appropriate facts gathering function
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common.facts.facts import FactsBase
|
|
||||||
from ansible.module_utils.network.iosxr.facts.legacy.base import Default, Hardware, Interfaces, Config
|
|
||||||
from ansible.module_utils.network.iosxr.facts.lacp.lacp import LacpFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.lldp_global.lldp_global import Lldp_globalFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.interfaces.interfaces import InterfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.acls.acls import AclsFacts
|
|
||||||
from ansible.module_utils.network.iosxr.facts.static_routes.static_routes import Static_routesFacts
|
|
||||||
|
|
||||||
|
|
||||||
FACT_LEGACY_SUBSETS = dict(
|
|
||||||
default=Default,
|
|
||||||
hardware=Hardware,
|
|
||||||
interfaces=Interfaces,
|
|
||||||
config=Config,
|
|
||||||
)
|
|
||||||
FACT_RESOURCE_SUBSETS = dict(
|
|
||||||
lacp=LacpFacts,
|
|
||||||
lacp_interfaces=Lacp_interfacesFacts,
|
|
||||||
lldp_global=Lldp_globalFacts,
|
|
||||||
lldp_interfaces=Lldp_interfacesFacts,
|
|
||||||
interfaces=InterfacesFacts,
|
|
||||||
l2_interfaces=L2_InterfacesFacts,
|
|
||||||
lag_interfaces=Lag_interfacesFacts,
|
|
||||||
l3_interfaces=L3_InterfacesFacts,
|
|
||||||
acl_interfaces=Acl_interfacesFacts,
|
|
||||||
acls=AclsFacts,
|
|
||||||
static_routes=Static_routesFacts
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Facts(FactsBase):
|
|
||||||
""" The fact class for iosxr
|
|
||||||
"""
|
|
||||||
|
|
||||||
VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
|
|
||||||
VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(Facts, self).__init__(module)
|
|
||||||
|
|
||||||
def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
|
|
||||||
""" Collect the facts for iosxr
|
|
||||||
|
|
||||||
:param legacy_facts_type: List of legacy facts types
|
|
||||||
:param resource_facts_type: List of resource fact types
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dict
|
|
||||||
:return: the facts gathered
|
|
||||||
"""
|
|
||||||
if self.VALID_RESOURCE_SUBSETS:
|
|
||||||
self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data)
|
|
||||||
|
|
||||||
if self.VALID_LEGACY_GATHER_SUBSETS:
|
|
||||||
self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
|
|
||||||
|
|
||||||
return self.ansible_facts, self._warnings
|
|
@ -1,102 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat Inc.
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
import re
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.interfaces.interfaces import InterfacesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class InterfacesFacts(object):
|
|
||||||
""" The iosxr interfaces fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = InterfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for interfaces
|
|
||||||
:param module: the module instance
|
|
||||||
:param connection: the device connection
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
objs = []
|
|
||||||
if not data:
|
|
||||||
data = connection.get('show running-config interface')
|
|
||||||
|
|
||||||
# operate on a collection of resource x
|
|
||||||
config = data.split('interface ')
|
|
||||||
for conf in config:
|
|
||||||
if conf:
|
|
||||||
obj = self.render_config(self.generated_spec, conf)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
facts = {}
|
|
||||||
if objs:
|
|
||||||
facts['interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys from spec for null values
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
|
|
||||||
config = deepcopy(spec)
|
|
||||||
match = re.search(r'^(\S+)', conf)
|
|
||||||
|
|
||||||
intf = match.group(1)
|
|
||||||
if match.group(1).lower() == "preconfigure":
|
|
||||||
match = re.search(r'^(\S+) (.*)', conf)
|
|
||||||
if match:
|
|
||||||
intf = match.group(2)
|
|
||||||
|
|
||||||
if get_interface_type(intf) == 'unknown':
|
|
||||||
return {}
|
|
||||||
# populate the facts from the configuration
|
|
||||||
config['name'] = intf
|
|
||||||
config['description'] = utils.parse_conf_arg(conf, 'description')
|
|
||||||
if utils.parse_conf_arg(conf, 'speed'):
|
|
||||||
config['speed'] = int(utils.parse_conf_arg(conf, 'speed'))
|
|
||||||
if utils.parse_conf_arg(conf, 'mtu'):
|
|
||||||
config['mtu'] = int(utils.parse_conf_arg(conf, 'mtu'))
|
|
||||||
config['duplex'] = utils.parse_conf_arg(conf, 'duplex')
|
|
||||||
enabled = utils.parse_conf_cmd_arg(conf, 'shutdown', False)
|
|
||||||
config['enabled'] = enabled if enabled is not None else True
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
@ -1,125 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat Inc.
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr l2_interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
import re
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class L2_InterfacesFacts(object):
|
|
||||||
""" The iosxr l2_interfaces fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = L2_InterfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for l2_interfaces
|
|
||||||
:param module: the module instance
|
|
||||||
:param connection: the device connection
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
objs = []
|
|
||||||
if not data:
|
|
||||||
data = connection.get('show running-config interface')
|
|
||||||
|
|
||||||
# operate on a collection of resource x
|
|
||||||
config = data.split('interface ')
|
|
||||||
for conf in config:
|
|
||||||
if conf:
|
|
||||||
obj = self.render_config(self.generated_spec, conf)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
facts = {}
|
|
||||||
if objs:
|
|
||||||
facts['l2_interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['l2_interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys from spec for null values
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
match = re.search(r'^(\S+)', conf)
|
|
||||||
|
|
||||||
intf = match.group(1)
|
|
||||||
|
|
||||||
if match.group(1).lower() == "preconfigure":
|
|
||||||
match = re.search(r'^(\S+) (.*)', conf)
|
|
||||||
if match:
|
|
||||||
intf = match.group(2)
|
|
||||||
|
|
||||||
if get_interface_type(intf) == 'unknown':
|
|
||||||
return {}
|
|
||||||
|
|
||||||
if intf.lower().startswith('gi'):
|
|
||||||
config['name'] = intf
|
|
||||||
|
|
||||||
# populate the facts from the configuration
|
|
||||||
native_vlan = re.search(r"dot1q native vlan (\d+)", conf)
|
|
||||||
if native_vlan:
|
|
||||||
config["native_vlan"] = int(native_vlan.group(1))
|
|
||||||
|
|
||||||
dot1q = utils.parse_conf_arg(conf, 'encapsulation dot1q')
|
|
||||||
config['q_vlan'] = []
|
|
||||||
if dot1q:
|
|
||||||
config['q_vlan'].append(int(dot1q.split(' ')[0]))
|
|
||||||
if len(dot1q.split(' ')) > 1:
|
|
||||||
config['q_vlan'].append(int(dot1q.split(' ')[2]))
|
|
||||||
|
|
||||||
if utils.parse_conf_cmd_arg(conf, 'l2transport', True):
|
|
||||||
config['l2transport'] = True
|
|
||||||
if utils.parse_conf_arg(conf, 'propagate'):
|
|
||||||
config['propagate'] = True
|
|
||||||
config['l2protocol'] = []
|
|
||||||
|
|
||||||
cdp = utils.parse_conf_arg(conf, 'l2protocol cdp')
|
|
||||||
pvst = utils.parse_conf_arg(conf, 'l2protocol pvst')
|
|
||||||
stp = utils.parse_conf_arg(conf, 'l2protocol stp')
|
|
||||||
vtp = utils.parse_conf_arg(conf, 'l2protocol vtp')
|
|
||||||
if cdp:
|
|
||||||
config['l2protocol'].append({'cdp': cdp})
|
|
||||||
if pvst:
|
|
||||||
config['l2protocol'].append({'pvst': pvst})
|
|
||||||
if stp:
|
|
||||||
config['l2protocol'].append({'stp': stp})
|
|
||||||
if vtp:
|
|
||||||
config['l2protocol'].append({'vtp': vtp})
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
@ -1,117 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr_l3_interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
import re
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.utils.utils import get_interface_type
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class L3_InterfacesFacts(object):
|
|
||||||
""" The iosxr_l3_interfaces fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = L3_InterfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for interfaces
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
objs = []
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
data = connection.get('show running-config interface')
|
|
||||||
# operate on a collection of resource x
|
|
||||||
config = data.split('interface ')
|
|
||||||
for conf in config:
|
|
||||||
if conf:
|
|
||||||
obj = self.render_config(self.generated_spec, conf)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
if objs:
|
|
||||||
facts['l3_interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['l3_interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys from spec for null values
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
match = re.search(r'^(\S+)', conf)
|
|
||||||
|
|
||||||
intf = match.group(1)
|
|
||||||
if match.group(1).lower() == "preconfigure":
|
|
||||||
match = re.search(r'^(\S+) (.*)', conf)
|
|
||||||
if match:
|
|
||||||
intf = match.group(2)
|
|
||||||
|
|
||||||
if get_interface_type(intf) == 'unknown':
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# populate the facts from the configuration
|
|
||||||
config['name'] = intf
|
|
||||||
|
|
||||||
# Get the configured IPV4 details
|
|
||||||
ipv4 = []
|
|
||||||
ipv4_all = re.findall(r"ipv4 address (\S+.*)", conf)
|
|
||||||
for each in ipv4_all:
|
|
||||||
each_ipv4 = dict()
|
|
||||||
if 'secondary' in each:
|
|
||||||
each_ipv4['address'] = each.split(' secondary')[0]
|
|
||||||
each_ipv4['secondary'] = True
|
|
||||||
else:
|
|
||||||
each_ipv4['address'] = each
|
|
||||||
ipv4.append(each_ipv4)
|
|
||||||
config['ipv4'] = ipv4
|
|
||||||
|
|
||||||
# Get the configured IPV6 details
|
|
||||||
ipv6 = []
|
|
||||||
ipv6_all = re.findall(r"ipv6 address (\S+)", conf)
|
|
||||||
for each in ipv6_all:
|
|
||||||
each_ipv6 = dict()
|
|
||||||
each_ipv6['address'] = each
|
|
||||||
ipv6.append(each_ipv6)
|
|
||||||
config['ipv6'] = ipv6
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
@ -1,82 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr lacp fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lacp.lacp import LacpArgs
|
|
||||||
|
|
||||||
|
|
||||||
class LacpFacts(object):
|
|
||||||
""" The iosxr lacp fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = LacpArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for lacp
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
if not data:
|
|
||||||
data = connection.get_config(flags='lacp')
|
|
||||||
|
|
||||||
obj = {}
|
|
||||||
if data:
|
|
||||||
lacp_obj = self.render_config(self.generated_spec, data)
|
|
||||||
if lacp_obj:
|
|
||||||
obj = lacp_obj
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('lacp', None)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': obj})
|
|
||||||
facts['lacp'] = utils.remove_empties(params['config'])
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
|
|
||||||
system_priority = utils.parse_conf_arg(conf, 'priority')
|
|
||||||
config['system']['priority'] = int(system_priority) if system_priority else system_priority
|
|
||||||
config['system']['mac']['address'] = utils.parse_conf_arg(conf, 'mac')
|
|
||||||
|
|
||||||
return config
|
|
@ -1,104 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr lacp_interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
|
|
||||||
|
|
||||||
class Lacp_interfacesFacts(object):
|
|
||||||
""" The iosxr lacp_interfaces fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = Lacp_interfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for lacp_interfaces
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
data = connection.get_config(flags='interface')
|
|
||||||
interfaces = data.split('interface ')
|
|
||||||
|
|
||||||
objs = []
|
|
||||||
for interface in interfaces:
|
|
||||||
obj = self.render_config(self.generated_spec, interface)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('lacp_interfaces', None)
|
|
||||||
facts = {}
|
|
||||||
if objs:
|
|
||||||
facts['lacp_interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['lacp_interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
|
|
||||||
match = re.search(r'(GigabitEthernet|Bundle-Ether|TenGigE|FortyGigE|HundredGigE)(\S+)', conf, re.M)
|
|
||||||
if match:
|
|
||||||
config['name'] = match.group(1) + match.group(2)
|
|
||||||
|
|
||||||
temp = {
|
|
||||||
'churn_logging': 'lacp churn logging',
|
|
||||||
'switchover_suppress_flaps': 'lacp switchover suppress-flaps',
|
|
||||||
'collector_max_delay': 'lacp collector-max-delay',
|
|
||||||
'period': 'lacp period'
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value in iteritems(temp):
|
|
||||||
config[key] = utils.parse_conf_arg(
|
|
||||||
conf, value)
|
|
||||||
|
|
||||||
for key in config['system'].keys():
|
|
||||||
config['system'][key] = utils.parse_conf_arg(
|
|
||||||
conf, 'lacp system {0}'.format(key))
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
@ -1,128 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr lag_interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class Lag_interfacesFacts(object):
|
|
||||||
""" The iosxr lag_interfaces fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = Lag_interfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for lag_interfaces
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
data = connection.get_config(flags='interface')
|
|
||||||
interfaces = data.split('interface ')
|
|
||||||
|
|
||||||
objs = []
|
|
||||||
|
|
||||||
for interface in interfaces:
|
|
||||||
if interface.startswith("Bundle-Ether"):
|
|
||||||
obj = self.render_config(self.generated_spec, interface, interfaces)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('lag_interfaces', None)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
facts['lag_interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['lag_interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf, data):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
match = re.search(r'(Bundle-Ether)(\d+)', conf, re.M)
|
|
||||||
if match:
|
|
||||||
config['name'] = match.group(1) + match.group(2)
|
|
||||||
config['load_balancing_hash'] = utils.parse_conf_arg(
|
|
||||||
conf, 'bundle load-balancing hash')
|
|
||||||
config['mode'] = utils.parse_conf_arg(conf, 'lacp mode')
|
|
||||||
config['links']['max_active'] = utils.parse_conf_arg(
|
|
||||||
conf, 'bundle maximum-active links')
|
|
||||||
config['links']['min_active'] = utils.parse_conf_arg(
|
|
||||||
conf, 'bundle minimum-active links')
|
|
||||||
config['members'] = self.parse_members(match.group(2), data)
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
||||||
|
|
||||||
def parse_members(self, bundle_id, interfaces):
|
|
||||||
"""
|
|
||||||
Renders a list of member interfaces for every bundle
|
|
||||||
present in running-config.
|
|
||||||
|
|
||||||
:param bundle_id: The Bundle-Ether ID fetched from running-config
|
|
||||||
:param interfaces: Data of all interfaces present in running-config
|
|
||||||
:rtype: list
|
|
||||||
:returns: A list of member interfaces
|
|
||||||
"""
|
|
||||||
def _parse_interface(name):
|
|
||||||
if name.startswith('preconfigure'):
|
|
||||||
return name.split()[1]
|
|
||||||
else:
|
|
||||||
return name.split()[0]
|
|
||||||
|
|
||||||
members = []
|
|
||||||
for interface in interfaces:
|
|
||||||
if not interface.startswith('Bu'):
|
|
||||||
match = re.search(r'bundle id (\d+) mode (\S+)', interface, re.M)
|
|
||||||
if match:
|
|
||||||
if bundle_id == match.group(1):
|
|
||||||
members.append(
|
|
||||||
{
|
|
||||||
'member': _parse_interface(interface),
|
|
||||||
'mode': match.group(2)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return members
|
|
@ -1,259 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
"""
|
|
||||||
The iosxr legacy fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import run_commands, get_capabilities
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.six.moves import zip
|
|
||||||
|
|
||||||
|
|
||||||
class FactsBase(object):
|
|
||||||
|
|
||||||
COMMANDS = frozenset()
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
self.module = module
|
|
||||||
self.facts = dict()
|
|
||||||
self.warnings = list()
|
|
||||||
self.responses = None
|
|
||||||
|
|
||||||
def populate(self):
|
|
||||||
self.responses = run_commands(self.module, list(self.COMMANDS), check_rc=False)
|
|
||||||
|
|
||||||
|
|
||||||
class Default(FactsBase):
|
|
||||||
|
|
||||||
def populate(self):
|
|
||||||
self.facts.update(self.platform_facts())
|
|
||||||
|
|
||||||
def platform_facts(self):
|
|
||||||
platform_facts = {}
|
|
||||||
|
|
||||||
resp = get_capabilities(self.module)
|
|
||||||
device_info = resp['device_info']
|
|
||||||
|
|
||||||
platform_facts['system'] = device_info['network_os']
|
|
||||||
|
|
||||||
for item in ('model', 'image', 'version', 'platform', 'hostname'):
|
|
||||||
val = device_info.get('network_os_%s' % item)
|
|
||||||
if val:
|
|
||||||
platform_facts[item] = val
|
|
||||||
|
|
||||||
platform_facts['api'] = resp['network_api']
|
|
||||||
platform_facts['python_version'] = platform.python_version()
|
|
||||||
|
|
||||||
return platform_facts
|
|
||||||
|
|
||||||
|
|
||||||
class Hardware(FactsBase):
|
|
||||||
|
|
||||||
COMMANDS = [
|
|
||||||
'dir /all',
|
|
||||||
'show memory summary'
|
|
||||||
]
|
|
||||||
|
|
||||||
def populate(self):
|
|
||||||
super(Hardware, self).populate()
|
|
||||||
data = self.responses[0]
|
|
||||||
self.facts['filesystems'] = self.parse_filesystems(data)
|
|
||||||
|
|
||||||
data = self.responses[1]
|
|
||||||
match = re.search(r'Physical Memory: (\d+)M total \((\d+)', data)
|
|
||||||
if match:
|
|
||||||
self.facts['memtotal_mb'] = match.group(1)
|
|
||||||
self.facts['memfree_mb'] = match.group(2)
|
|
||||||
|
|
||||||
def parse_filesystems(self, data):
|
|
||||||
return re.findall(r'^Directory of (\S+)', data, re.M)
|
|
||||||
|
|
||||||
|
|
||||||
class Config(FactsBase):
|
|
||||||
|
|
||||||
COMMANDS = [
|
|
||||||
'show running-config'
|
|
||||||
]
|
|
||||||
|
|
||||||
def populate(self):
|
|
||||||
super(Config, self).populate()
|
|
||||||
self.facts['config'] = self.responses[0]
|
|
||||||
|
|
||||||
|
|
||||||
class Interfaces(FactsBase):
|
|
||||||
|
|
||||||
COMMANDS = [
|
|
||||||
'show interfaces',
|
|
||||||
'show ipv6 interface',
|
|
||||||
'show lldp',
|
|
||||||
'show lldp neighbors detail'
|
|
||||||
]
|
|
||||||
|
|
||||||
def populate(self):
|
|
||||||
super(Interfaces, self).populate()
|
|
||||||
self.facts['all_ipv4_addresses'] = list()
|
|
||||||
self.facts['all_ipv6_addresses'] = list()
|
|
||||||
|
|
||||||
interfaces = self.parse_interfaces(self.responses[0])
|
|
||||||
self.facts['interfaces'] = self.populate_interfaces(interfaces)
|
|
||||||
|
|
||||||
data = self.responses[1]
|
|
||||||
if len(data) > 0:
|
|
||||||
data = self.parse_interfaces(data)
|
|
||||||
self.populate_ipv6_interfaces(data)
|
|
||||||
|
|
||||||
if 'LLDP is not enabled' not in self.responses[2]:
|
|
||||||
neighbors = self.responses[3]
|
|
||||||
self.facts['neighbors'] = self.parse_neighbors(neighbors)
|
|
||||||
|
|
||||||
def populate_interfaces(self, interfaces):
|
|
||||||
facts = dict()
|
|
||||||
for key, value in iteritems(interfaces):
|
|
||||||
intf = dict()
|
|
||||||
intf['description'] = self.parse_description(value)
|
|
||||||
intf['macaddress'] = self.parse_macaddress(value)
|
|
||||||
|
|
||||||
ipv4 = self.parse_ipv4(value)
|
|
||||||
intf['ipv4'] = self.parse_ipv4(value)
|
|
||||||
if ipv4:
|
|
||||||
self.add_ip_address(ipv4['address'], 'ipv4')
|
|
||||||
|
|
||||||
intf['mtu'] = self.parse_mtu(value)
|
|
||||||
intf['bandwidth'] = self.parse_bandwidth(value)
|
|
||||||
intf['duplex'] = self.parse_duplex(value)
|
|
||||||
intf['lineprotocol'] = self.parse_lineprotocol(value)
|
|
||||||
intf['operstatus'] = self.parse_operstatus(value)
|
|
||||||
intf['type'] = self.parse_type(value)
|
|
||||||
|
|
||||||
facts[key] = intf
|
|
||||||
return facts
|
|
||||||
|
|
||||||
def populate_ipv6_interfaces(self, data):
|
|
||||||
for key, value in iteritems(data):
|
|
||||||
if key in ['No', 'RPF'] or key.startswith('IP'):
|
|
||||||
continue
|
|
||||||
self.facts['interfaces'][key]['ipv6'] = list()
|
|
||||||
addresses = re.findall(r'\s+(.+), subnet', value, re.M)
|
|
||||||
subnets = re.findall(r', subnet is (.+)$', value, re.M)
|
|
||||||
for addr, subnet in zip(addresses, subnets):
|
|
||||||
ipv6 = dict(address=addr.strip(), subnet=subnet.strip())
|
|
||||||
self.add_ip_address(addr.strip(), 'ipv6')
|
|
||||||
self.facts['interfaces'][key]['ipv6'].append(ipv6)
|
|
||||||
|
|
||||||
def add_ip_address(self, address, family):
|
|
||||||
if family == 'ipv4':
|
|
||||||
self.facts['all_ipv4_addresses'].append(address)
|
|
||||||
else:
|
|
||||||
self.facts['all_ipv6_addresses'].append(address)
|
|
||||||
|
|
||||||
def parse_neighbors(self, neighbors):
|
|
||||||
facts = dict()
|
|
||||||
nbors = neighbors.split('------------------------------------------------')
|
|
||||||
for entry in nbors[1:]:
|
|
||||||
if entry == '':
|
|
||||||
continue
|
|
||||||
intf = self.parse_lldp_intf(entry)
|
|
||||||
if intf not in facts:
|
|
||||||
facts[intf] = list()
|
|
||||||
fact = dict()
|
|
||||||
fact['host'] = self.parse_lldp_host(entry)
|
|
||||||
fact['remote_description'] = self.parse_lldp_remote_desc(entry)
|
|
||||||
fact['port'] = self.parse_lldp_port(entry)
|
|
||||||
facts[intf].append(fact)
|
|
||||||
return facts
|
|
||||||
|
|
||||||
def parse_interfaces(self, data):
|
|
||||||
parsed = dict()
|
|
||||||
key = ''
|
|
||||||
for line in data.split('\n'):
|
|
||||||
if len(line) == 0:
|
|
||||||
continue
|
|
||||||
elif line[0] == ' ':
|
|
||||||
parsed[key] += '\n%s' % line
|
|
||||||
else:
|
|
||||||
match = re.match(r'^(\S+)', line)
|
|
||||||
if match:
|
|
||||||
key = match.group(1)
|
|
||||||
parsed[key] = line
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
def parse_description(self, data):
|
|
||||||
match = re.search(r'Description: (.+)$', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_macaddress(self, data):
|
|
||||||
match = re.search(r'address is (\S+)', data)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_ipv4(self, data):
|
|
||||||
match = re.search(r'Internet address is (\S+)/(\d+)', data)
|
|
||||||
if match:
|
|
||||||
addr = match.group(1)
|
|
||||||
masklen = int(match.group(2))
|
|
||||||
return dict(address=addr, masklen=masklen)
|
|
||||||
|
|
||||||
def parse_mtu(self, data):
|
|
||||||
match = re.search(r'MTU (\d+)', data)
|
|
||||||
if match:
|
|
||||||
return int(match.group(1))
|
|
||||||
|
|
||||||
def parse_bandwidth(self, data):
|
|
||||||
match = re.search(r'BW (\d+)', data)
|
|
||||||
if match:
|
|
||||||
return int(match.group(1))
|
|
||||||
|
|
||||||
def parse_duplex(self, data):
|
|
||||||
match = re.search(r'(\w+)(?: D|-d)uplex', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_type(self, data):
|
|
||||||
match = re.search(r'Hardware is (.+),', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_lineprotocol(self, data):
|
|
||||||
match = re.search(r'line protocol is (.+)\s+?$', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_operstatus(self, data):
|
|
||||||
match = re.search(r'^(?:.+) is (.+),', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_lldp_intf(self, data):
|
|
||||||
match = re.search(r'^Local Interface: (.+)$', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_lldp_remote_desc(self, data):
|
|
||||||
match = re.search(r'Port Description: (.+)$', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_lldp_host(self, data):
|
|
||||||
match = re.search(r'System Name: (.+)$', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_lldp_port(self, data):
|
|
||||||
match = re.search(r'Port id: (.+)$', data, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
@ -1,91 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr lldp fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lldp_global.lldp_global import Lldp_globalArgs
|
|
||||||
|
|
||||||
|
|
||||||
class Lldp_globalFacts(object):
|
|
||||||
""" The iosxr lldp fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = Lldp_globalArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for lldp
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
if not data:
|
|
||||||
data = connection.get_config(flags='lldp')
|
|
||||||
|
|
||||||
obj = {}
|
|
||||||
if data:
|
|
||||||
lldp_obj = self.render_config(self.generated_spec, data)
|
|
||||||
if lldp_obj:
|
|
||||||
obj = lldp_obj
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('lldp_global', None)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': obj})
|
|
||||||
facts['lldp_global'] = utils.remove_empties(params['config'])
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
|
|
||||||
for key in spec.keys():
|
|
||||||
if key == 'subinterfaces':
|
|
||||||
config[key] = True if 'subinterfaces enable' in conf else None
|
|
||||||
|
|
||||||
elif key == 'tlv_select':
|
|
||||||
for item in ['system_name', 'port_description', 'management_address', 'system_description', 'system_capabilities']:
|
|
||||||
config[key][item] = False if ('{0} disable'.format(item.replace('_', '-'))) in conf else None
|
|
||||||
|
|
||||||
else:
|
|
||||||
value = utils.parse_conf_arg(conf, key)
|
|
||||||
config[key] = int(value) if value else value
|
|
||||||
|
|
||||||
return config
|
|
@ -1,96 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr lldp_interfaces fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class Lldp_interfacesFacts(object):
|
|
||||||
""" The iosxr lldp_interfaces fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec='config', options='options'):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = Lldp_interfacesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for lldp_interfaces
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
data = connection.get_config(flags='interface')
|
|
||||||
interfaces = data.split('interface ')
|
|
||||||
|
|
||||||
objs = []
|
|
||||||
for interface in interfaces:
|
|
||||||
obj = self.render_config(self.generated_spec, interface)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].pop('lldp_interfaces', None)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
if objs:
|
|
||||||
facts['lldp_interfaces'] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {'config': objs})
|
|
||||||
for cfg in params['config']:
|
|
||||||
facts['lldp_interfaces'].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts['ansible_network_resources'].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
|
|
||||||
match = re.search(r'(GigabitEthernet|Bundle-Ether|TenGigE|FortyGigE|HundredGigE)(\S+)', conf, re.M)
|
|
||||||
if match:
|
|
||||||
config['name'] = match.group(1) + match.group(2)
|
|
||||||
|
|
||||||
for key in ['receive', 'transmit']:
|
|
||||||
config[key] = False if ('{0} disable'.format(key)) in conf else None
|
|
||||||
|
|
||||||
for x in ['ieee-nearest-bridge', 'ieee-nearest-non-tmpr-bridge']:
|
|
||||||
if x in conf:
|
|
||||||
config['destination']['mac_address'] = x
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
@ -1,176 +0,0 @@
|
|||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The iosxr static_routes fact class
|
|
||||||
It is in this file the configuration is collected from the device
|
|
||||||
for a given resource, parsed, and the facts tree is populated
|
|
||||||
based on the configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
from ansible.module_utils.network.common import utils
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.static_routes.static_routes import Static_routesArgs
|
|
||||||
|
|
||||||
|
|
||||||
class Static_routesFacts(object):
|
|
||||||
""" The iosxr static_routes fact class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, module, subspec="config", options="options"):
|
|
||||||
self._module = module
|
|
||||||
self.argument_spec = Static_routesArgs.argument_spec
|
|
||||||
spec = deepcopy(self.argument_spec)
|
|
||||||
if subspec:
|
|
||||||
if options:
|
|
||||||
facts_argument_spec = spec[subspec][options]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec[subspec]
|
|
||||||
else:
|
|
||||||
facts_argument_spec = spec
|
|
||||||
|
|
||||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
|
||||||
|
|
||||||
def get_device_data(self, connection):
|
|
||||||
return connection.get_config(flags="router static")
|
|
||||||
|
|
||||||
def populate_facts(self, connection, ansible_facts, data=None):
|
|
||||||
""" Populate the facts for static_routes
|
|
||||||
:param connection: the device connection
|
|
||||||
:param ansible_facts: Facts dictionary
|
|
||||||
:param data: previously collected conf
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: facts
|
|
||||||
"""
|
|
||||||
if not data:
|
|
||||||
data = self.get_device_data(connection)
|
|
||||||
|
|
||||||
objs = []
|
|
||||||
|
|
||||||
if "No such configuration" not in data:
|
|
||||||
for entry in re.compile(r"(\s) vrf").split(data):
|
|
||||||
obj = self.render_config(self.generated_spec, entry)
|
|
||||||
if obj:
|
|
||||||
objs.append(obj)
|
|
||||||
|
|
||||||
ansible_facts["ansible_network_resources"].pop("static_routes", None)
|
|
||||||
facts = {}
|
|
||||||
|
|
||||||
facts["static_routes"] = []
|
|
||||||
params = utils.validate_config(self.argument_spec, {"config": objs})
|
|
||||||
for cfg in params["config"]:
|
|
||||||
facts["static_routes"].append(utils.remove_empties(cfg))
|
|
||||||
|
|
||||||
ansible_facts["ansible_network_resources"].update(facts)
|
|
||||||
return ansible_facts
|
|
||||||
|
|
||||||
def render_config(self, spec, conf):
|
|
||||||
"""
|
|
||||||
Render config as dictionary structure and delete keys
|
|
||||||
from spec for null values
|
|
||||||
|
|
||||||
:param spec: The facts tree, generated from the argspec
|
|
||||||
:param conf: The configuration
|
|
||||||
:rtype: dictionary
|
|
||||||
:returns: The generated config
|
|
||||||
"""
|
|
||||||
config = deepcopy(spec)
|
|
||||||
entry_list = conf.split(" address-family")
|
|
||||||
config["address_families"] = []
|
|
||||||
|
|
||||||
if "router static" not in entry_list[0]:
|
|
||||||
config["vrf"] = entry_list[0].replace("!", "").strip()
|
|
||||||
|
|
||||||
for item in entry_list[1:]:
|
|
||||||
routes = []
|
|
||||||
address_family = {"routes": []}
|
|
||||||
address_family["afi"], address_family["safi"] = self.parse_af(item)
|
|
||||||
|
|
||||||
destinations = re.findall(r"((?:\S+)/(?:\d+)) (?:.*)", item, re.M)
|
|
||||||
for dest in set(destinations):
|
|
||||||
route = {"next_hops": []}
|
|
||||||
route["dest"] = dest
|
|
||||||
|
|
||||||
regex = r"%s .+$" % dest
|
|
||||||
cfg = re.findall(regex, item, re.M)
|
|
||||||
|
|
||||||
for route_entry in cfg:
|
|
||||||
exit_point = {}
|
|
||||||
exit_point["forward_router_address"] = self.parse_faddr(route_entry)
|
|
||||||
exit_point["interface"] = self.parse_intf(route_entry)
|
|
||||||
exit_point["admin_distance"] = self.parse_admin_distance(route_entry)
|
|
||||||
|
|
||||||
for x in [
|
|
||||||
"tag",
|
|
||||||
"tunnel-id",
|
|
||||||
"metric",
|
|
||||||
"description",
|
|
||||||
"track",
|
|
||||||
"vrflabel",
|
|
||||||
"dest_vrf",
|
|
||||||
]:
|
|
||||||
exit_point[x.replace("-", "_")] = self.parse_attrib(
|
|
||||||
route_entry, x.replace("dest_vrf", "vrf")
|
|
||||||
)
|
|
||||||
|
|
||||||
route["next_hops"].append(exit_point)
|
|
||||||
|
|
||||||
routes.append(route)
|
|
||||||
address_family["routes"] = sorted(routes, key=lambda i: i["dest"])
|
|
||||||
config["address_families"].append(address_family)
|
|
||||||
|
|
||||||
return utils.remove_empties(config)
|
|
||||||
|
|
||||||
def parse_af(self, item):
|
|
||||||
match = re.search(r"(?:\s*)(\w+)(?:\s*)(\w+)", item, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1), match.group(2)
|
|
||||||
|
|
||||||
def parse_faddr(self, item):
|
|
||||||
for x in item.split(" "):
|
|
||||||
if (":" in x or "." in x) and "/" not in x:
|
|
||||||
return x
|
|
||||||
|
|
||||||
def parse_intf(self, item):
|
|
||||||
match = re.search(r" ((\w+)((?:\d)/(?:\d)/(?:\d)/(?:\d+)))", item)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_attrib(self, item, attrib):
|
|
||||||
match = re.search(r" %s (\S+)" % attrib, item)
|
|
||||||
if match:
|
|
||||||
val = match.group(1).strip("'")
|
|
||||||
if attrib in ["tunnel-id", "vrflabel", "tag", "metric"]:
|
|
||||||
val = int(val)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def parse_admin_distance(self, item):
|
|
||||||
split_item = item.split(" ")
|
|
||||||
for item in [
|
|
||||||
"vrf",
|
|
||||||
"metric",
|
|
||||||
"tunnel-id",
|
|
||||||
"vrflabel",
|
|
||||||
"track",
|
|
||||||
"tag",
|
|
||||||
"description",
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
del split_item[split_item.index(item) + 1]
|
|
||||||
del split_item[split_item.index(item)]
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
return [
|
|
||||||
i for i in split_item if "." not in i and ":" not in i and ord(i[0]) > 48 and ord(i[0]) < 57
|
|
||||||
][0]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
@ -1,565 +0,0 @@
|
|||||||
# This code is part of Ansible, but is an independent component.
|
|
||||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
||||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
|
||||||
# still belong to the author of the module, and may assign their own license
|
|
||||||
# to the complete work.
|
|
||||||
#
|
|
||||||
# Copyright (c) 2015 Peter Sprygada, <psprygada@ansible.com>
|
|
||||||
# Copyright (c) 2017 Red Hat Inc.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
# are permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# * Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
|
||||||
# and/or other materials provided with the distribution.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
||||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
from difflib import Differ
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text, to_bytes
|
|
||||||
from ansible.module_utils.basic import env_fallback
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.connection import Connection, ConnectionError
|
|
||||||
from ansible.module_utils.network.common.netconf import NetconfConnection
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ncclient.xml_ import to_xml
|
|
||||||
HAS_NCCLIENT = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_NCCLIENT = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
from lxml import etree
|
|
||||||
HAS_XML = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_XML = False
|
|
||||||
|
|
||||||
_EDIT_OPS = frozenset(['merge', 'create', 'replace', 'delete'])
|
|
||||||
|
|
||||||
BASE_1_0 = "{urn:ietf:params:xml:ns:netconf:base:1.0}"
|
|
||||||
|
|
||||||
NS_DICT = {
|
|
||||||
'BASE_NSMAP': {"xc": "urn:ietf:params:xml:ns:netconf:base:1.0"},
|
|
||||||
'BANNERS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-infra-cfg"},
|
|
||||||
'INTERFACES_NSMAP': {None: "http://openconfig.net/yang/interfaces"},
|
|
||||||
'INSTALL_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-installmgr-admin-oper"},
|
|
||||||
'HOST-NAMES_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-shellutil-cfg"},
|
|
||||||
'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"},
|
|
||||||
'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"},
|
|
||||||
'IP-DOMAIN_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ip-domain-cfg"},
|
|
||||||
'SYSLOG_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-syslog-cfg"},
|
|
||||||
'AAA_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-lib-cfg"},
|
|
||||||
'AAA_LOCALD_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-locald-cfg"},
|
|
||||||
}
|
|
||||||
|
|
||||||
iosxr_provider_spec = {
|
|
||||||
'host': dict(),
|
|
||||||
'port': dict(type='int'),
|
|
||||||
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
|
|
||||||
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
|
||||||
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
|
|
||||||
'timeout': dict(type='int'),
|
|
||||||
'transport': dict(type='str', default='cli', choices=['cli', 'netconf']),
|
|
||||||
}
|
|
||||||
|
|
||||||
iosxr_argument_spec = {
|
|
||||||
'provider': dict(type='dict', options=iosxr_provider_spec, removed_in_version=2.14)
|
|
||||||
}
|
|
||||||
|
|
||||||
command_spec = {
|
|
||||||
'command': dict(),
|
|
||||||
'prompt': dict(default=None),
|
|
||||||
'answer': dict(default=None)
|
|
||||||
}
|
|
||||||
|
|
||||||
CONFIG_MISPLACED_CHILDREN = [
|
|
||||||
re.compile(r'^end-\s*(.+)$')
|
|
||||||
]
|
|
||||||
|
|
||||||
# Objects defined in Route-policy Language guide of IOS_XR.
|
|
||||||
# Reconfiguring these objects replace existing configurations.
|
|
||||||
# Hence these objects should be played direcly from candidate
|
|
||||||
# configurations
|
|
||||||
CONFIG_BLOCKS_FORCED_IN_DIFF = [
|
|
||||||
{
|
|
||||||
'start': re.compile(r'route-policy'),
|
|
||||||
'end': re.compile(r'end-policy')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'start': re.compile(r'prefix-set'),
|
|
||||||
'end': re.compile(r'end-set')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'start': re.compile(r'as-path-set'),
|
|
||||||
'end': re.compile(r'end-set')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'start': re.compile(r'community-set'),
|
|
||||||
'end': re.compile(r'end-set')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'start': re.compile(r'rd-set'),
|
|
||||||
'end': re.compile(r'end-set')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'start': re.compile(r'extcommunity-set'),
|
|
||||||
'end': re.compile(r'end-set')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_provider_argspec():
|
|
||||||
return iosxr_provider_spec
|
|
||||||
|
|
||||||
|
|
||||||
def get_connection(module):
|
|
||||||
if hasattr(module, 'connection'):
|
|
||||||
return module.connection
|
|
||||||
|
|
||||||
capabilities = get_capabilities(module)
|
|
||||||
network_api = capabilities.get('network_api')
|
|
||||||
if network_api == 'cliconf':
|
|
||||||
module.connection = Connection(module._socket_path)
|
|
||||||
elif network_api == 'netconf':
|
|
||||||
module.connection = NetconfConnection(module._socket_path)
|
|
||||||
else:
|
|
||||||
module.fail_json(msg='Invalid connection type {0!s}'.format(network_api))
|
|
||||||
|
|
||||||
return module.connection
|
|
||||||
|
|
||||||
|
|
||||||
def get_capabilities(module):
|
|
||||||
if hasattr(module, 'capabilities'):
|
|
||||||
return module.capabilities
|
|
||||||
try:
|
|
||||||
capabilities = Connection(module._socket_path).get_capabilities()
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
module.capabilities = json.loads(capabilities)
|
|
||||||
|
|
||||||
return module.capabilities
|
|
||||||
|
|
||||||
|
|
||||||
def build_xml_subtree(container_ele, xmap, param=None, opcode=None):
|
|
||||||
sub_root = container_ele
|
|
||||||
meta_subtree = list()
|
|
||||||
|
|
||||||
for key, meta in xmap.items():
|
|
||||||
candidates = meta.get('xpath', "").split("/")
|
|
||||||
if container_ele.tag == candidates[-2]:
|
|
||||||
parent = container_ele
|
|
||||||
elif sub_root.tag == candidates[-2]:
|
|
||||||
parent = sub_root
|
|
||||||
else:
|
|
||||||
parent = sub_root.find(".//" + meta.get('xpath', "").split(sub_root.tag + '/', 1)[1].rsplit('/', 1)[0])
|
|
||||||
|
|
||||||
if ((opcode in ('delete', 'merge') and meta.get('operation', 'unknown') == 'edit') or
|
|
||||||
meta.get('operation', None) is None):
|
|
||||||
|
|
||||||
if meta.get('tag', False) is True:
|
|
||||||
if parent.tag == container_ele.tag:
|
|
||||||
if meta.get('ns', False) is True:
|
|
||||||
child = etree.Element(candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
|
|
||||||
else:
|
|
||||||
child = etree.Element(candidates[-1])
|
|
||||||
meta_subtree.append(child)
|
|
||||||
sub_root = child
|
|
||||||
else:
|
|
||||||
if meta.get('ns', False) is True:
|
|
||||||
child = etree.SubElement(parent, candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
|
|
||||||
else:
|
|
||||||
child = etree.SubElement(parent, candidates[-1])
|
|
||||||
|
|
||||||
if meta.get('attrib', None) is not None and opcode in ('delete', 'merge'):
|
|
||||||
child.set(BASE_1_0 + meta.get('attrib'), opcode)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
text = None
|
|
||||||
param_key = key.split(":")
|
|
||||||
if param_key[0] == 'a':
|
|
||||||
if param is not None and param.get(param_key[1], None) is not None:
|
|
||||||
text = param.get(param_key[1])
|
|
||||||
elif param_key[0] == 'm':
|
|
||||||
if meta.get('value', None) is not None:
|
|
||||||
text = meta.get('value')
|
|
||||||
|
|
||||||
if text:
|
|
||||||
if meta.get('ns', False) is True:
|
|
||||||
child = etree.SubElement(parent, candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
|
|
||||||
else:
|
|
||||||
child = etree.SubElement(parent, candidates[-1])
|
|
||||||
child.text = text
|
|
||||||
|
|
||||||
if meta.get('attrib', None) is not None and opcode in ('delete', 'merge'):
|
|
||||||
child.set(BASE_1_0 + meta.get('attrib'), opcode)
|
|
||||||
|
|
||||||
if len(meta_subtree) > 1:
|
|
||||||
for item in meta_subtree:
|
|
||||||
container_ele.append(item)
|
|
||||||
|
|
||||||
if sub_root == container_ele:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return sub_root
|
|
||||||
|
|
||||||
|
|
||||||
def build_xml(container, xmap=None, params=None, opcode=None):
|
|
||||||
"""
|
|
||||||
Builds netconf xml rpc document from meta-data
|
|
||||||
|
|
||||||
Args:
|
|
||||||
container: the YANG container within the namespace
|
|
||||||
xmap: meta-data map to build xml tree
|
|
||||||
params: Input params that feed xml tree values
|
|
||||||
opcode: operation to be performed (merge, delete etc.)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
Module inputs:
|
|
||||||
banner_params = [{'banner':'motd', 'text':'Ansible banner example', 'state':'present'}]
|
|
||||||
|
|
||||||
Meta-data definition:
|
|
||||||
bannermap = collections.OrderedDict()
|
|
||||||
bannermap.update([
|
|
||||||
('banner', {'xpath' : 'banners/banner', 'tag' : True, 'attrib' : "operation"}),
|
|
||||||
('a:banner', {'xpath' : 'banner/banner-name'}),
|
|
||||||
('a:text', {'xpath' : 'banner/banner-text', 'operation' : 'edit'})
|
|
||||||
])
|
|
||||||
|
|
||||||
Fields:
|
|
||||||
key: exact match to the key in arg_spec for a parameter
|
|
||||||
(prefixes --> a: value fetched from arg_spec, m: value fetched from meta-data)
|
|
||||||
xpath: xpath of the element (based on YANG model)
|
|
||||||
tag: True if no text on the element
|
|
||||||
attrib: attribute to be embedded in the element (e.g. xc:operation="merge")
|
|
||||||
operation: if edit --> includes the element in edit_config() query else ignores for get() queries
|
|
||||||
value: if key is prefixed with "m:", value is required in meta-data
|
|
||||||
|
|
||||||
Output:
|
|
||||||
<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>
|
|
||||||
:returns: xml rpc document as a string
|
|
||||||
"""
|
|
||||||
if opcode == 'filter':
|
|
||||||
root = etree.Element("filter", type="subtree")
|
|
||||||
elif opcode in ('delete', 'merge'):
|
|
||||||
root = etree.Element("config", nsmap=NS_DICT['BASE_NSMAP'])
|
|
||||||
|
|
||||||
container_ele = etree.SubElement(root, container, nsmap=NS_DICT[container.upper() + "_NSMAP"])
|
|
||||||
|
|
||||||
if xmap is not None:
|
|
||||||
if params is None:
|
|
||||||
build_xml_subtree(container_ele, xmap, opcode=opcode)
|
|
||||||
else:
|
|
||||||
subtree_list = list()
|
|
||||||
for param in to_list(params):
|
|
||||||
subtree_ele = build_xml_subtree(container_ele, xmap, param=param, opcode=opcode)
|
|
||||||
if subtree_ele is not None:
|
|
||||||
subtree_list.append(subtree_ele)
|
|
||||||
|
|
||||||
for item in subtree_list:
|
|
||||||
container_ele.append(item)
|
|
||||||
|
|
||||||
return etree.tostring(root, encoding='unicode')
|
|
||||||
|
|
||||||
|
|
||||||
def etree_find(root, node):
|
|
||||||
try:
|
|
||||||
root = etree.fromstring(to_bytes(root))
|
|
||||||
except (ValueError, etree.XMLSyntaxError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return root.find('.//%s' % node.strip())
|
|
||||||
|
|
||||||
|
|
||||||
def etree_findall(root, node):
|
|
||||||
try:
|
|
||||||
root = etree.fromstring(to_bytes(root))
|
|
||||||
except (ValueError, etree.XMLSyntaxError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return root.findall('.//%s' % node.strip())
|
|
||||||
|
|
||||||
|
|
||||||
def is_cliconf(module):
|
|
||||||
capabilities = get_capabilities(module)
|
|
||||||
return (capabilities.get('network_api') == 'cliconf')
|
|
||||||
|
|
||||||
|
|
||||||
def is_netconf(module):
|
|
||||||
capabilities = get_capabilities(module)
|
|
||||||
network_api = capabilities.get('network_api')
|
|
||||||
if network_api == 'netconf':
|
|
||||||
if not HAS_NCCLIENT:
|
|
||||||
module.fail_json(msg='ncclient is not installed')
|
|
||||||
if not HAS_XML:
|
|
||||||
module.fail_json(msg='lxml is not installed')
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_config_diff(module, running=None, candidate=None):
|
|
||||||
conn = get_connection(module)
|
|
||||||
|
|
||||||
if is_cliconf(module):
|
|
||||||
try:
|
|
||||||
response = conn.get('show commit changes diff')
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
return response
|
|
||||||
elif is_netconf(module):
|
|
||||||
if running and candidate:
|
|
||||||
# ignore rpc-reply root node and diff from data element onwards
|
|
||||||
running_data_ele = etree.fromstring(to_bytes(running.strip())).getchildren()[0]
|
|
||||||
candidate_data_ele = etree.fromstring(to_bytes(candidate.strip())).getchildren()[0]
|
|
||||||
|
|
||||||
running_data = to_text(etree.tostring(running_data_ele)).strip()
|
|
||||||
candidate_data = to_text(etree.tostring(candidate_data_ele)).strip()
|
|
||||||
if running_data != candidate_data:
|
|
||||||
d = Differ()
|
|
||||||
diff = list(d.compare(running_data.splitlines(), candidate_data.splitlines()))
|
|
||||||
return '\n'.join(diff).strip()
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def discard_config(module):
|
|
||||||
conn = get_connection(module)
|
|
||||||
try:
|
|
||||||
if is_netconf(module):
|
|
||||||
conn.discard_changes(remove_ns=True)
|
|
||||||
else:
|
|
||||||
conn.discard_changes()
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
|
|
||||||
|
|
||||||
def commit_config(module, comment=None, confirmed=False, confirm_timeout=None,
|
|
||||||
persist=False, check=False, label=None):
|
|
||||||
conn = get_connection(module)
|
|
||||||
reply = None
|
|
||||||
try:
|
|
||||||
if is_netconf(module):
|
|
||||||
if check:
|
|
||||||
reply = conn.validate(remove_ns=True)
|
|
||||||
else:
|
|
||||||
reply = conn.commit(confirmed=confirmed, timeout=confirm_timeout, persist=persist, remove_ns=True)
|
|
||||||
elif is_cliconf(module):
|
|
||||||
if check:
|
|
||||||
module.fail_json(msg="Validate configuration is not supported with network_cli connection type")
|
|
||||||
else:
|
|
||||||
reply = conn.commit(comment=comment, label=label)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
|
|
||||||
return reply
|
|
||||||
|
|
||||||
|
|
||||||
def get_oper(module, filter=None):
|
|
||||||
conn = get_connection(module)
|
|
||||||
|
|
||||||
if filter is not None:
|
|
||||||
try:
|
|
||||||
if is_netconf(module):
|
|
||||||
response = conn.get(filter=filter, remove_ns=True)
|
|
||||||
else:
|
|
||||||
response = conn.get(filter)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return to_bytes(etree.tostring(response), errors='surrogate_then_replace').strip()
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(module, config_filter=None, source='running'):
|
|
||||||
conn = get_connection(module)
|
|
||||||
|
|
||||||
# Note: Does not cache config in favour of latest config on every get operation.
|
|
||||||
try:
|
|
||||||
if is_netconf(module):
|
|
||||||
out = to_xml(conn.get_config(source=source, filter=config_filter, remove_ns=True))
|
|
||||||
elif is_cliconf(module):
|
|
||||||
out = conn.get_config(source=source, flags=config_filter)
|
|
||||||
cfg = out.strip()
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
return cfg
|
|
||||||
|
|
||||||
|
|
||||||
def check_existing_commit_labels(conn, label):
|
|
||||||
out = conn.get(command='show configuration history detail | include %s' % label)
|
|
||||||
label_exist = re.search(label, out, re.M)
|
|
||||||
if label_exist:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def load_config(module, command_filter, commit=False, replace=False,
|
|
||||||
comment=None, admin=False, exclusive=False, running=None, nc_get_filter=None,
|
|
||||||
label=None):
|
|
||||||
|
|
||||||
conn = get_connection(module)
|
|
||||||
|
|
||||||
diff = None
|
|
||||||
if is_netconf(module):
|
|
||||||
# FIXME: check for platform behaviour and restore this
|
|
||||||
# conn.lock(target = 'candidate')
|
|
||||||
# conn.discard_changes()
|
|
||||||
|
|
||||||
try:
|
|
||||||
for filter in to_list(command_filter):
|
|
||||||
conn.edit_config(config=filter, remove_ns=True)
|
|
||||||
|
|
||||||
candidate = get_config(module, source='candidate', config_filter=nc_get_filter)
|
|
||||||
diff = get_config_diff(module, running, candidate)
|
|
||||||
|
|
||||||
if commit and diff:
|
|
||||||
commit_config(module)
|
|
||||||
else:
|
|
||||||
discard_config(module)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
finally:
|
|
||||||
# conn.unlock(target = 'candidate')
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif is_cliconf(module):
|
|
||||||
try:
|
|
||||||
if label:
|
|
||||||
old_label = check_existing_commit_labels(conn, label)
|
|
||||||
if old_label:
|
|
||||||
module.fail_json(
|
|
||||||
msg='commit label {%s} is already used for'
|
|
||||||
' an earlier commit, please choose a different label'
|
|
||||||
' and rerun task' % label
|
|
||||||
)
|
|
||||||
|
|
||||||
response = conn.edit_config(candidate=command_filter, commit=commit, admin=admin,
|
|
||||||
exclusive=exclusive, replace=replace, comment=comment, label=label)
|
|
||||||
if module._diff:
|
|
||||||
diff = response.get('diff')
|
|
||||||
|
|
||||||
# Overwrite the default diff by the IOS XR commit diff.
|
|
||||||
# See plugins/cliconf/iosxr.py for this key set: show_commit_config_diff
|
|
||||||
diff = response.get('show_commit_config_diff')
|
|
||||||
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
|
|
||||||
return diff
|
|
||||||
|
|
||||||
|
|
||||||
def run_commands(module, commands, check_rc=True):
|
|
||||||
connection = get_connection(module)
|
|
||||||
try:
|
|
||||||
return connection.run_commands(commands=commands, check_rc=check_rc)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc))
|
|
||||||
|
|
||||||
|
|
||||||
def copy_file(module, src, dst, proto='scp'):
|
|
||||||
conn = get_connection(module)
|
|
||||||
try:
|
|
||||||
conn.copy_file(source=src, destination=dst, proto=proto)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
|
|
||||||
|
|
||||||
def get_file(module, src, dst, proto='scp'):
|
|
||||||
conn = get_connection(module)
|
|
||||||
try:
|
|
||||||
conn.get_file(source=src, destination=dst, proto=proto)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
|
|
||||||
|
|
||||||
# A list of commands like {end-set, end-policy, ...} are part of configuration
|
|
||||||
# block like { prefix-set, as-path-set , ... } but they are not indented properly
|
|
||||||
# to be included with their parent. sanitize_config will add indentation to
|
|
||||||
# end-* commands so they are included with their parents
|
|
||||||
def sanitize_config(config, force_diff_prefix=None):
|
|
||||||
conf_lines = config.split('\n')
|
|
||||||
for regex in CONFIG_MISPLACED_CHILDREN:
|
|
||||||
for index, line in enumerate(conf_lines):
|
|
||||||
m = regex.search(line)
|
|
||||||
if m and m.group(0):
|
|
||||||
if force_diff_prefix:
|
|
||||||
conf_lines[index] = ' ' + m.group(0) + force_diff_prefix
|
|
||||||
else:
|
|
||||||
conf_lines[index] = ' ' + m.group(0)
|
|
||||||
conf = ('\n').join(conf_lines)
|
|
||||||
return conf
|
|
||||||
|
|
||||||
|
|
||||||
def mask_config_blocks_from_diff(config, candidate, force_diff_prefix):
|
|
||||||
conf_lines = config.split('\n')
|
|
||||||
candidate_lines = candidate.split('\n')
|
|
||||||
|
|
||||||
for regex in CONFIG_BLOCKS_FORCED_IN_DIFF:
|
|
||||||
block_index_start_end = []
|
|
||||||
for index, line in enumerate(candidate_lines):
|
|
||||||
startre = regex['start'].search(line)
|
|
||||||
if startre and startre.group(0):
|
|
||||||
start_index = index
|
|
||||||
else:
|
|
||||||
endre = regex['end'].search(line)
|
|
||||||
if endre and endre.group(0):
|
|
||||||
end_index = index
|
|
||||||
new_block = True
|
|
||||||
for prev_start, prev_end in block_index_start_end:
|
|
||||||
if start_index == prev_start:
|
|
||||||
# This might be end-set of another regex
|
|
||||||
# otherwise we would be having new start
|
|
||||||
new_block = False
|
|
||||||
break
|
|
||||||
if new_block:
|
|
||||||
block_index_start_end.append((start_index, end_index))
|
|
||||||
|
|
||||||
for start, end in block_index_start_end:
|
|
||||||
diff = False
|
|
||||||
if candidate_lines[start] in conf_lines:
|
|
||||||
run_conf_start_index = conf_lines.index(candidate_lines[start])
|
|
||||||
else:
|
|
||||||
diff = False
|
|
||||||
continue
|
|
||||||
for i in range(start, end + 1):
|
|
||||||
if conf_lines[run_conf_start_index] == candidate_lines[i]:
|
|
||||||
run_conf_start_index = run_conf_start_index + 1
|
|
||||||
else:
|
|
||||||
diff = True
|
|
||||||
break
|
|
||||||
if diff:
|
|
||||||
run_conf_start_index = conf_lines.index(candidate_lines[start])
|
|
||||||
for i in range(start, end + 1):
|
|
||||||
conf_lines[run_conf_start_index] = conf_lines[run_conf_start_index] + force_diff_prefix
|
|
||||||
run_conf_start_index = run_conf_start_index + 1
|
|
||||||
|
|
||||||
conf = ('\n').join(conf_lines)
|
|
||||||
return conf
|
|
@ -1,114 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2019, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
#
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.providers.providers import CliProvider
|
|
||||||
|
|
||||||
|
|
||||||
class AddressFamily(CliProvider):
|
|
||||||
|
|
||||||
def render(self, config=None):
|
|
||||||
commands = list()
|
|
||||||
safe_list = list()
|
|
||||||
|
|
||||||
router_context = 'router bgp %s' % self.get_value('config.bgp_as')
|
|
||||||
context_config = None
|
|
||||||
|
|
||||||
for item in self.get_value('config.address_family'):
|
|
||||||
context = 'address-family %s %s' % (item['afi'], item['safi'])
|
|
||||||
context_commands = list()
|
|
||||||
|
|
||||||
if config:
|
|
||||||
context_path = [router_context, context]
|
|
||||||
context_config = self.get_config_context(config, context_path, indent=1)
|
|
||||||
|
|
||||||
for key, value in iteritems(item):
|
|
||||||
if value is not None:
|
|
||||||
meth = getattr(self, '_render_%s' % key, None)
|
|
||||||
if meth:
|
|
||||||
resp = meth(item, context_config)
|
|
||||||
if resp:
|
|
||||||
context_commands.extend(to_list(resp))
|
|
||||||
|
|
||||||
if context_commands:
|
|
||||||
commands.append(context)
|
|
||||||
commands.extend(context_commands)
|
|
||||||
commands.append('exit')
|
|
||||||
|
|
||||||
safe_list.append(context)
|
|
||||||
|
|
||||||
if config:
|
|
||||||
resp = self._negate_config(config, safe_list)
|
|
||||||
commands.extend(resp)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _negate_config(self, config, safe_list=None):
|
|
||||||
commands = list()
|
|
||||||
matches = re.findall(r'(address-family .+)$', config, re.M)
|
|
||||||
for item in set(matches).difference(safe_list):
|
|
||||||
commands.append('no %s' % item)
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_networks(self, item, config=None):
|
|
||||||
commands = list()
|
|
||||||
safe_list = list()
|
|
||||||
|
|
||||||
for entry in item['networks']:
|
|
||||||
network = entry['prefix']
|
|
||||||
if entry['masklen']:
|
|
||||||
network = '%s/%s' % (entry['prefix'], entry['masklen'])
|
|
||||||
safe_list.append(network)
|
|
||||||
|
|
||||||
cmd = 'network %s' % network
|
|
||||||
|
|
||||||
if entry['route_map']:
|
|
||||||
cmd += ' route-policy %s' % entry['route_map']
|
|
||||||
|
|
||||||
if not config or cmd not in config:
|
|
||||||
commands.append(cmd)
|
|
||||||
|
|
||||||
if config and self.params['operation'] == 'replace':
|
|
||||||
matches = re.findall(r'network (\S+)', config, re.M)
|
|
||||||
for entry in set(matches).difference(safe_list):
|
|
||||||
commands.append('no network %s' % entry)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_redistribute(self, item, config=None):
|
|
||||||
commands = list()
|
|
||||||
safe_list = list()
|
|
||||||
|
|
||||||
for entry in item['redistribute']:
|
|
||||||
option = entry['protocol']
|
|
||||||
|
|
||||||
cmd = 'redistribute %s' % entry['protocol']
|
|
||||||
|
|
||||||
if entry['id'] and entry['protocol'] in ('ospf', 'eigrp', 'isis', 'ospfv3'):
|
|
||||||
cmd += ' %s' % entry['id']
|
|
||||||
option += ' %s' % entry['id']
|
|
||||||
|
|
||||||
if entry['metric']:
|
|
||||||
cmd += ' metric %s' % entry['metric']
|
|
||||||
|
|
||||||
if entry['route_map']:
|
|
||||||
cmd += ' route-policy %s' % entry['route_map']
|
|
||||||
|
|
||||||
if not config or cmd not in config:
|
|
||||||
commands.append(cmd)
|
|
||||||
|
|
||||||
safe_list.append(option)
|
|
||||||
|
|
||||||
if self.params['operation'] == 'replace':
|
|
||||||
if config:
|
|
||||||
matches = re.findall(r'redistribute (\S+)(?:\s*)(\d*)', config, re.M)
|
|
||||||
for i in range(0, len(matches)):
|
|
||||||
matches[i] = ' '.join(matches[i]).strip()
|
|
||||||
for entry in set(matches).difference(safe_list):
|
|
||||||
commands.append('no redistribute %s' % entry)
|
|
||||||
|
|
||||||
return commands
|
|
@ -1,125 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2019, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
#
|
|
||||||
import re
|
|
||||||
import socket
|
|
||||||
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.providers.providers import CliProvider
|
|
||||||
|
|
||||||
|
|
||||||
class Neighbors(CliProvider):
|
|
||||||
|
|
||||||
def render(self, config=None):
|
|
||||||
commands = list()
|
|
||||||
safe_list = list()
|
|
||||||
|
|
||||||
router_context = 'router bgp %s' % self.get_value('config.bgp_as')
|
|
||||||
context_config = None
|
|
||||||
|
|
||||||
for item in self.get_value('config.neighbors'):
|
|
||||||
context_commands = list()
|
|
||||||
|
|
||||||
neighbor = item['neighbor']
|
|
||||||
|
|
||||||
try:
|
|
||||||
socket.inet_aton(neighbor)
|
|
||||||
context = 'neighbor %s' % neighbor
|
|
||||||
except socket.error:
|
|
||||||
context = 'neighbor-group %s' % neighbor
|
|
||||||
|
|
||||||
if config:
|
|
||||||
context_path = [router_context, context]
|
|
||||||
context_config = self.get_config_context(config, context_path, indent=1)
|
|
||||||
|
|
||||||
for key, value in iteritems(item):
|
|
||||||
if value is not None:
|
|
||||||
meth = getattr(self, '_render_%s' % key, None)
|
|
||||||
if meth:
|
|
||||||
resp = meth(item, context_config)
|
|
||||||
if resp:
|
|
||||||
context_commands.extend(to_list(resp))
|
|
||||||
|
|
||||||
if context_commands:
|
|
||||||
commands.append(context)
|
|
||||||
commands.extend(context_commands)
|
|
||||||
commands.append('exit')
|
|
||||||
|
|
||||||
safe_list.append(context)
|
|
||||||
|
|
||||||
if config and safe_list:
|
|
||||||
commands.extend(self._negate_config(config, safe_list))
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _negate_config(self, config, safe_list=None):
|
|
||||||
commands = list()
|
|
||||||
matches = re.findall(r'(neighbor \S+)', config, re.M)
|
|
||||||
for item in set(matches).difference(safe_list):
|
|
||||||
commands.append('no %s' % item)
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_remote_as(self, item, config=None):
|
|
||||||
cmd = 'remote-as %s' % item['remote_as']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_description(self, item, config=None):
|
|
||||||
cmd = 'description %s' % item['description']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_enabled(self, item, config=None):
|
|
||||||
cmd = 'shutdown'
|
|
||||||
if item['enabled'] is True:
|
|
||||||
cmd = 'no %s' % cmd
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_update_source(self, item, config=None):
|
|
||||||
cmd = 'update-source %s' % item['update_source'].replace(' ', '')
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_password(self, item, config=None):
|
|
||||||
cmd = 'password %s' % item['password']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_ebgp_multihop(self, item, config=None):
|
|
||||||
cmd = 'ebgp-multihop %s' % item['ebgp_multihop']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_tcp_mss(self, item, config=None):
|
|
||||||
cmd = 'tcp mss %s' % item['tcp_mss']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_advertisement_interval(self, item, config=None):
|
|
||||||
cmd = 'advertisement-interval %s' % item['advertisement_interval']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_neighbor_group(self, item, config=None):
|
|
||||||
cmd = 'use neighbor-group %s' % item['neighbor_group']
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_timers(self, item, config):
|
|
||||||
"""generate bgp timer related configuration
|
|
||||||
"""
|
|
||||||
keepalive = item['timers']['keepalive']
|
|
||||||
holdtime = item['timers']['holdtime']
|
|
||||||
min_neighbor_holdtime = item['timers']['min_neighbor_holdtime']
|
|
||||||
|
|
||||||
if keepalive and holdtime:
|
|
||||||
cmd = 'timers %s %s' % (keepalive, holdtime)
|
|
||||||
if min_neighbor_holdtime:
|
|
||||||
cmd += ' %s' % min_neighbor_holdtime
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
else:
|
|
||||||
raise ValueError("required both options for timers: keepalive and holdtime")
|
|
@ -1,97 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2019, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
#
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.providers.providers import register_provider
|
|
||||||
from ansible.module_utils.network.iosxr.providers.providers import CliProvider
|
|
||||||
from ansible.module_utils.network.iosxr.providers.cli.config.bgp.neighbors import Neighbors
|
|
||||||
from ansible.module_utils.network.iosxr.providers.cli.config.bgp.address_family import AddressFamily
|
|
||||||
|
|
||||||
REDISTRIBUTE_PROTOCOLS = frozenset(['ospf', 'ospfv3', 'eigrp', 'isis', 'static',
|
|
||||||
'connected', 'lisp', 'mobile', 'rip',
|
|
||||||
'subscriber'])
|
|
||||||
|
|
||||||
|
|
||||||
@register_provider('iosxr', 'iosxr_bgp')
|
|
||||||
class Provider(CliProvider):
|
|
||||||
|
|
||||||
def render(self, config=None):
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
existing_as = None
|
|
||||||
if config:
|
|
||||||
match = re.search(r'router bgp (\d+)', config, re.M)
|
|
||||||
if match:
|
|
||||||
existing_as = match.group(1)
|
|
||||||
|
|
||||||
operation = self.params['operation']
|
|
||||||
|
|
||||||
context = None
|
|
||||||
|
|
||||||
if self.params['config']:
|
|
||||||
context = 'router bgp %s' % self.get_value('config.bgp_as')
|
|
||||||
|
|
||||||
if operation == 'delete':
|
|
||||||
if existing_as:
|
|
||||||
commands.append('no router bgp %s' % existing_as)
|
|
||||||
elif context:
|
|
||||||
commands.append('no %s' % context)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if operation == 'replace':
|
|
||||||
if existing_as and int(existing_as) != self.get_value('config.bgp_as'):
|
|
||||||
# The negate command has to be committed before new BGP AS is used.
|
|
||||||
self.connection.edit_config('no router bgp %s' % existing_as)
|
|
||||||
config = None
|
|
||||||
|
|
||||||
elif operation == 'override':
|
|
||||||
if existing_as:
|
|
||||||
# The negate command has to be committed before new BGP AS is used.
|
|
||||||
self.connection.edit_config('no router bgp %s' % existing_as)
|
|
||||||
config = None
|
|
||||||
|
|
||||||
context_commands = list()
|
|
||||||
|
|
||||||
for key, value in iteritems(self.get_value('config')):
|
|
||||||
if value is not None:
|
|
||||||
meth = getattr(self, '_render_%s' % key, None)
|
|
||||||
if meth:
|
|
||||||
resp = meth(config)
|
|
||||||
if resp:
|
|
||||||
context_commands.extend(to_list(resp))
|
|
||||||
|
|
||||||
if context and context_commands:
|
|
||||||
commands.append(context)
|
|
||||||
commands.extend(context_commands)
|
|
||||||
commands.append('exit')
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
def _render_router_id(self, config=None):
|
|
||||||
cmd = 'bgp router-id %s' % self.get_value('config.router_id')
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def _render_log_neighbor_changes(self, config=None):
|
|
||||||
cmd = 'bgp log neighbor changes'
|
|
||||||
log_neighbor_changes = self.get_value('config.log_neighbor_changes')
|
|
||||||
if log_neighbor_changes is True:
|
|
||||||
if not config or cmd not in config:
|
|
||||||
return '%s detail' % cmd
|
|
||||||
elif log_neighbor_changes is False:
|
|
||||||
if config and cmd in config:
|
|
||||||
return '%s disable' % cmd
|
|
||||||
|
|
||||||
def _render_neighbors(self, config):
|
|
||||||
""" generate bgp neighbor configuration
|
|
||||||
"""
|
|
||||||
return Neighbors(self.params).render(config)
|
|
||||||
|
|
||||||
def _render_address_family(self, config):
|
|
||||||
""" generate address-family configuration
|
|
||||||
"""
|
|
||||||
return AddressFamily(self.params).render(config)
|
|
@ -1,62 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2019, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
#
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.connection import Connection
|
|
||||||
from ansible.module_utils.network.iosxr.providers import providers
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkModule(AnsibleModule):
|
|
||||||
|
|
||||||
fail_on_missing_provider = True
|
|
||||||
|
|
||||||
def __init__(self, connection=None, *args, **kwargs):
|
|
||||||
super(NetworkModule, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
if connection is None:
|
|
||||||
connection = Connection(self._socket_path)
|
|
||||||
|
|
||||||
self.connection = connection
|
|
||||||
|
|
||||||
@property
|
|
||||||
def provider(self):
|
|
||||||
if not hasattr(self, '_provider'):
|
|
||||||
capabilities = self.from_json(self.connection.get_capabilities())
|
|
||||||
|
|
||||||
network_os = capabilities['device_info']['network_os']
|
|
||||||
network_api = capabilities['network_api']
|
|
||||||
|
|
||||||
if network_api == 'cliconf':
|
|
||||||
connection_type = 'network_cli'
|
|
||||||
|
|
||||||
cls = providers.get(network_os, self._name.split('.')[-1], connection_type)
|
|
||||||
|
|
||||||
if not cls:
|
|
||||||
msg = 'unable to find suitable provider for network os %s' % network_os
|
|
||||||
if self.fail_on_missing_provider:
|
|
||||||
self.fail_json(msg=msg)
|
|
||||||
else:
|
|
||||||
self.warn(msg)
|
|
||||||
|
|
||||||
obj = cls(self.params, self.connection, self.check_mode)
|
|
||||||
|
|
||||||
setattr(self, '_provider', obj)
|
|
||||||
|
|
||||||
return getattr(self, '_provider')
|
|
||||||
|
|
||||||
def get_facts(self, subset=None):
|
|
||||||
try:
|
|
||||||
self.provider.get_facts(subset)
|
|
||||||
except Exception as exc:
|
|
||||||
self.fail_json(msg=to_text(exc))
|
|
||||||
|
|
||||||
def edit_config(self, config_filter=None):
|
|
||||||
current_config = self.connection.get_config(flags=config_filter)
|
|
||||||
try:
|
|
||||||
commands = self.provider.edit_config(current_config)
|
|
||||||
changed = bool(commands)
|
|
||||||
return {'commands': commands, 'changed': changed}
|
|
||||||
except Exception as exc:
|
|
||||||
self.fail_json(msg=to_text(exc))
|
|
@ -1,120 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2019, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
#
|
|
||||||
import json
|
|
||||||
|
|
||||||
from threading import RLock
|
|
||||||
|
|
||||||
from ansible.module_utils.six import itervalues
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.common.config import NetworkConfig
|
|
||||||
|
|
||||||
|
|
||||||
_registered_providers = {}
|
|
||||||
_provider_lock = RLock()
|
|
||||||
|
|
||||||
|
|
||||||
def register_provider(network_os, module_name):
|
|
||||||
def wrapper(cls):
|
|
||||||
_provider_lock.acquire()
|
|
||||||
try:
|
|
||||||
if network_os not in _registered_providers:
|
|
||||||
_registered_providers[network_os] = {}
|
|
||||||
for ct in cls.supported_connections:
|
|
||||||
if ct not in _registered_providers[network_os]:
|
|
||||||
_registered_providers[network_os][ct] = {}
|
|
||||||
for item in to_list(module_name):
|
|
||||||
for entry in itervalues(_registered_providers[network_os]):
|
|
||||||
entry[item] = cls
|
|
||||||
finally:
|
|
||||||
_provider_lock.release()
|
|
||||||
return cls
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def get(network_os, module_name, connection_type):
|
|
||||||
network_os_providers = _registered_providers.get(network_os)
|
|
||||||
if network_os_providers is None:
|
|
||||||
raise ValueError('unable to find a suitable provider for this module')
|
|
||||||
if connection_type not in network_os_providers:
|
|
||||||
raise ValueError('provider does not support this connection type')
|
|
||||||
elif module_name not in network_os_providers[connection_type]:
|
|
||||||
raise ValueError('could not find a suitable provider for this module')
|
|
||||||
return network_os_providers[connection_type][module_name]
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderBase(object):
|
|
||||||
|
|
||||||
supported_connections = ()
|
|
||||||
|
|
||||||
def __init__(self, params, connection=None, check_mode=False):
|
|
||||||
self.params = params
|
|
||||||
self.connection = connection
|
|
||||||
self.check_mode = check_mode
|
|
||||||
|
|
||||||
@property
|
|
||||||
def capabilities(self):
|
|
||||||
if not hasattr(self, '_capabilities'):
|
|
||||||
resp = self.from_json(self.connection.get_capabilities())
|
|
||||||
setattr(self, '_capabilities', resp)
|
|
||||||
return getattr(self, '_capabilities')
|
|
||||||
|
|
||||||
def get_value(self, path):
|
|
||||||
params = self.params.copy()
|
|
||||||
for key in path.split('.'):
|
|
||||||
params = params[key]
|
|
||||||
return params
|
|
||||||
|
|
||||||
def get_facts(self, subset=None):
|
|
||||||
raise NotImplementedError(self.__class__.__name__)
|
|
||||||
|
|
||||||
def edit_config(self):
|
|
||||||
raise NotImplementedError(self.__class__.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CliProvider(ProviderBase):
|
|
||||||
|
|
||||||
supported_connections = ('network_cli',)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def capabilities(self):
|
|
||||||
if not hasattr(self, '_capabilities'):
|
|
||||||
resp = self.from_json(self.connection.get_capabilities())
|
|
||||||
setattr(self, '_capabilities', resp)
|
|
||||||
return getattr(self, '_capabilities')
|
|
||||||
|
|
||||||
def get_config_context(self, config, path, indent=1):
|
|
||||||
if config is not None:
|
|
||||||
netcfg = NetworkConfig(indent=indent, contents=config)
|
|
||||||
try:
|
|
||||||
config = netcfg.get_block_config(to_list(path))
|
|
||||||
except ValueError:
|
|
||||||
config = None
|
|
||||||
return config
|
|
||||||
|
|
||||||
def render(self, config=None):
|
|
||||||
raise NotImplementedError(self.__class__.__name__)
|
|
||||||
|
|
||||||
def cli(self, command):
|
|
||||||
try:
|
|
||||||
if not hasattr(self, '_command_output'):
|
|
||||||
setattr(self, '_command_output', {})
|
|
||||||
return self._command_output[command]
|
|
||||||
except KeyError:
|
|
||||||
out = self.connection.get(command)
|
|
||||||
try:
|
|
||||||
out = json.loads(out)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
self._command_output[command] = out
|
|
||||||
return out
|
|
||||||
|
|
||||||
def get_facts(self, subset=None):
|
|
||||||
return self.populate()
|
|
||||||
|
|
||||||
def edit_config(self, config=None):
|
|
||||||
commands = self.render(config)
|
|
||||||
if commands and self.check_mode is False:
|
|
||||||
self.connection.edit_config(commands)
|
|
||||||
return commands
|
|
@ -1,355 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
# utils
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
from ansible.module_utils.compat import ipaddress
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
from ansible.module_utils.network.common.utils import dict_diff, is_masklen, to_netmask, search_obj_in_list
|
|
||||||
|
|
||||||
|
|
||||||
def remove_command_from_config_list(interface, cmd, commands):
|
|
||||||
# To delete the passed config
|
|
||||||
if interface not in commands:
|
|
||||||
commands.insert(0, interface)
|
|
||||||
commands.append('no %s' % cmd)
|
|
||||||
return commands
|
|
||||||
|
|
||||||
|
|
||||||
def add_command_to_config_list(interface, cmd, commands):
|
|
||||||
# To set the passed config
|
|
||||||
if interface not in commands:
|
|
||||||
commands.insert(0, interface)
|
|
||||||
commands.append(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def dict_to_set(sample_dict):
|
|
||||||
# Generate a set with passed dictionary for comparison
|
|
||||||
test_dict = {}
|
|
||||||
if isinstance(sample_dict, dict):
|
|
||||||
for k, v in iteritems(sample_dict):
|
|
||||||
if v is not None:
|
|
||||||
if isinstance(v, list):
|
|
||||||
if isinstance(v[0], dict):
|
|
||||||
li = []
|
|
||||||
for each in v:
|
|
||||||
for key, value in iteritems(each):
|
|
||||||
if isinstance(value, list):
|
|
||||||
each[key] = tuple(value)
|
|
||||||
li.append(tuple(iteritems(each)))
|
|
||||||
v = tuple(li)
|
|
||||||
else:
|
|
||||||
v = tuple(v)
|
|
||||||
elif isinstance(v, dict):
|
|
||||||
li = []
|
|
||||||
for key, value in iteritems(v):
|
|
||||||
if isinstance(value, list):
|
|
||||||
v[key] = tuple(value)
|
|
||||||
li.extend(tuple(iteritems(v)))
|
|
||||||
v = tuple(li)
|
|
||||||
test_dict.update({k: v})
|
|
||||||
return_set = set(tuple(iteritems(test_dict)))
|
|
||||||
else:
|
|
||||||
return_set = set(sample_dict)
|
|
||||||
return return_set
|
|
||||||
|
|
||||||
|
|
||||||
def filter_dict_having_none_value(want, have):
|
|
||||||
# Generate dict with have dict value which is None in want dict
|
|
||||||
test_dict = dict()
|
|
||||||
test_key_dict = dict()
|
|
||||||
name = want.get('name')
|
|
||||||
if name:
|
|
||||||
test_dict['name'] = name
|
|
||||||
diff_ip = False
|
|
||||||
want_ip = ''
|
|
||||||
for k, v in iteritems(want):
|
|
||||||
if isinstance(v, dict):
|
|
||||||
for key, value in iteritems(v):
|
|
||||||
if value is None:
|
|
||||||
dict_val = have.get(k).get(key)
|
|
||||||
test_key_dict.update({key: dict_val})
|
|
||||||
test_dict.update({k: test_key_dict})
|
|
||||||
if isinstance(v, list) and isinstance(v[0], dict):
|
|
||||||
for key, value in iteritems(v[0]):
|
|
||||||
if value is None:
|
|
||||||
dict_val = have.get(k).get(key)
|
|
||||||
test_key_dict.update({key: dict_val})
|
|
||||||
test_dict.update({k: test_key_dict})
|
|
||||||
# below conditions checks are added to check if
|
|
||||||
# secondary IP is configured, if yes then delete
|
|
||||||
# the already configured IP if want and have IP
|
|
||||||
# is different else if it's same no need to delete
|
|
||||||
for each in v:
|
|
||||||
if each.get('secondary'):
|
|
||||||
want_ip = each.get('address').split('/')
|
|
||||||
have_ip = have.get('ipv4')
|
|
||||||
for each in have_ip:
|
|
||||||
if len(want_ip) > 1 and each.get('secondary'):
|
|
||||||
have_ip = each.get('address').split(' ')[0]
|
|
||||||
if have_ip != want_ip[0]:
|
|
||||||
diff_ip = True
|
|
||||||
if each.get('secondary') and diff_ip is True:
|
|
||||||
test_key_dict.update({'secondary': True})
|
|
||||||
test_dict.update({'ipv4': test_key_dict})
|
|
||||||
# Checks if want doesn't have secondary IP but have has secondary IP set
|
|
||||||
elif have.get('ipv4'):
|
|
||||||
if [
|
|
||||||
True for each_have in have.get('ipv4')
|
|
||||||
if 'secondary' in each_have
|
|
||||||
]:
|
|
||||||
test_dict.update({'ipv4': {'secondary': True}})
|
|
||||||
if k == 'l2protocol':
|
|
||||||
if want[k] != have.get('l2protocol') and have.get('l2protocol'):
|
|
||||||
test_dict.update({k: v})
|
|
||||||
if v is None:
|
|
||||||
val = have.get(k)
|
|
||||||
test_dict.update({k: val})
|
|
||||||
return test_dict
|
|
||||||
|
|
||||||
|
|
||||||
def remove_duplicate_interface(commands):
|
|
||||||
# Remove duplicate interface from commands
|
|
||||||
set_cmd = []
|
|
||||||
for each in commands:
|
|
||||||
if 'interface' in each:
|
|
||||||
if each not in set_cmd:
|
|
||||||
set_cmd.append(each)
|
|
||||||
else:
|
|
||||||
set_cmd.append(each)
|
|
||||||
|
|
||||||
return set_cmd
|
|
||||||
|
|
||||||
|
|
||||||
def flatten_dict(x):
|
|
||||||
result = {}
|
|
||||||
if not isinstance(x, dict):
|
|
||||||
return result
|
|
||||||
|
|
||||||
for key, value in iteritems(x):
|
|
||||||
if isinstance(value, dict):
|
|
||||||
result.update(flatten_dict(value))
|
|
||||||
else:
|
|
||||||
result[key] = value
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def dict_delete(base, comparable):
|
|
||||||
"""
|
|
||||||
|
|
||||||
This function generates a dict containing key, value pairs for keys
|
|
||||||
that are present in the `base` dict but not present in the `comparable`
|
|
||||||
dict.
|
|
||||||
|
|
||||||
:param base: dict object to base the diff on
|
|
||||||
:param comparable: dict object to compare against base
|
|
||||||
|
|
||||||
:returns: new dict object with key, value pairs that needs to be deleted.
|
|
||||||
|
|
||||||
"""
|
|
||||||
to_delete = dict()
|
|
||||||
|
|
||||||
for key in base:
|
|
||||||
if isinstance(base[key], dict):
|
|
||||||
sub_diff = dict_delete(base[key], comparable.get(key, {}))
|
|
||||||
if sub_diff:
|
|
||||||
to_delete[key] = sub_diff
|
|
||||||
else:
|
|
||||||
if key not in comparable:
|
|
||||||
to_delete[key] = base[key]
|
|
||||||
|
|
||||||
return to_delete
|
|
||||||
|
|
||||||
|
|
||||||
def pad_commands(commands, interface):
|
|
||||||
commands.insert(0, 'interface {0}'.format(interface))
|
|
||||||
|
|
||||||
|
|
||||||
def diff_list_of_dicts(w, h, key='member'):
|
|
||||||
"""
|
|
||||||
Returns a list containing diff between
|
|
||||||
two list of dictionaries
|
|
||||||
"""
|
|
||||||
if not w:
|
|
||||||
w = []
|
|
||||||
if not h:
|
|
||||||
h = []
|
|
||||||
|
|
||||||
diff = []
|
|
||||||
for w_item in w:
|
|
||||||
h_item = search_obj_in_list(w_item[key], h, key=key) or {}
|
|
||||||
d = dict_diff(h_item, w_item)
|
|
||||||
if d:
|
|
||||||
if key not in d.keys():
|
|
||||||
d[key] = w_item[key]
|
|
||||||
diff.append(d)
|
|
||||||
|
|
||||||
return diff
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv4(value, module):
|
|
||||||
if value:
|
|
||||||
address = value.split('/')
|
|
||||||
if len(address) != 2:
|
|
||||||
module.fail_json(msg='address format is <ipv4 address>/<mask>, got invalid format {0}'.format(value))
|
|
||||||
|
|
||||||
if not is_masklen(address[1]):
|
|
||||||
module.fail_json(
|
|
||||||
msg='invalid value for mask: {0}, mask should be in range 0-32'
|
|
||||||
.format(address[1]))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv6(value, module):
|
|
||||||
if value:
|
|
||||||
address = value.split('/')
|
|
||||||
if len(address) != 2:
|
|
||||||
module.fail_json(msg='address format is <ipv6 address>/<mask>, got invalid format {0}'.format(value))
|
|
||||||
else:
|
|
||||||
if not 0 <= int(address[1]) <= 128:
|
|
||||||
module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-128'.format(address[1]))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_n_expand_ipv4(module, want):
|
|
||||||
# Check if input IPV4 is valid IP and expand IPV4 with its subnet mask
|
|
||||||
ip_addr_want = want.get('address')
|
|
||||||
if len(ip_addr_want.split(' ')) > 1:
|
|
||||||
return ip_addr_want
|
|
||||||
validate_ipv4(ip_addr_want, module)
|
|
||||||
ip = ip_addr_want.split('/')
|
|
||||||
if len(ip) == 2:
|
|
||||||
ip_addr_want = '{0} {1}'.format(ip[0], to_netmask(ip[1]))
|
|
||||||
|
|
||||||
return ip_addr_want
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_interface(name):
|
|
||||||
"""Return the normalized interface name
|
|
||||||
"""
|
|
||||||
if not name:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _get_number(name):
|
|
||||||
digits = ''
|
|
||||||
for char in name:
|
|
||||||
if char.isdigit() or char in '/.':
|
|
||||||
digits += char
|
|
||||||
return digits
|
|
||||||
|
|
||||||
if name.lower().startswith('gi'):
|
|
||||||
if_type = 'GigabitEthernet'
|
|
||||||
elif name.lower().startswith('fa'):
|
|
||||||
if_type = 'FastEthernet'
|
|
||||||
elif name.lower().startswith('fo'):
|
|
||||||
if_type = 'FortyGigE'
|
|
||||||
elif name.lower().startswith('te'):
|
|
||||||
if_type = 'TenGigE'
|
|
||||||
elif name.lower().startswith('twe'):
|
|
||||||
if_type = 'TwentyFiveGigE'
|
|
||||||
elif name.lower().startswith('hu'):
|
|
||||||
if_type = 'HundredGigE'
|
|
||||||
elif name.lower().startswith('vl'):
|
|
||||||
if_type = 'Vlan'
|
|
||||||
elif name.lower().startswith('lo'):
|
|
||||||
if_type = 'Loopback'
|
|
||||||
elif name.lower().startswith('be'):
|
|
||||||
if_type = 'Bundle-Ether'
|
|
||||||
elif name.lower().startswith('bp'):
|
|
||||||
if_type = 'Bundle-POS'
|
|
||||||
else:
|
|
||||||
if_type = None
|
|
||||||
|
|
||||||
number_list = name.split(' ')
|
|
||||||
if len(number_list) == 2:
|
|
||||||
number = number_list[-1].strip()
|
|
||||||
else:
|
|
||||||
number = _get_number(name)
|
|
||||||
|
|
||||||
if if_type:
|
|
||||||
proper_interface = if_type + number
|
|
||||||
else:
|
|
||||||
proper_interface = name
|
|
||||||
|
|
||||||
return proper_interface
|
|
||||||
|
|
||||||
|
|
||||||
def get_interface_type(interface):
|
|
||||||
"""Gets the type of interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
if interface.upper().startswith('GI'):
|
|
||||||
return 'GigabitEthernet'
|
|
||||||
elif interface.upper().startswith('FA'):
|
|
||||||
return 'FastEthernet'
|
|
||||||
elif interface.upper().startswith('FO'):
|
|
||||||
return 'FortyGigE'
|
|
||||||
elif interface.upper().startswith('ET'):
|
|
||||||
return 'Ethernet'
|
|
||||||
elif interface.upper().startswith('LO'):
|
|
||||||
return 'Loopback'
|
|
||||||
elif interface.upper().startswith('BE'):
|
|
||||||
return 'Bundle-Ether'
|
|
||||||
elif interface.upper().startswith('NV'):
|
|
||||||
return 'nve'
|
|
||||||
elif interface.upper().startswith('TWE'):
|
|
||||||
return 'TwentyFiveGigE'
|
|
||||||
elif interface.upper().startswith('HU'):
|
|
||||||
return 'HundredGigE'
|
|
||||||
elif interface.upper().startswith('PRE'):
|
|
||||||
return 'preconfigure'
|
|
||||||
else:
|
|
||||||
return 'unknown'
|
|
||||||
|
|
||||||
|
|
||||||
def isipaddress(data):
|
|
||||||
"""
|
|
||||||
Checks if the passed string is
|
|
||||||
a valid IPv4 or IPv6 address
|
|
||||||
"""
|
|
||||||
isipaddress = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
ipaddress.ip_address(data)
|
|
||||||
except ValueError:
|
|
||||||
isipaddress = False
|
|
||||||
|
|
||||||
return isipaddress
|
|
||||||
|
|
||||||
|
|
||||||
def is_ipv4_address(data):
|
|
||||||
"""
|
|
||||||
Checks if the passed string is
|
|
||||||
a valid IPv4 address
|
|
||||||
"""
|
|
||||||
if '/' in data:
|
|
||||||
data = data.split('/')[0]
|
|
||||||
|
|
||||||
if not isipaddress(to_text(data)):
|
|
||||||
raise ValueError('{0} is not a valid IP address'.format(data))
|
|
||||||
|
|
||||||
return (ipaddress.ip_address(to_text(data)).version == 4)
|
|
||||||
|
|
||||||
|
|
||||||
def prefix_to_address_wildcard(prefix):
|
|
||||||
""" Converts a IPv4 prefix into address and
|
|
||||||
wildcard mask
|
|
||||||
|
|
||||||
:returns: IPv4 address and wildcard mask
|
|
||||||
"""
|
|
||||||
wildcard = []
|
|
||||||
|
|
||||||
subnet = to_text(ipaddress.IPv4Network(to_text(prefix)).netmask)
|
|
||||||
|
|
||||||
for x in subnet.split('.'):
|
|
||||||
component = 255 - int(x)
|
|
||||||
wildcard.append(str(component))
|
|
||||||
|
|
||||||
wildcard = '.'.join(wildcard)
|
|
||||||
|
|
||||||
return prefix.split('/')[0], wildcard
|
|
@ -1,664 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['deprecated'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_interface
|
|
||||||
version_added: "2.4"
|
|
||||||
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.
|
|
||||||
deprecated:
|
|
||||||
removed_in: '2.13'
|
|
||||||
alternative: iosxr_interfaces
|
|
||||||
why: Newer and updated modules released with more functionality in Ansible 2.9
|
|
||||||
requirements:
|
|
||||||
- ncclient >= 0.5.3 when using netconf
|
|
||||||
- lxml >= 4.1.1 when using netconf
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
notes:
|
|
||||||
- This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- Tested against IOS XRv 6.1.3.
|
|
||||||
- Preconfiguration of physical interfaces is not supported with C(netconf) transport.
|
|
||||||
options:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- 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 being configured.
|
|
||||||
enabled:
|
|
||||||
description:
|
|
||||||
- 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:
|
|
||||||
- Configure the speed for an interface. Default is auto-negotiation when not configured.
|
|
||||||
choices: ['10', '100', '1000']
|
|
||||||
mtu:
|
|
||||||
description:
|
|
||||||
- Sets the MTU value for the interface. Range is between 64 and 65535'
|
|
||||||
duplex:
|
|
||||||
description:
|
|
||||||
- Configures the interface duplex mode. Default is auto-negotiation when not configured.
|
|
||||||
choices: ['full', 'half']
|
|
||||||
tx_rate:
|
|
||||||
description:
|
|
||||||
- Transmit rate in bits per second (bps).
|
|
||||||
- This is state check parameter only.
|
|
||||||
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
|
|
||||||
rx_rate:
|
|
||||||
description:
|
|
||||||
- Receiver rate in bits per second (bps).
|
|
||||||
- This is state check parameter only.
|
|
||||||
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
|
|
||||||
aggregate:
|
|
||||||
description:
|
|
||||||
- List of Interface definitions. Include multiple interface configurations together,
|
|
||||||
one each on a separate line
|
|
||||||
delay:
|
|
||||||
description:
|
|
||||||
- Time in seconds to wait before checking for the operational state on remote
|
|
||||||
device. This wait is applicable for operational state argument which are
|
|
||||||
I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate).
|
|
||||||
default: 10
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- State of the Interface configuration, C(up) means present and
|
|
||||||
operationally up and C(down) means present and operationally C(down)
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent', 'up', 'down']
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: configure interface
|
|
||||||
iosxr_interface:
|
|
||||||
name: GigabitEthernet0/0/0/2
|
|
||||||
description: test-interface
|
|
||||||
speed: 100
|
|
||||||
duplex: half
|
|
||||||
mtu: 512
|
|
||||||
|
|
||||||
- name: remove interface
|
|
||||||
iosxr_interface:
|
|
||||||
name: GigabitEthernet0/0/0/2
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: make interface up
|
|
||||||
iosxr_interface:
|
|
||||||
name: GigabitEthernet0/0/0/2
|
|
||||||
enabled: True
|
|
||||||
|
|
||||||
- name: make interface down
|
|
||||||
iosxr_interface:
|
|
||||||
name: GigabitEthernet0/0/0/2
|
|
||||||
enabled: False
|
|
||||||
|
|
||||||
- name: Create interface using aggregate
|
|
||||||
iosxr_interface:
|
|
||||||
aggregate:
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
speed: 100
|
|
||||||
duplex: full
|
|
||||||
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:
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Check intent arguments
|
|
||||||
iosxr_interface:
|
|
||||||
name: GigabitEthernet0/0/0/5
|
|
||||||
state: up
|
|
||||||
delay: 20
|
|
||||||
|
|
||||||
- name: Config + intent
|
|
||||||
iosxr_interface:
|
|
||||||
name: GigabitEthernet0/0/0/5
|
|
||||||
enabled: False
|
|
||||||
state: down
|
|
||||||
delay: 20
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
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.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, build_xml
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import run_commands, 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):
|
|
||||||
if value and not 64 <= int(value) <= 65535:
|
|
||||||
return False, 'mtu must be between 64 and 65535'
|
|
||||||
return True, None
|
|
||||||
|
|
||||||
|
|
||||||
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 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()
|
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
self._want.append(d)
|
|
||||||
|
|
||||||
else:
|
|
||||||
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'],
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class CliConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(CliConfiguration, self).__init__(module)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def search_obj_in_list(self, name):
|
|
||||||
for obj in self._have:
|
|
||||||
if obj['name'] == name:
|
|
||||||
return obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
def map_config_to_obj(self):
|
|
||||||
data = get_config(self._module, config_filter='interface')
|
|
||||||
data_lines = data.splitlines()
|
|
||||||
start_indexes = [i for i, e in enumerate(data_lines) if e.startswith('interface')]
|
|
||||||
end_indexes = [i for i, e in enumerate(data_lines) if e == '!']
|
|
||||||
|
|
||||||
intf_configs = list()
|
|
||||||
for start_index, end_index in zip(start_indexes, end_indexes):
|
|
||||||
intf_configs.append([i.strip() for i in data_lines[start_index:end_index]])
|
|
||||||
|
|
||||||
if not intf_configs:
|
|
||||||
return list()
|
|
||||||
|
|
||||||
for intf_config in intf_configs:
|
|
||||||
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': not bool(self.parse_shutdown(intf_config)),
|
|
||||||
'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 {0!s}'.format(want_item['name'])
|
|
||||||
out = run_commands(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({0!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
|
|
||||||
|
|
||||||
|
|
||||||
class NCConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(NCConfiguration, self).__init__(module)
|
|
||||||
|
|
||||||
self._intf_meta = collections.OrderedDict()
|
|
||||||
self._shut_meta = collections.OrderedDict()
|
|
||||||
self._data_rate_meta = collections.OrderedDict()
|
|
||||||
self._line_state_meta = collections.OrderedDict()
|
|
||||||
|
|
||||||
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'}),
|
|
||||||
])
|
|
||||||
|
|
||||||
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'):
|
|
||||||
intf_params = self._want
|
|
||||||
opcode = 'merge'
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
if self._module._diff:
|
|
||||||
self._result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
self._result['xml'] = _edit_filter_list
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def check_declarative_intent_params(self):
|
|
||||||
failed_conditions = []
|
|
||||||
|
|
||||||
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}),
|
|
||||||
])
|
|
||||||
|
|
||||||
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}),
|
|
||||||
])
|
|
||||||
|
|
||||||
_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})
|
|
||||||
|
|
||||||
_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})
|
|
||||||
|
|
||||||
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'])
|
|
||||||
|
|
||||||
if want_state in ('up', 'down'):
|
|
||||||
if want_state not in line_state_map[want_item['name']]:
|
|
||||||
failed_conditions.append('state ' + 'eq({0!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(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'),
|
|
||||||
state=dict(default='present',
|
|
||||||
choices=['present', 'absent', 'up', 'down'])
|
|
||||||
)
|
|
||||||
|
|
||||||
aggregate_spec = deepcopy(element_spec)
|
|
||||||
aggregate_spec['name'] = dict(required=True)
|
|
||||||
|
|
||||||
# remove default in aggregate spec, to handle common arguments
|
|
||||||
remove_default_spec(aggregate_spec)
|
|
||||||
|
|
||||||
argument_spec = dict(
|
|
||||||
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(element_spec)
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
required_one_of = [['name', 'aggregate']]
|
|
||||||
mutually_exclusive = [['name', 'aggregate']]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
required_one_of=required_one_of,
|
|
||||||
mutually_exclusive=mutually_exclusive,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
config_object = None
|
|
||||||
if is_cliconf(module):
|
|
||||||
# Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported
|
|
||||||
# module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
|
|
||||||
# version='2.9')
|
|
||||||
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()
|
|
@ -1,743 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_acl_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_acl_interfaces
|
|
||||||
version_added: "2.10"
|
|
||||||
short_description: Manage Access Control Lists (ACLs) configuration for interfaces in IOS-XR.
|
|
||||||
description:
|
|
||||||
- This module manages adding and removing Access Control Lists (ACLs) from interfaces on devices running IOS-XR software.
|
|
||||||
author: Nilashish Chakraborty (@NilashishC)
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A dictionary of ACL options for interfaces.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name/Identifier for the interface
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
access_groups:
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
description:
|
|
||||||
- Specifies ACLs attached to the interfaces.
|
|
||||||
suboptions:
|
|
||||||
afi:
|
|
||||||
description:
|
|
||||||
- Specifies the AFI for the ACL(s) to be configured on this interface.
|
|
||||||
type: str
|
|
||||||
choices: ['ipv4', 'ipv6']
|
|
||||||
required: True
|
|
||||||
acls:
|
|
||||||
type: list
|
|
||||||
description:
|
|
||||||
- Specifies the ACLs for the provided AFI.
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Specifies the name of the IPv4/IPv6 ACL for the interface.
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
direction:
|
|
||||||
description:
|
|
||||||
- Specifies the direction of packets that the ACL will be applied on.
|
|
||||||
type: str
|
|
||||||
choices: ['in', 'out']
|
|
||||||
required: True
|
|
||||||
running_config:
|
|
||||||
description:
|
|
||||||
- The module, by default, will connect to the remote device and
|
|
||||||
retrieve the current running-config to use as a base for comparing
|
|
||||||
against the contents of source. There are times when it is not
|
|
||||||
desirable to have the task get the current running-config for
|
|
||||||
every task in a playbook. The I(running_config) argument allows the
|
|
||||||
implementer to pass in the configuration to use as the base
|
|
||||||
config for comparison. This value of this option should be the
|
|
||||||
output received from device by executing command
|
|
||||||
B(show running-config interface).
|
|
||||||
type: str
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state the configuration should be left in.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
- gathered
|
|
||||||
- parsed
|
|
||||||
- rendered
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
# Using merged
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:22:32.911 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Merge the provided configuration with the existing running configuration
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:27:49.378 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using merged to update interface ACL configuration
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:27:49.378 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Update acl_interfaces configuration using merged
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:27:49.378 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Replace device configurations of listed interface with provided configurations
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: in
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv6 access-group acl6_3 ingress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Overridde all interface ACL configuration with provided configuration
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: in
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: out
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_2 ingress
|
|
||||||
# ipv6 access-group acl6_3 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using 'deleted' to delete all ACL attributes of a single interface
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete all ACL attributes of GigabitEthernet0/0/0/1
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using 'deleted' to delete a single attached ACL from an interface
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete a single ACL attached to GigabitEthernet0/0/0/0
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using 'deleted' to delete all ACLs of a particular AFI from an interface
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using 'deleted' to remove all ACLs attached to all the interfaces in the device
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete all ACL interfaces configuration from the device
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config interface
|
|
||||||
# Wed Jan 15 12:34:56.689 UTC
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using parsed
|
|
||||||
|
|
||||||
# parsed.cfg
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# interface MgmtEth0/RP0/CPU0/0
|
|
||||||
# ipv4 address dhcp
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 ingress
|
|
||||||
# ipv4 access-group acl_2 egress
|
|
||||||
# ipv6 access-group acl6_1 ingress
|
|
||||||
# ipv6 access-group acl6_2 egress
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# ipv4 access-group acl_1 egress
|
|
||||||
# !
|
|
||||||
|
|
||||||
# - name: Convert ACL interfaces config to argspec without connecting to the appliance
|
|
||||||
# iosxr_acl_interfaces:
|
|
||||||
# running_config: "{{ lookup('file', './parsed.cfg') }}"
|
|
||||||
# state: parsed
|
|
||||||
|
|
||||||
|
|
||||||
# Task Output (redacted)
|
|
||||||
# -----------------------
|
|
||||||
|
|
||||||
# "parsed": [
|
|
||||||
# {
|
|
||||||
# "name": "MgmtEth0/RP0/CPU0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "access_groups": [
|
|
||||||
# {
|
|
||||||
# "acls": [
|
|
||||||
# {
|
|
||||||
# "direction": "in",
|
|
||||||
# "name": "acl_1"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "direction": "out",
|
|
||||||
# "name": "acl_2"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "afi": "ipv4"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "acls": [
|
|
||||||
# {
|
|
||||||
# "direction": "in",
|
|
||||||
# "name": "acl6_1"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "direction": "out",
|
|
||||||
# "name": "acl6_2"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "afi": "ipv6"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "name": "GigabitEthernet0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "access_groups": [
|
|
||||||
# {
|
|
||||||
# "acls": [
|
|
||||||
# {
|
|
||||||
# "direction": "out",
|
|
||||||
# "name": "acl_1"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "afi": "ipv4"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "name": "GigabitEthernet0/0/0/1"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
|
|
||||||
|
|
||||||
# Using gathered
|
|
||||||
|
|
||||||
- name: Gather ACL interfaces facts using gathered state
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
state: gathered
|
|
||||||
|
|
||||||
|
|
||||||
# Task Output (redacted)
|
|
||||||
# -----------------------
|
|
||||||
#
|
|
||||||
# "gathered": [
|
|
||||||
# {
|
|
||||||
# "name": "MgmtEth0/RP0/CPU0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "access_groups": [
|
|
||||||
# {
|
|
||||||
# "acls": [
|
|
||||||
# {
|
|
||||||
# "direction": "in",
|
|
||||||
# "name": "acl_1"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "direction": "out",
|
|
||||||
# "name": "acl_2"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "afi": "ipv4"
|
|
||||||
# }
|
|
||||||
# "name": "GigabitEthernet0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "access_groups": [
|
|
||||||
# {
|
|
||||||
# "acls": [
|
|
||||||
# {
|
|
||||||
# "direction": "in",
|
|
||||||
# "name": "acl6_1"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "afi": "ipv6"
|
|
||||||
# }
|
|
||||||
# "name": "GigabitEthernet0/0/0/1"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
|
|
||||||
|
|
||||||
# Using rendered
|
|
||||||
|
|
||||||
- name: Render platform specific commands from task input using rendered state
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
state: rendered
|
|
||||||
|
|
||||||
# Task Output (redacted)
|
|
||||||
# -----------------------
|
|
||||||
|
|
||||||
# "rendered": [
|
|
||||||
# "interface GigabitEthernet0/0/0/0",
|
|
||||||
# "ipv4 access-group acl_1 ingress",
|
|
||||||
# "ipv4 access-group acl_2 egress"
|
|
||||||
# ]
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration prior to the model invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The resulting configuration model invocation.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
- "interface GigabitEthernet0/0/0/1"
|
|
||||||
- "ipv4 access-group acl_1 ingress"
|
|
||||||
- "ipv4 access-group acl_2 egress"
|
|
||||||
- "ipv6 access-group acl6_1 ingress"
|
|
||||||
- "interface GigabitEthernet0/0/0/2"
|
|
||||||
- "no ipv4 access-group acl_3 ingress"
|
|
||||||
- "ipv4 access-group acl_4 egress"
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.acl_interfaces.acl_interfaces import Acl_interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',)),
|
|
||||||
('state', 'rendered', ('config',)),
|
|
||||||
('state', 'parsed', ('running_config',))]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=Acl_interfacesArgs.argument_spec, required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Acl_interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
File diff suppressed because it is too large
Load Diff
@ -1,255 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_banner
|
|
||||||
version_added: "2.4"
|
|
||||||
author:
|
|
||||||
- Trishna Guha (@trishnaguha)
|
|
||||||
- Kedar Kekan (@kedarX)
|
|
||||||
short_description: Manage multiline banners on Cisco IOS XR devices
|
|
||||||
description:
|
|
||||||
- This module will configure both exec and motd banners on remote device
|
|
||||||
running Cisco IOS XR. It allows playbooks to add or remove
|
|
||||||
banner text from the running configuration.
|
|
||||||
requirements:
|
|
||||||
- ncclient >= 0.5.3 when using netconf
|
|
||||||
- lxml >= 4.1.1 when using netconf
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
notes:
|
|
||||||
- Tested against IOS XRv 6.1.3.
|
|
||||||
- This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
options:
|
|
||||||
banner:
|
|
||||||
description:
|
|
||||||
- Specifies the type of banner to configure on remote device.
|
|
||||||
required: true
|
|
||||||
choices: ['login', 'motd']
|
|
||||||
text:
|
|
||||||
description:
|
|
||||||
- Banner text to be configured. Accepts multiline string,
|
|
||||||
without empty lines. Requires I(state=present).
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Existential state of the configuration on the device.
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent']
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: configure the login banner
|
|
||||||
iosxr_banner:
|
|
||||||
banner: login
|
|
||||||
text: |
|
|
||||||
this is my login banner
|
|
||||||
that contains a multiline
|
|
||||||
string
|
|
||||||
state: present
|
|
||||||
- name: remove the motd banner
|
|
||||||
iosxr_banner:
|
|
||||||
banner: motd
|
|
||||||
state: absent
|
|
||||||
- name: Configure banner from file
|
|
||||||
iosxr_banner:
|
|
||||||
banner: motd
|
|
||||||
text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}"
|
|
||||||
state: present
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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):
|
|
||||||
def __init__(self, module):
|
|
||||||
self._module = module
|
|
||||||
self._result = {'changed': False, 'warnings': []}
|
|
||||||
self._want = {}
|
|
||||||
self._have = {}
|
|
||||||
|
|
||||||
def map_params_to_obj(self):
|
|
||||||
text = self._module.params['text']
|
|
||||||
if text:
|
|
||||||
text = "{0!r}".format(str(text).strip())
|
|
||||||
self._want.update({
|
|
||||||
'banner': self._module.params['banner'],
|
|
||||||
'text': text,
|
|
||||||
'state': self._module.params['state']
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class CliConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(CliConfiguration, self).__init__(module)
|
|
||||||
|
|
||||||
def map_obj_to_commands(self):
|
|
||||||
commands = list()
|
|
||||||
state = self._module.params['state']
|
|
||||||
if state == 'absent':
|
|
||||||
if self._have.get('state') != 'absent' and ('text' in self._have.keys() and self._have['text']):
|
|
||||||
commands.append('no banner {0!s}'.format(self._module.params['banner']))
|
|
||||||
elif state == 'present':
|
|
||||||
if (self._want['text'] and
|
|
||||||
self._want['text'].encode().decode('unicode_escape').strip("'") != self._have.get('text')):
|
|
||||||
banner_cmd = 'banner {0!s} '.format(self._module.params['banner'])
|
|
||||||
banner_cmd += self._want['text'].strip()
|
|
||||||
commands.append(banner_cmd)
|
|
||||||
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 map_config_to_obj(self):
|
|
||||||
cli_filter = 'banner {0!s}'.format(self._module.params['banner'])
|
|
||||||
output = get_config(self._module, config_filter=cli_filter)
|
|
||||||
match = re.search(r'banner (\S+) (.*)', output, re.DOTALL)
|
|
||||||
if match:
|
|
||||||
text = match.group(2).strip("'")
|
|
||||||
else:
|
|
||||||
text = None
|
|
||||||
obj = {'banner': self._module.params['banner'], 'state': 'absent'}
|
|
||||||
if output:
|
|
||||||
obj['text'] = text
|
|
||||||
obj['state'] = 'present'
|
|
||||||
self._have.update(obj)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_config_to_obj()
|
|
||||||
self.map_obj_to_commands()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
class NCConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(NCConfiguration, self).__init__(module)
|
|
||||||
self._banners_meta = collections.OrderedDict()
|
|
||||||
self._banners_meta.update([
|
|
||||||
('banner', {'xpath': 'banners/banner', 'tag': True, 'attrib': "operation"}),
|
|
||||||
('a:banner', {'xpath': 'banner/banner-name'}),
|
|
||||||
('a:text', {'xpath': 'banner/banner-text', 'operation': 'edit'})
|
|
||||||
])
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
running = get_config(self._module, source='running', config_filter=_get_filter)
|
|
||||||
|
|
||||||
banner_name = None
|
|
||||||
banner_text = None
|
|
||||||
if etree_find(running, 'banner-text') is not None:
|
|
||||||
banner_name = etree_find(running, 'banner-name').text
|
|
||||||
banner_text = etree_find(running, 'banner-text').text
|
|
||||||
|
|
||||||
opcode = None
|
|
||||||
if state == 'absent' and banner_name == self._module.params['banner'] and len(banner_text):
|
|
||||||
opcode = "delete"
|
|
||||||
elif state == 'present':
|
|
||||||
opcode = 'merge'
|
|
||||||
|
|
||||||
self._result['xml'] = []
|
|
||||||
if opcode:
|
|
||||||
_edit_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode=opcode)
|
|
||||||
|
|
||||||
if _edit_filter is not None:
|
|
||||||
commit = not self._module.check_mode
|
|
||||||
diff = load_config(self._module, _edit_filter, commit=commit, running=running, nc_get_filter=_get_filter)
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
self._result['xml'] = _edit_filter
|
|
||||||
if self._module._diff:
|
|
||||||
self._result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_obj_to_xml_rpc()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
""" main entry point for module execution
|
|
||||||
"""
|
|
||||||
argument_spec = dict(
|
|
||||||
banner=dict(required=True, choices=['login', 'motd']),
|
|
||||||
text=dict(),
|
|
||||||
state=dict(default='present', choices=['present', 'absent'])
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
required_if = [('state', 'present', ('text',))]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
config_object = None
|
|
||||||
if is_cliconf(module):
|
|
||||||
# Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported
|
|
||||||
# module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
|
|
||||||
# version='2.9')
|
|
||||||
config_object = CliConfiguration(module)
|
|
||||||
elif is_netconf(module):
|
|
||||||
config_object = NCConfiguration(module)
|
|
||||||
|
|
||||||
result = None
|
|
||||||
if config_object is not None:
|
|
||||||
result = config_object.run()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,296 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (c) 2019, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_bgp
|
|
||||||
version_added: "2.8"
|
|
||||||
author: "Nilashish Chakraborty (@NilashishC)"
|
|
||||||
short_description: Configure global BGP protocol settings on Cisco IOS-XR
|
|
||||||
description:
|
|
||||||
- This module provides configuration management of global BGP parameters
|
|
||||||
on devices running Cisco IOS-XR
|
|
||||||
notes:
|
|
||||||
- Tested against Cisco IOS XR Software Version 6.1.3
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description:
|
|
||||||
- Specifies the BGP related configuration.
|
|
||||||
suboptions:
|
|
||||||
bgp_as:
|
|
||||||
description:
|
|
||||||
- Specifies the BGP Autonomous System (AS) number to configure on the device.
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
router_id:
|
|
||||||
description:
|
|
||||||
- Configures the BGP routing process router-id value.
|
|
||||||
default: null
|
|
||||||
log_neighbor_changes:
|
|
||||||
description:
|
|
||||||
- Enable/disable logging neighbor up/down and reset reason.
|
|
||||||
type: bool
|
|
||||||
neighbors:
|
|
||||||
description:
|
|
||||||
- Specifies BGP neighbor related configurations.
|
|
||||||
suboptions:
|
|
||||||
neighbor:
|
|
||||||
description:
|
|
||||||
- Neighbor router address.
|
|
||||||
required: True
|
|
||||||
remote_as:
|
|
||||||
description:
|
|
||||||
- Remote AS of the BGP neighbor to configure.
|
|
||||||
type: int
|
|
||||||
required: True
|
|
||||||
update_source:
|
|
||||||
description:
|
|
||||||
- Source of the routing updates.
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- Password to authenticate the BGP peer connection.
|
|
||||||
enabled:
|
|
||||||
description:
|
|
||||||
- Administratively shutdown or enable a neighbor.
|
|
||||||
type: bool
|
|
||||||
description:
|
|
||||||
description:
|
|
||||||
- Neighbor specific description.
|
|
||||||
advertisement_interval:
|
|
||||||
description:
|
|
||||||
- Specifies the minimum interval (in seconds) between sending BGP routing updates.
|
|
||||||
- The range is from 0 to 600.
|
|
||||||
type: int
|
|
||||||
tcp_mss:
|
|
||||||
description:
|
|
||||||
- Specifies the TCP initial maximum segment size to use.
|
|
||||||
- The range is from 68 to 10000.
|
|
||||||
type: int
|
|
||||||
ebgp_multihop:
|
|
||||||
description:
|
|
||||||
- Specifies the maximum hop count for EBGP neighbors not on directly connected networks.
|
|
||||||
- The range is from 0 to 255.
|
|
||||||
type: int
|
|
||||||
timers:
|
|
||||||
description:
|
|
||||||
- Specifies BGP neighbor timer related configurations.
|
|
||||||
suboptions:
|
|
||||||
keepalive:
|
|
||||||
description:
|
|
||||||
- Frequency with which the Cisco IOS-XR software sends keepalive messages to its peer.
|
|
||||||
- The range is from 0 to 65535.
|
|
||||||
type: int
|
|
||||||
required: True
|
|
||||||
holdtime:
|
|
||||||
description:
|
|
||||||
- Interval after not receiving a keepalive message that the software declares a peer dead.
|
|
||||||
- The range is from 3 to 65535.
|
|
||||||
type: int
|
|
||||||
required: True
|
|
||||||
min_neighbor_holdtime:
|
|
||||||
description:
|
|
||||||
- Interval specifying the minimum acceptable hold-time from a BGP neighbor.
|
|
||||||
- The minimum acceptable hold-time must be less than, or equal to, the interval specified in the holdtime argument.
|
|
||||||
- The range is from 3 to 65535.
|
|
||||||
type: int
|
|
||||||
address_family:
|
|
||||||
description:
|
|
||||||
- Specifies BGP address family related configurations.
|
|
||||||
suboptions:
|
|
||||||
afi:
|
|
||||||
description:
|
|
||||||
- Type of address family to configure.
|
|
||||||
choices:
|
|
||||||
- ipv4
|
|
||||||
- ipv6
|
|
||||||
required: True
|
|
||||||
safi:
|
|
||||||
description:
|
|
||||||
- Specifies the type of cast for the address family.
|
|
||||||
choices:
|
|
||||||
- flowspec
|
|
||||||
- unicast
|
|
||||||
- multicast
|
|
||||||
- labeled-unicast
|
|
||||||
default: unicast
|
|
||||||
redistribute:
|
|
||||||
description:
|
|
||||||
- Specifies the redistribute information from another routing protocol.
|
|
||||||
suboptions:
|
|
||||||
protocol:
|
|
||||||
description:
|
|
||||||
- Specifies the protocol for configuring redistribute information.
|
|
||||||
choices: ['ospf', 'ospfv3', 'eigrp', 'isis', 'static', 'connected', 'lisp', 'mobile', 'rip', 'subscriber']
|
|
||||||
required: True
|
|
||||||
id:
|
|
||||||
description:
|
|
||||||
- Identifier for the routing protocol for configuring redistribute information.
|
|
||||||
- Valid for protocols 'ospf', 'eigrp', 'isis' and 'ospfv3'.
|
|
||||||
metric:
|
|
||||||
description:
|
|
||||||
- Specifies the metric for redistributed routes.
|
|
||||||
route_map:
|
|
||||||
description:
|
|
||||||
- Specifies the route map reference.
|
|
||||||
networks:
|
|
||||||
description:
|
|
||||||
- Specify networks to announce via BGP.
|
|
||||||
- For operation replace, this option is mutually exclusive with root level networks option.
|
|
||||||
suboptions:
|
|
||||||
network:
|
|
||||||
description:
|
|
||||||
- Network ID to announce via BGP.
|
|
||||||
required: True
|
|
||||||
masklen:
|
|
||||||
description:
|
|
||||||
- Subnet mask length for the network to announce(e.g, 8, 16, 24, etc.).
|
|
||||||
route_map:
|
|
||||||
description:
|
|
||||||
- Route map to modify the attributes.
|
|
||||||
operation:
|
|
||||||
description:
|
|
||||||
- Specifies the operation to be performed on the BGP process configured on the device.
|
|
||||||
- In case of merge, the input configuration will be merged with the existing BGP configuration on the device.
|
|
||||||
- In case of replace, if there is a diff between the existing configuration and the input configuration, the
|
|
||||||
existing configuration will be replaced by the input configuration for every option that has the diff.
|
|
||||||
- In case of override, all the existing BGP configuration will be removed from the device and replaced with
|
|
||||||
the input configuration.
|
|
||||||
- In case of delete the existing BGP configuration will be removed from the device.
|
|
||||||
default: merge
|
|
||||||
choices: ['merge', 'replace', 'override', 'delete']
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: configure global bgp as 65000
|
|
||||||
iosxr_bgp:
|
|
||||||
bgp_as: 65000
|
|
||||||
router_id: 1.1.1.1
|
|
||||||
neighbors:
|
|
||||||
- neighbor: 182.168.10.1
|
|
||||||
remote_as: 500
|
|
||||||
description: PEER_1
|
|
||||||
- neighbor: 192.168.20.1
|
|
||||||
remote_as: 500
|
|
||||||
update_source: GigabitEthernet 0/0/0/0
|
|
||||||
address_family:
|
|
||||||
- name: ipv4
|
|
||||||
cast: unicast
|
|
||||||
networks:
|
|
||||||
- network: 192.168.2.0/23
|
|
||||||
- network: 10.0.0.0/8
|
|
||||||
redistribute:
|
|
||||||
- protocol: ospf
|
|
||||||
id: 400
|
|
||||||
metric: 110
|
|
||||||
|
|
||||||
- name: remove bgp as 65000 from config
|
|
||||||
ios_bgp:
|
|
||||||
bgp_as: 65000
|
|
||||||
state: absent
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
description: The list of configuration mode commands to send to the device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
- router bgp 65000
|
|
||||||
- bgp router-id 1.1.1.1
|
|
||||||
- neighbor 182.168.10.1 remote-as 500
|
|
||||||
- neighbor 182.168.10.1 description PEER_1
|
|
||||||
- neighbor 192.168.20.1 remote-as 500
|
|
||||||
- neighbor 192.168.20.1 update-source GigabitEthernet0/0/0/0
|
|
||||||
- address-family ipv4 unicast
|
|
||||||
- redistribute ospf 400 metric 110
|
|
||||||
- network 192.168.2.0/23
|
|
||||||
- network 10.0.0.0/8
|
|
||||||
- exit
|
|
||||||
"""
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
from ansible.module_utils.network.iosxr.providers.module import NetworkModule
|
|
||||||
from ansible.module_utils.network.iosxr.providers.cli.config.bgp.process import REDISTRIBUTE_PROTOCOLS
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
""" main entry point for module execution
|
|
||||||
"""
|
|
||||||
network_spec = {
|
|
||||||
'prefix': dict(required=True),
|
|
||||||
'masklen': dict(type='int', required=True),
|
|
||||||
'route_map': dict(),
|
|
||||||
}
|
|
||||||
|
|
||||||
redistribute_spec = {
|
|
||||||
'protocol': dict(choices=REDISTRIBUTE_PROTOCOLS, required=True),
|
|
||||||
'id': dict(),
|
|
||||||
'metric': dict(type='int'),
|
|
||||||
'route_map': dict(),
|
|
||||||
}
|
|
||||||
|
|
||||||
timer_spec = {
|
|
||||||
'keepalive': dict(type='int'),
|
|
||||||
'holdtime': dict(type='int'),
|
|
||||||
'min_neighbor_holdtime': dict(type='int'),
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbor_spec = {
|
|
||||||
'neighbor': dict(required=True),
|
|
||||||
'remote_as': dict(type='int', required=True),
|
|
||||||
'update_source': dict(),
|
|
||||||
'password': dict(no_log=True),
|
|
||||||
'enabled': dict(type='bool'),
|
|
||||||
'description': dict(),
|
|
||||||
'advertisement_interval': dict(type='int'),
|
|
||||||
'ebgp_multihop': dict(type='int'),
|
|
||||||
'tcp_mss': dict(type='int'),
|
|
||||||
'timers': dict(type='dict', options=timer_spec),
|
|
||||||
}
|
|
||||||
|
|
||||||
address_family_spec = {
|
|
||||||
'afi': dict(choices=['ipv4', 'ipv6'], required=True),
|
|
||||||
'safi': dict(choices=['flowspec', 'labeled-unicast', 'multicast', 'unicast'], default='unicast'),
|
|
||||||
'networks': dict(type='list', elements='dict', options=network_spec),
|
|
||||||
'redistribute': dict(type='list', elements='dict', options=redistribute_spec),
|
|
||||||
}
|
|
||||||
|
|
||||||
config_spec = {
|
|
||||||
'bgp_as': dict(type='int', required=True),
|
|
||||||
'router_id': dict(),
|
|
||||||
'log_neighbor_changes': dict(type='bool'),
|
|
||||||
'neighbors': dict(type='list', elements='dict', options=neighbor_spec),
|
|
||||||
'address_family': dict(type='list', elements='dict', options=address_family_spec),
|
|
||||||
}
|
|
||||||
|
|
||||||
argument_spec = {
|
|
||||||
'config': dict(type='dict', options=config_spec),
|
|
||||||
'operation': dict(default='merge', choices=['merge', 'replace', 'override', 'delete'])
|
|
||||||
}
|
|
||||||
|
|
||||||
module = NetworkModule(argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = module.edit_config(config_filter='router bgp')
|
|
||||||
except Exception as exc:
|
|
||||||
module.fail_json(msg=to_text(exc))
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,212 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Copyright: Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_command
|
|
||||||
version_added: "2.1"
|
|
||||||
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
|
|
||||||
short_description: Run commands on remote devices running Cisco IOS XR
|
|
||||||
description:
|
|
||||||
- Sends arbitrary commands to an IOS XR node and returns the results
|
|
||||||
read from the device. This module includes an
|
|
||||||
argument that will cause the module to wait for a specific condition
|
|
||||||
before returning or timing out if the condition is not met.
|
|
||||||
- This module does not support running commands in configuration mode.
|
|
||||||
Please use M(iosxr_config) to configure iosxr devices.
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
notes:
|
|
||||||
- Make sure the user has been authorized to execute commands terminal length 0, terminal width 512 and terminal exec prompt no-timestamp.
|
|
||||||
- This module works with C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- This module does not support C(netconf) connection.
|
|
||||||
- Tested against IOS XR 6.1.3
|
|
||||||
options:
|
|
||||||
commands:
|
|
||||||
description:
|
|
||||||
- List of commands to send to the remote iosxr device over the
|
|
||||||
configured provider. The resulting output from the command
|
|
||||||
is returned. If the I(wait_for) argument is provided, the
|
|
||||||
module is not returned until the condition is satisfied or
|
|
||||||
the number of retries has expired.
|
|
||||||
required: true
|
|
||||||
wait_for:
|
|
||||||
description:
|
|
||||||
- List of conditions to evaluate against the output of the
|
|
||||||
command. The task will wait for each condition to be true
|
|
||||||
before moving forward. If the conditional is not true
|
|
||||||
within the configured number of retries, the task fails.
|
|
||||||
See examples.
|
|
||||||
aliases: ['waitfor']
|
|
||||||
version_added: "2.2"
|
|
||||||
match:
|
|
||||||
description:
|
|
||||||
- The I(match) argument is used in conjunction with the
|
|
||||||
I(wait_for) argument to specify the match policy. Valid
|
|
||||||
values are C(all) or C(any). If the value is set to C(all)
|
|
||||||
then all conditionals in the wait_for must be satisfied. If
|
|
||||||
the value is set to C(any) then only one of the values must be
|
|
||||||
satisfied.
|
|
||||||
default: all
|
|
||||||
choices: ['any', 'all']
|
|
||||||
version_added: "2.2"
|
|
||||||
retries:
|
|
||||||
description:
|
|
||||||
- Specifies the number of retries a command should by tried
|
|
||||||
before it is considered failed. The command is run on the
|
|
||||||
target device every retry and evaluated against the
|
|
||||||
I(wait_for) conditions.
|
|
||||||
default: 10
|
|
||||||
interval:
|
|
||||||
description:
|
|
||||||
- Configures the interval in seconds to wait between retries
|
|
||||||
of the command. If the command does not pass the specified
|
|
||||||
conditions, the interval indicates how long to wait before
|
|
||||||
trying the command again.
|
|
||||||
default: 1
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
tasks:
|
|
||||||
- name: run show version on remote devices
|
|
||||||
iosxr_command:
|
|
||||||
commands: show version
|
|
||||||
|
|
||||||
- name: run show version and check to see if output contains iosxr
|
|
||||||
iosxr_command:
|
|
||||||
commands: show version
|
|
||||||
wait_for: result[0] contains IOS-XR
|
|
||||||
|
|
||||||
- name: run multiple commands on remote nodes
|
|
||||||
iosxr_command:
|
|
||||||
commands:
|
|
||||||
- show version
|
|
||||||
- show interfaces
|
|
||||||
- { command: example command that prompts, prompt: expected prompt, answer: yes}
|
|
||||||
|
|
||||||
- name: run multiple commands and evaluate the output
|
|
||||||
iosxr_command:
|
|
||||||
commands:
|
|
||||||
- show version
|
|
||||||
- show interfaces
|
|
||||||
wait_for:
|
|
||||||
- result[0] contains IOS-XR
|
|
||||||
- result[1] contains Loopback0
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
stdout:
|
|
||||||
description: The set of responses from the commands
|
|
||||||
returned: always apart from low level errors (such as action plugin)
|
|
||||||
type: list
|
|
||||||
sample: ['...', '...']
|
|
||||||
stdout_lines:
|
|
||||||
description: The value of stdout split into a list
|
|
||||||
returned: always apart from low level errors (such as action plugin)
|
|
||||||
type: list
|
|
||||||
sample: [['...', '...'], ['...'], ['...']]
|
|
||||||
failed_conditions:
|
|
||||||
description: The list of conditionals that have failed
|
|
||||||
returned: failed
|
|
||||||
type: list
|
|
||||||
sample: ['...', '...']
|
|
||||||
"""
|
|
||||||
import time
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.common.parsing import Conditional
|
|
||||||
from ansible.module_utils.network.common.utils import to_lines
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import run_commands, iosxr_argument_spec
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import command_spec
|
|
||||||
|
|
||||||
|
|
||||||
def parse_commands(module, warnings):
|
|
||||||
commands = module.params['commands']
|
|
||||||
for item in list(commands):
|
|
||||||
try:
|
|
||||||
command = item['command']
|
|
||||||
except Exception:
|
|
||||||
command = item
|
|
||||||
if module.check_mode and not command.startswith('show'):
|
|
||||||
warnings.append(
|
|
||||||
'Only show commands are supported when using check mode, not '
|
|
||||||
'executing %s' % command
|
|
||||||
)
|
|
||||||
commands.remove(item)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argument_spec = dict(
|
|
||||||
commands=dict(type='list', required=True),
|
|
||||||
|
|
||||||
wait_for=dict(type='list', aliases=['waitfor']),
|
|
||||||
match=dict(default='all', choices=['all', 'any']),
|
|
||||||
|
|
||||||
retries=dict(default=10, type='int'),
|
|
||||||
interval=dict(default=1, type='int')
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
argument_spec.update(command_spec)
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
warnings = list()
|
|
||||||
result = {'changed': False, 'warnings': warnings}
|
|
||||||
commands = parse_commands(module, warnings)
|
|
||||||
wait_for = module.params['wait_for'] or list()
|
|
||||||
|
|
||||||
try:
|
|
||||||
conditionals = [Conditional(c) for c in wait_for]
|
|
||||||
except AttributeError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc))
|
|
||||||
|
|
||||||
retries = module.params['retries']
|
|
||||||
interval = module.params['interval']
|
|
||||||
match = module.params['match']
|
|
||||||
|
|
||||||
while retries > 0:
|
|
||||||
responses = run_commands(module, commands)
|
|
||||||
|
|
||||||
for item in list(conditionals):
|
|
||||||
if item(responses):
|
|
||||||
if match == 'any':
|
|
||||||
conditionals = list()
|
|
||||||
break
|
|
||||||
conditionals.remove(item)
|
|
||||||
|
|
||||||
if not conditionals:
|
|
||||||
break
|
|
||||||
|
|
||||||
time.sleep(interval)
|
|
||||||
retries -= 1
|
|
||||||
|
|
||||||
if conditionals:
|
|
||||||
failed_conditions = [item.raw for item in conditionals]
|
|
||||||
msg = 'One or more conditional statements have not been satisfied'
|
|
||||||
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
|
||||||
|
|
||||||
result.update({
|
|
||||||
'stdout': responses,
|
|
||||||
'stdout_lines': list(to_lines(responses)),
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,436 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Copyright: Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_config
|
|
||||||
version_added: "2.1"
|
|
||||||
author: "Ricardo Carrillo Cruz (@rcarrillocruz)"
|
|
||||||
short_description: Manage Cisco IOS XR configuration sections
|
|
||||||
description:
|
|
||||||
- Cisco IOS XR configurations use a simple block indent file syntax
|
|
||||||
for segmenting configuration into sections. This module provides
|
|
||||||
an implementation for working with IOS XR configuration sections in
|
|
||||||
a deterministic way.
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
notes:
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- Tested against IOS XRv 6.1.3.
|
|
||||||
- This module does not support C(netconf) connection
|
|
||||||
- Abbreviated commands are NOT idempotent, see
|
|
||||||
L(Network FAQ,../network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands).
|
|
||||||
- Avoid service disrupting changes (viz. Management IP) from config replace.
|
|
||||||
- Do not use C(end) in the replace config file.
|
|
||||||
options:
|
|
||||||
lines:
|
|
||||||
description:
|
|
||||||
- The ordered set of commands that should be configured in the
|
|
||||||
section. The commands must be the exact same commands as found
|
|
||||||
in the device running-config. Be sure to note the configuration
|
|
||||||
command syntax as some commands are automatically modified by the
|
|
||||||
device config parser.
|
|
||||||
aliases: ['commands']
|
|
||||||
parents:
|
|
||||||
description:
|
|
||||||
- The ordered set of parents that uniquely identify the section or hierarchy
|
|
||||||
the commands should be checked against. If the parents argument
|
|
||||||
is omitted, the commands are checked against the set of top
|
|
||||||
level or global commands.
|
|
||||||
src:
|
|
||||||
description:
|
|
||||||
- Specifies the source path to the file that contains the configuration
|
|
||||||
or configuration template to load. The path to the source file can
|
|
||||||
either be the full path on the Ansible control host or a relative
|
|
||||||
path from the playbook or role root directory. This argument is mutually
|
|
||||||
exclusive with I(lines), I(parents).
|
|
||||||
version_added: "2.2"
|
|
||||||
before:
|
|
||||||
description:
|
|
||||||
- The ordered set of commands to push on to the command stack if
|
|
||||||
a change needs to be made. This allows the playbook designer
|
|
||||||
the opportunity to perform configuration commands prior to pushing
|
|
||||||
any changes without affecting how the set of commands are matched
|
|
||||||
against the system.
|
|
||||||
after:
|
|
||||||
description:
|
|
||||||
- The ordered set of commands to append to the end of the command
|
|
||||||
stack if a change needs to be made. Just like with I(before) this
|
|
||||||
allows the playbook designer to append a set of commands to be
|
|
||||||
executed after the command set.
|
|
||||||
match:
|
|
||||||
description:
|
|
||||||
- Instructs the module on the way to perform the matching of
|
|
||||||
the set of commands against the current device config. If
|
|
||||||
match is set to I(line), commands are matched line by line. If
|
|
||||||
match is set to I(strict), command lines are matched with respect
|
|
||||||
to position. If match is set to I(exact), command lines
|
|
||||||
must be an equal match. Finally, if match is set to I(none), the
|
|
||||||
module will not attempt to compare the source configuration with
|
|
||||||
the running configuration on the remote device.
|
|
||||||
default: line
|
|
||||||
choices: ['line', 'strict', 'exact', 'none']
|
|
||||||
replace:
|
|
||||||
description:
|
|
||||||
- Instructs the module on the way to perform the configuration
|
|
||||||
on the device. If the replace argument is set to I(line) then
|
|
||||||
the modified lines are pushed to the device in configuration
|
|
||||||
mode. If the replace argument is set to I(block) then the entire
|
|
||||||
command block is pushed to the device in configuration mode if any
|
|
||||||
line is not correct.
|
|
||||||
default: line
|
|
||||||
choices: ['line', 'block', 'config']
|
|
||||||
force:
|
|
||||||
description:
|
|
||||||
- The force argument instructs the module to not consider the
|
|
||||||
current devices running-config. When set to true, this will
|
|
||||||
cause the module to push the contents of I(src) into the device
|
|
||||||
without first checking if already configured.
|
|
||||||
- Note this argument should be considered deprecated. To achieve
|
|
||||||
the equivalent, set the C(match=none) which is idempotent. This argument
|
|
||||||
will be removed in a future release.
|
|
||||||
type: bool
|
|
||||||
default: 'no'
|
|
||||||
version_added: "2.2"
|
|
||||||
config:
|
|
||||||
description:
|
|
||||||
- The module, by default, will connect to the remote device and
|
|
||||||
retrieve the current running-config to use as a base for comparing
|
|
||||||
against the contents of source. There are times when it is not
|
|
||||||
desirable to have the task get the current running-config for
|
|
||||||
every task in a playbook. The I(config) argument allows the
|
|
||||||
implementer to pass in the configuration to use as the base
|
|
||||||
config for comparison.
|
|
||||||
backup:
|
|
||||||
description:
|
|
||||||
- This argument will cause the module to create a full backup of
|
|
||||||
the current C(running-config) from the remote device before any
|
|
||||||
changes are made. If the C(backup_options) value is not given,
|
|
||||||
the backup file is written to the C(backup) folder in the playbook
|
|
||||||
root directory or role root directory, if playbook is part of an
|
|
||||||
ansible role. If the directory does not exist, it is created.
|
|
||||||
type: bool
|
|
||||||
default: 'no'
|
|
||||||
version_added: "2.2"
|
|
||||||
comment:
|
|
||||||
description:
|
|
||||||
- Allows a commit description to be specified to be included
|
|
||||||
when the configuration is committed. If the configuration is
|
|
||||||
not changed or committed, this argument is ignored.
|
|
||||||
default: 'configured by iosxr_config'
|
|
||||||
version_added: "2.2"
|
|
||||||
admin:
|
|
||||||
description:
|
|
||||||
- Enters into administration configuration mode for making config
|
|
||||||
changes to the device.
|
|
||||||
type: bool
|
|
||||||
default: 'no'
|
|
||||||
version_added: "2.4"
|
|
||||||
label:
|
|
||||||
description:
|
|
||||||
- Allows a commit label to be specified to be included when the
|
|
||||||
configuration is committed. A valid label must begin with an alphabet
|
|
||||||
and not exceed 30 characters, only alphabets, digits, hyphens and
|
|
||||||
underscores are allowed. If the configuration is not changed or
|
|
||||||
committed, this argument is ignored.
|
|
||||||
version_added: "2.7"
|
|
||||||
backup_options:
|
|
||||||
description:
|
|
||||||
- This is a dict object containing configurable options related to backup file path.
|
|
||||||
The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set
|
|
||||||
to I(no) this option will be silently ignored.
|
|
||||||
suboptions:
|
|
||||||
filename:
|
|
||||||
description:
|
|
||||||
- The filename to be used to store the backup configuration. If the filename
|
|
||||||
is not given it will be generated based on the hostname, current time and date
|
|
||||||
in format defined by <hostname>_config.<current-date>@<current-time>
|
|
||||||
dir_path:
|
|
||||||
description:
|
|
||||||
- This option provides the path ending with directory name in which the backup
|
|
||||||
configuration file will be stored. If the directory does not exist it will be first
|
|
||||||
created and the filename is either the value of C(filename) or default filename
|
|
||||||
as described in C(filename) options description. If the path value is not given
|
|
||||||
in that case a I(backup) directory will be created in the current working directory
|
|
||||||
and backup configuration will be copied in C(filename) within I(backup) directory.
|
|
||||||
type: path
|
|
||||||
type: dict
|
|
||||||
version_added: "2.8"
|
|
||||||
exclusive:
|
|
||||||
description:
|
|
||||||
- Enters into exclusive configuration mode that locks out all users from committing
|
|
||||||
configuration changes until the exclusive session ends.
|
|
||||||
type: bool
|
|
||||||
default: false
|
|
||||||
version_added: "2.9"
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: configure top level configuration
|
|
||||||
iosxr_config:
|
|
||||||
lines: hostname {{ inventory_hostname }}
|
|
||||||
|
|
||||||
- name: configure interface settings
|
|
||||||
iosxr_config:
|
|
||||||
lines:
|
|
||||||
- description test interface
|
|
||||||
- ip address 172.31.1.1 255.255.255.0
|
|
||||||
parents: interface GigabitEthernet0/0/0/0
|
|
||||||
|
|
||||||
- name: load a config from disk and replace the current config
|
|
||||||
iosxr_config:
|
|
||||||
src: config.cfg
|
|
||||||
replace: config
|
|
||||||
backup: yes
|
|
||||||
|
|
||||||
- name: for idempotency, use full-form commands
|
|
||||||
iosxr_config:
|
|
||||||
lines:
|
|
||||||
# - shut
|
|
||||||
- shutdown
|
|
||||||
# parents: int g0/0/0/1
|
|
||||||
parents: interface GigabitEthernet0/0/0/1
|
|
||||||
|
|
||||||
- name: configurable backup path
|
|
||||||
iosxr_config:
|
|
||||||
src: config.cfg
|
|
||||||
backup: yes
|
|
||||||
backup_options:
|
|
||||||
filename: backup.cfg
|
|
||||||
dir_path: /home/user
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
description: The set of commands that will be pushed to the remote device
|
|
||||||
returned: If there are commands to run against the host
|
|
||||||
type: list
|
|
||||||
sample: ['hostname foo', 'router ospf 1', 'router-id 1.1.1.1']
|
|
||||||
backup_path:
|
|
||||||
description: The full path to the backup file
|
|
||||||
returned: when backup is yes
|
|
||||||
type: str
|
|
||||||
sample: /playbooks/ansible/backup/iosxr01_config.2016-07-16@22:28:34
|
|
||||||
filename:
|
|
||||||
description: The name of the backup file
|
|
||||||
returned: when backup is yes and filename is not specified in backup options
|
|
||||||
type: str
|
|
||||||
sample: iosxr01_config.2016-07-16@22:28:34
|
|
||||||
shortname:
|
|
||||||
description: The full path to the backup file excluding the timestamp
|
|
||||||
returned: when backup is yes and filename is not specified in backup options
|
|
||||||
type: str
|
|
||||||
sample: /playbooks/ansible/backup/iosxr01_config
|
|
||||||
date:
|
|
||||||
description: The date extracted from the backup file name
|
|
||||||
returned: when backup is yes
|
|
||||||
type: str
|
|
||||||
sample: "2016-07-16"
|
|
||||||
time:
|
|
||||||
description: The time extracted from the backup file name
|
|
||||||
returned: when backup is yes
|
|
||||||
type: str
|
|
||||||
sample: "22:28:34"
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text, to_bytes
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.connection import ConnectionError
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import load_config, get_config, get_connection
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, copy_file
|
|
||||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
|
||||||
|
|
||||||
DEFAULT_COMMIT_COMMENT = 'configured by iosxr_config'
|
|
||||||
|
|
||||||
|
|
||||||
def copy_file_to_node(module):
|
|
||||||
""" Copy config file to IOS-XR node. We use SFTP because older IOS-XR versions don't handle SCP very well.
|
|
||||||
"""
|
|
||||||
src = '/tmp/ansible_config.txt'
|
|
||||||
file = open(src, 'wb')
|
|
||||||
file.write(to_bytes(module.params['src'], errors='surrogate_or_strict'))
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
dst = '/harddisk:/ansible_config.txt'
|
|
||||||
copy_file(module, src, dst, 'sftp')
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def check_args(module, warnings):
|
|
||||||
if module.params['comment']:
|
|
||||||
if len(module.params['comment']) > 60:
|
|
||||||
module.fail_json(msg='comment argument cannot be more than 60 characters')
|
|
||||||
if module.params['label']:
|
|
||||||
label = module.params['label']
|
|
||||||
if len(label) > 30:
|
|
||||||
module.fail_json(msg='label argument cannot be more than 30 characters')
|
|
||||||
if not label[0].isalpha():
|
|
||||||
module.fail_json(msg='label argument must begin with an alphabet')
|
|
||||||
valid_chars = re.match(r'[\w-]*$', label)
|
|
||||||
if not valid_chars:
|
|
||||||
module.fail_json(
|
|
||||||
msg='label argument must only contain alphabets,' +
|
|
||||||
'digits, underscores or hyphens'
|
|
||||||
)
|
|
||||||
if module.params['force']:
|
|
||||||
warnings.append('The force argument is deprecated, please use '
|
|
||||||
'match=none instead. This argument will be '
|
|
||||||
'removed in the future')
|
|
||||||
|
|
||||||
|
|
||||||
def get_running_config(module):
|
|
||||||
contents = module.params['config']
|
|
||||||
if not contents:
|
|
||||||
contents = get_config(module)
|
|
||||||
return contents
|
|
||||||
|
|
||||||
|
|
||||||
def get_candidate(module):
|
|
||||||
candidate = ''
|
|
||||||
if module.params['src']:
|
|
||||||
candidate = module.params['src']
|
|
||||||
elif module.params['lines']:
|
|
||||||
candidate_obj = NetworkConfig(indent=1)
|
|
||||||
parents = module.params['parents'] or list()
|
|
||||||
candidate_obj.add(module.params['lines'], parents=parents)
|
|
||||||
candidate = dumps(candidate_obj, 'raw')
|
|
||||||
return candidate
|
|
||||||
|
|
||||||
|
|
||||||
def run(module, result):
|
|
||||||
match = module.params['match']
|
|
||||||
replace = module.params['replace']
|
|
||||||
replace_config = replace == 'config'
|
|
||||||
path = module.params['parents']
|
|
||||||
comment = module.params['comment']
|
|
||||||
admin = module.params['admin']
|
|
||||||
exclusive = module.params['exclusive']
|
|
||||||
check_mode = module.check_mode
|
|
||||||
label = module.params['label']
|
|
||||||
|
|
||||||
candidate_config = get_candidate(module)
|
|
||||||
running_config = get_running_config(module)
|
|
||||||
|
|
||||||
commands = None
|
|
||||||
replace_file_path = None
|
|
||||||
connection = get_connection(module)
|
|
||||||
try:
|
|
||||||
response = connection.get_diff(candidate=candidate_config, running=running_config, diff_match=match, path=path, diff_replace=replace)
|
|
||||||
except ConnectionError as exc:
|
|
||||||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
|
||||||
|
|
||||||
config_diff = response.get('config_diff')
|
|
||||||
|
|
||||||
if replace_config:
|
|
||||||
running_base_diff_resp = connection.get_diff(candidate=running_config, running=candidate_config, diff_match=match, path=path, diff_replace=replace)
|
|
||||||
if config_diff or running_base_diff_resp['config_diff']:
|
|
||||||
ret = copy_file_to_node(module)
|
|
||||||
if not ret:
|
|
||||||
module.fail_json(msg='Copy of config file to the node failed')
|
|
||||||
|
|
||||||
commands = ['load harddisk:/ansible_config.txt']
|
|
||||||
replace_file_path = 'harddisk:/ansible_config.txt'
|
|
||||||
|
|
||||||
if config_diff or commands:
|
|
||||||
if not replace_config:
|
|
||||||
commands = config_diff.split('\n')
|
|
||||||
|
|
||||||
if any((module.params['lines'], module.params['src'])):
|
|
||||||
if module.params['before']:
|
|
||||||
commands[:0] = module.params['before']
|
|
||||||
|
|
||||||
if module.params['after']:
|
|
||||||
commands.extend(module.params['after'])
|
|
||||||
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
commit = not check_mode
|
|
||||||
diff = load_config(
|
|
||||||
module, commands, commit=commit,
|
|
||||||
replace=replace_file_path, comment=comment, admin=admin, exclusive=exclusive,
|
|
||||||
label=label
|
|
||||||
)
|
|
||||||
if diff:
|
|
||||||
result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""main entry point for module execution
|
|
||||||
"""
|
|
||||||
backup_spec = dict(
|
|
||||||
filename=dict(),
|
|
||||||
dir_path=dict(type='path')
|
|
||||||
)
|
|
||||||
argument_spec = dict(
|
|
||||||
src=dict(type='path'),
|
|
||||||
|
|
||||||
lines=dict(aliases=['commands'], type='list'),
|
|
||||||
parents=dict(type='list'),
|
|
||||||
|
|
||||||
before=dict(type='list'),
|
|
||||||
after=dict(type='list'),
|
|
||||||
|
|
||||||
match=dict(default='line', choices=['line', 'strict', 'exact', 'none']),
|
|
||||||
replace=dict(default='line', choices=['line', 'block', 'config']),
|
|
||||||
|
|
||||||
# this argument is deprecated in favor of setting match: none
|
|
||||||
# it will be removed in a future version
|
|
||||||
force=dict(default=False, type='bool'),
|
|
||||||
|
|
||||||
config=dict(),
|
|
||||||
backup=dict(type='bool', default=False),
|
|
||||||
backup_options=dict(type='dict', options=backup_spec),
|
|
||||||
comment=dict(default=DEFAULT_COMMIT_COMMENT),
|
|
||||||
admin=dict(type='bool', default=False),
|
|
||||||
exclusive=dict(type='bool', default=False),
|
|
||||||
label=dict()
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
mutually_exclusive = [('lines', 'src'),
|
|
||||||
('parents', 'src')]
|
|
||||||
|
|
||||||
required_if = [('match', 'strict', ['lines']),
|
|
||||||
('match', 'exact', ['lines']),
|
|
||||||
('replace', 'block', ['lines']),
|
|
||||||
('replace', 'config', ['src'])]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
mutually_exclusive=mutually_exclusive,
|
|
||||||
required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
if module.params['force'] is True:
|
|
||||||
module.params['match'] = 'none'
|
|
||||||
|
|
||||||
warnings = list()
|
|
||||||
check_args(module, warnings)
|
|
||||||
|
|
||||||
result = dict(changed=False, warnings=warnings)
|
|
||||||
|
|
||||||
if module.params['backup']:
|
|
||||||
result['__backup__'] = get_config(module)
|
|
||||||
|
|
||||||
if any((module.params['src'], module.params['lines'])):
|
|
||||||
run(module, result)
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,215 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
The module file for iosxr_facts
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': [u'preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_facts
|
|
||||||
version_added: 2.2
|
|
||||||
short_description: Get facts about iosxr devices.
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
description:
|
|
||||||
- Collects facts from network devices running the iosxr operating
|
|
||||||
system. This module places the facts gathered in the fact tree keyed by the
|
|
||||||
respective resource name. The facts module will always collect a
|
|
||||||
base set of facts from the device and can enable or disable
|
|
||||||
collection of additional facts.
|
|
||||||
notes:
|
|
||||||
- Tested against IOS-XR 6.1.3.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
author:
|
|
||||||
- Ricardo Carrillo Cruz (@rcarrillocruz)
|
|
||||||
- Nilashish Chakraborty (@Nilashishc)
|
|
||||||
options:
|
|
||||||
gather_subset:
|
|
||||||
description:
|
|
||||||
- When supplied, this argument will restrict the facts collected
|
|
||||||
to a given subset. Possible values for this argument include
|
|
||||||
all, hardware, config, and interfaces. Can specify a list of
|
|
||||||
values to include a larger subset. Values can also be used
|
|
||||||
with an initial C(M(!)) to specify that a specific subset should
|
|
||||||
not be collected.
|
|
||||||
required: false
|
|
||||||
default: '!config'
|
|
||||||
gather_network_resources:
|
|
||||||
description:
|
|
||||||
- When supplied, this argument will restrict the facts collected
|
|
||||||
to a given subset. Possible values for this argument include
|
|
||||||
all and the resources like interfaces, lacp etc.
|
|
||||||
Can specify a list of values to include a larger subset. Values
|
|
||||||
can also be used with an initial C(M(!)) to specify that a
|
|
||||||
specific subset should not be collected.
|
|
||||||
Valid subsets are 'all', 'lacp', 'lacp_interfaces', 'lldp_global',
|
|
||||||
'lldp_interfaces', 'interfaces', 'l2_interfaces', 'l3_interfaces',
|
|
||||||
'lag_interfaces', 'acls', 'acl_interfaces', 'static_routes.
|
|
||||||
required: false
|
|
||||||
version_added: "2.9"
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
# Gather all facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_subset: all
|
|
||||||
gather_network_resources: all
|
|
||||||
|
|
||||||
# Collect only the config and default facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_subset:
|
|
||||||
- config
|
|
||||||
|
|
||||||
# Do not collect hardware facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_subset:
|
|
||||||
- "!hardware"
|
|
||||||
|
|
||||||
# Collect only the lacp facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_subset:
|
|
||||||
- "!all"
|
|
||||||
- "!min"
|
|
||||||
gather_network_resources:
|
|
||||||
- lacp
|
|
||||||
|
|
||||||
# Do not collect lacp_interfaces facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_network_resources:
|
|
||||||
- "!lacp_interfaces"
|
|
||||||
|
|
||||||
# Collect lacp and minimal default facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_subset: min
|
|
||||||
gather_network_resources: lacp
|
|
||||||
|
|
||||||
# Collect only the interfaces facts
|
|
||||||
- iosxr_facts:
|
|
||||||
gather_subset:
|
|
||||||
- "!all"
|
|
||||||
- "!min"
|
|
||||||
gather_network_resources:
|
|
||||||
- interfaces
|
|
||||||
- l2_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
ansible_net_gather_subset:
|
|
||||||
description: The list of fact subsets collected from the device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
|
|
||||||
# default
|
|
||||||
ansible_net_version:
|
|
||||||
description: The operating system version running on the remote device
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
ansible_net_hostname:
|
|
||||||
description: The configured hostname of the device
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
ansible_net_image:
|
|
||||||
description: The image file the device is running
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
ansible_net_api:
|
|
||||||
description: The name of the transport
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
ansible_net_python_version:
|
|
||||||
description: The Python version Ansible controller is using
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
ansible_net_model:
|
|
||||||
description: The model name returned from the device
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
|
|
||||||
# hardware
|
|
||||||
ansible_net_filesystems:
|
|
||||||
description: All file system names available on the device
|
|
||||||
returned: when hardware is configured
|
|
||||||
type: list
|
|
||||||
ansible_net_memfree_mb:
|
|
||||||
description: The available free memory on the remote device in Mb
|
|
||||||
returned: when hardware is configured
|
|
||||||
type: int
|
|
||||||
ansible_net_memtotal_mb:
|
|
||||||
description: The total memory on the remote device in Mb
|
|
||||||
returned: when hardware is configured
|
|
||||||
type: int
|
|
||||||
|
|
||||||
# config
|
|
||||||
ansible_net_config:
|
|
||||||
description: The current active config from the device
|
|
||||||
returned: when config is configured
|
|
||||||
type: str
|
|
||||||
|
|
||||||
# interfaces
|
|
||||||
ansible_net_all_ipv4_addresses:
|
|
||||||
description: All IPv4 addresses configured on the device
|
|
||||||
returned: when interfaces is configured
|
|
||||||
type: list
|
|
||||||
ansible_net_all_ipv6_addresses:
|
|
||||||
description: All IPv6 addresses configured on the device
|
|
||||||
returned: when interfaces is configured
|
|
||||||
type: list
|
|
||||||
ansible_net_interfaces:
|
|
||||||
description: A hash of all interfaces running on the system
|
|
||||||
returned: when interfaces is configured
|
|
||||||
type: dict
|
|
||||||
ansible_net_neighbors:
|
|
||||||
description: The list of LLDP neighbors from the remote device
|
|
||||||
returned: when interfaces is configured
|
|
||||||
type: dict
|
|
||||||
|
|
||||||
# network resources
|
|
||||||
ansible_net_gather_network_resources:
|
|
||||||
description: The list of fact resource subsets collected from the device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.facts.facts import FactsArgs
|
|
||||||
from ansible.module_utils.network.iosxr.facts.facts import Facts
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: ansible_facts
|
|
||||||
"""
|
|
||||||
argument_spec = FactsArgs.argument_spec
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
warnings = []
|
|
||||||
if module.params["gather_subset"] == "!config":
|
|
||||||
warnings.append('default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards')
|
|
||||||
|
|
||||||
result = Facts(module).get_facts()
|
|
||||||
|
|
||||||
ansible_facts, additional_warnings = result
|
|
||||||
warnings.extend(additional_warnings)
|
|
||||||
|
|
||||||
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,365 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
module: iosxr_interfaces
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage interface attributes on Cisco IOS-XR network devices
|
|
||||||
description: This module manages the interface attributes on Cisco IOS-XR network devices.
|
|
||||||
author: Sumit Jaiswal (@justjais)
|
|
||||||
notes:
|
|
||||||
- Tested against Cisco IOS-XRv Version 6.1.3 on VIRL.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A dictionary of interface options
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Full name of the interface to configure in C(type + path) format. e.g. C(GigabitEthernet0/0/0/0)
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
description:
|
|
||||||
description:
|
|
||||||
- Interface description.
|
|
||||||
type: str
|
|
||||||
enabled:
|
|
||||||
default: True
|
|
||||||
description:
|
|
||||||
- Administrative state of the interface.
|
|
||||||
- Set the value to C(True) to administratively enable the interface or C(False) to disable it.
|
|
||||||
type: bool
|
|
||||||
speed:
|
|
||||||
description:
|
|
||||||
- Configure the speed for an interface. Default is auto-negotiation when not configured.
|
|
||||||
type: int
|
|
||||||
mtu:
|
|
||||||
description:
|
|
||||||
- Sets the MTU value for the interface. Applicable for Ethernet interfaces only.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
duplex:
|
|
||||||
description:
|
|
||||||
- Configures the interface duplex mode. Default is auto-negotiation when not configured.
|
|
||||||
type: str
|
|
||||||
choices: ['full', 'half']
|
|
||||||
state:
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion
|
|
||||||
type: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
---
|
|
||||||
# Using merged
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Replaced by Ansible Team
|
|
||||||
# mtu 2000
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Configure Ethernet interfaces
|
|
||||||
iosxr_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
description: 'Configured by Ansible'
|
|
||||||
enabled: True
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
description: 'Configured by Ansible Network'
|
|
||||||
enabled: False
|
|
||||||
duplex: full
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description Configured and Merged by Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Configured and Merged by Ansible Network
|
|
||||||
# mtu 2600
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex full
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description Configured by Ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description Test
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Configure following interfaces and replace their existing config
|
|
||||||
iosxr_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
description: Configured by Ansible
|
|
||||||
enabled: True
|
|
||||||
mtu: 2000
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
description: 'Configured by Ansible Network'
|
|
||||||
enabled: False
|
|
||||||
duplex: auto
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description Configured by Ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description Configured and Replaced by Ansible
|
|
||||||
# mtu 2000
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Configured and Replaced by Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description Configured by Ansible
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Configured by Ansible
|
|
||||||
# mtu 2600
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex full
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Override interfaces
|
|
||||||
iosxr_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
description: 'Configured by Ansible'
|
|
||||||
enabled: True
|
|
||||||
duplex: auto
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
description: 'Configured by Ansible Network'
|
|
||||||
enabled: False
|
|
||||||
speed: 1000
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description Configured and Overridden by Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# speed 1000
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Configured and Overridden by Ansible Network
|
|
||||||
# mtu 2000
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex full
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description Configured and Overridden by Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# speed 1000
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Configured and Overridden by Ansible Network
|
|
||||||
# mtu 2000
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex full
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Delete IOSXR interfaces as in given arguments
|
|
||||||
iosxr_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 178.18.169.23 255.255.255.0
|
|
||||||
# dot1q native vlan 30
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# dot1q native vlan 1021
|
|
||||||
# !
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: The configuration returned will always be in the same format of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: The configuration returned will always be in the same format of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['interface GigabitEthernet0/0/0/2', 'description: Configured by Ansible', 'shutdown']
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.interfaces.interfaces import InterfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.interfaces.interfaces import Interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',))]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
|
|
||||||
required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,429 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_l2_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
GENERATOR_VERSION = '1.0'
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_l2_interfaces
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage Layer-2 interface on Cisco IOS-XR devices
|
|
||||||
description: This module manages the Layer-2 interface attributes on Cisco IOS-XR devices.
|
|
||||||
author: Sumit Jaiswal (@justjais)
|
|
||||||
notes:
|
|
||||||
- Tested against Cisco IOS-XRv Version 6.1.3 on VIRL.
|
|
||||||
- This module works with connection C(network_cli).
|
|
||||||
See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A dictionary of Layer-2 interface options
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Full name of the interface/sub-interface excluding any logical unit number,
|
|
||||||
e.g. GigabitEthernet0/0/0/1 or GigabitEthernet0/0/0/1.100.
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
native_vlan:
|
|
||||||
description:
|
|
||||||
- Configure a native VLAN ID for the trunk
|
|
||||||
type: int
|
|
||||||
l2transport:
|
|
||||||
description:
|
|
||||||
- Switchport mode access command to configure the interface as a layer 2 access
|
|
||||||
type: bool
|
|
||||||
l2protocol:
|
|
||||||
description:
|
|
||||||
- Configures Layer 2 protocol tunneling and protocol data unit (PDU) filtering on an interface.
|
|
||||||
type: list
|
|
||||||
suboptions:
|
|
||||||
cdp:
|
|
||||||
description:
|
|
||||||
- Cisco Discovery Protocol (CDP) tunneling and data unit parameters.
|
|
||||||
choices: ['drop','forward', 'tunnel']
|
|
||||||
type: str
|
|
||||||
pvst:
|
|
||||||
description:
|
|
||||||
- Configures the per-VLAN Spanning Tree Protocol (PVST) tunneling and data unit parameters.
|
|
||||||
choices: ['drop','forward', 'tunnel']
|
|
||||||
type: str
|
|
||||||
stp:
|
|
||||||
description:
|
|
||||||
- Spanning Tree Protocol (STP) tunneling and data unit parameters.
|
|
||||||
choices: ['drop','forward', 'tunnel']
|
|
||||||
type: str
|
|
||||||
vtp:
|
|
||||||
description:
|
|
||||||
- VLAN Trunk Protocol (VTP) tunneling and data unit parameters.
|
|
||||||
choices: ['drop','forward', 'tunnel']
|
|
||||||
type: str
|
|
||||||
q_vlan:
|
|
||||||
description:
|
|
||||||
- 802.1Q VLAN configuration. Note that it can accept either 2 VLAN IDs when configuring Q-in-Q VLAN,
|
|
||||||
or it will accept 1 VLAN ID and 'any' as input list when configuring Q-in-any vlan as input. Note, that
|
|
||||||
this option is valid only with respect to Sub-Interface and is not valid when configuring for Interface.
|
|
||||||
type: list
|
|
||||||
propagate:
|
|
||||||
description:
|
|
||||||
- Propagate Layer 2 transport events. Note that it will work only when the I(l2tranport) option is set
|
|
||||||
to TRUE
|
|
||||||
type: bool
|
|
||||||
state:
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion
|
|
||||||
type: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
---
|
|
||||||
# Using merged
|
|
||||||
#
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Merge provided configuration with device configuration
|
|
||||||
iosxr_l2_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
native_vlan: 20
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
native_vlan: 40
|
|
||||||
l2transport: True
|
|
||||||
l2protocol:
|
|
||||||
- stp: tunnel
|
|
||||||
- name: GigabitEthernet0/0/0/3.900
|
|
||||||
l2transport: True
|
|
||||||
q_vlan:
|
|
||||||
- 20
|
|
||||||
- 40
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 10
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp tunnel
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.900 l2transport
|
|
||||||
# dot1q vlan 20 40
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
#
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 10
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp tunnel
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.900 l2transport
|
|
||||||
# dot1q vlan 20 40
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Replaces device configuration of listed interfaces with provided configuration
|
|
||||||
iosxr_l2_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
native_vlan: 40
|
|
||||||
l2transport: True
|
|
||||||
l2protocol:
|
|
||||||
- stp: forward
|
|
||||||
- name: GigabitEthernet0/0/0/3.900
|
|
||||||
q_vlan:
|
|
||||||
- 20
|
|
||||||
- any
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 40
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp forward
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.900 l2transport
|
|
||||||
# dot1q vlan 20 any
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
#
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 10
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp tunnel
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.900 l2transport
|
|
||||||
# dot1q vlan 20 40
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Override device configuration of all interfaces with provided configuration
|
|
||||||
iosxr_l2_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
native_vlan: 40
|
|
||||||
l2transport: True
|
|
||||||
l2protocol:
|
|
||||||
- stp: forward
|
|
||||||
- name: GigabitEthernet0/0/0/3.900
|
|
||||||
q_vlan:
|
|
||||||
- 20
|
|
||||||
- any
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 40
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp forward
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.900
|
|
||||||
# dot1q vlan 20 any
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
#
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 10
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp tunnel
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: "Delete L2 attributes of given interfaces (Note: This won't delete the interface itself)"
|
|
||||||
iosxr_l2_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using Deleted without any config passed
|
|
||||||
# "(NOTE: This will delete all of configured resource module attributes from each configured interface)"
|
|
||||||
#
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# dot1q native vlan 20
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# dot1q native vlan 10
|
|
||||||
# l2transport
|
|
||||||
# l2protocol stp tunnel
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: "Delete L2 attributes of all interfaces (Note: This won't delete the interface itself)"
|
|
||||||
iosxr_l2_interfaces:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description Ansible Network
|
|
||||||
# vrf custB
|
|
||||||
# ipv4 address 10.10.0.2 255.255.255.0
|
|
||||||
# duplex half
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description Test description
|
|
||||||
# !
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: The configuration returned will always be in the same format of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: The configuration returned will always be in the same format of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['interface GigabitEthernet0/0/0/2', 'command 2', 'command 3']
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.l2_interfaces.l2_interfaces import L2_InterfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.l2_interfaces.l2_interfaces import L2_Interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',))]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=L2_InterfacesArgs.argument_spec,
|
|
||||||
required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = L2_Interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,424 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat Inc.
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
##############################################
|
|
||||||
# WARNING
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
##############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for ios_l3_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
module: iosxr_l3_interfaces
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage Layer-3 interface on Cisco IOS-XR devices.
|
|
||||||
description: This module provides declarative management of Layer-3 interface on Cisco IOS-XR devices.
|
|
||||||
author: Sumit Jaiswal (@justjais)
|
|
||||||
notes:
|
|
||||||
- Tested against Cisco IOS-XRv Version 6.1.3 on VIRL.
|
|
||||||
- This module works with connection C(network_cli).
|
|
||||||
See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A dictionary of Layer-3 interface options
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1.
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
ipv4:
|
|
||||||
description:
|
|
||||||
- IPv4 address to be set for the Layer-3 interface mentioned in I(name) option.
|
|
||||||
- The address format is <ipv4 address>/<mask>, the mask is number in range 0-32
|
|
||||||
eg. 192.168.0.1/24
|
|
||||||
type: list
|
|
||||||
suboptions:
|
|
||||||
address:
|
|
||||||
description:
|
|
||||||
- Configures the IPv4 address for Interface.
|
|
||||||
type: str
|
|
||||||
secondary:
|
|
||||||
description:
|
|
||||||
- Configures the IP address as a secondary address.
|
|
||||||
type: bool
|
|
||||||
ipv6:
|
|
||||||
description:
|
|
||||||
- IPv6 address to be set for the Layer-3 interface mentioned in I(name) option.
|
|
||||||
- The address format is <ipv6 address>/<mask>, the mask is number in range 0-128
|
|
||||||
eg. fd5d:12c9:2201:1::1/64
|
|
||||||
type: list
|
|
||||||
suboptions:
|
|
||||||
address:
|
|
||||||
description:
|
|
||||||
- Configures the IPv6 address for Interface.
|
|
||||||
type: str
|
|
||||||
state:
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion
|
|
||||||
type: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
---
|
|
||||||
# Using merged
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.0.2 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Merge provided configuration with device configuration
|
|
||||||
iosxr_l3_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
ipv4:
|
|
||||||
- address: 192.168.0.1/24
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
ipv4:
|
|
||||||
- address: 192.168.2.1/24
|
|
||||||
secondary: True
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.1.0 255.255.255.0
|
|
||||||
# ipv4 address 192.168.2.1 255.255.255.0 secondary
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.1.0 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Override device configuration of all interfaces with provided configuration
|
|
||||||
iosxr_l3_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
ipv4:
|
|
||||||
- address: 192.168.0.1/24
|
|
||||||
- name: GigabitEthernet0/0/0/3.700
|
|
||||||
ipv4:
|
|
||||||
- address: 192.168.0.2/24
|
|
||||||
- address: 192.168.2.1/24
|
|
||||||
secondary: True
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# ipv4 address 192.168.0.2 255.255.255.0
|
|
||||||
# ipv4 address 192.168.2.1 255.255.255.0 secondary
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.0.2 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Replaces device configuration of listed interfaces with provided configuration
|
|
||||||
iosxr_l3_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
ipv6:
|
|
||||||
- address: fd5d:12c9:2201:1::1/64
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
ipv4:
|
|
||||||
- address: 192.168.0.2/24
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv4 address 192.168.0.2 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# ipv4 address 192.168.2.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# ipv4 address 192.168.3.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.0.2 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: "Delete L3 attributes of given interfaces (Note: This won't delete the interface itself)"
|
|
||||||
iosxr_l3_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/3
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
- name: GigabitEthernet0/0/0/3.700
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# ipv4 address 192.168.2.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# ipv4 address 192.168.3.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using Deleted without any config passed
|
|
||||||
# "(NOTE: This will delete all of configured resource module attributes from each configured interface)"
|
|
||||||
|
|
||||||
# Before state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# ipv4 address 192.168.2.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# ipv4 address 192.168.3.1 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# ipv4 address 192.168.0.2 255.255.255.0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# ipv4 address 192.168.0.1 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# ipv6 address fd5d:12c9:2201:1::1/64
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
|
|
||||||
- name: "Delete L3 attributes of all interfaces (Note: This won't delete the interface itself)"
|
|
||||||
iosxr_l3_interfaces:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state:
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# viosxr#show running-config interface
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3.700
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: The configuration returned will always be in the same format of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: The configuration returned will always be in the same format of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['interface GigabitEthernet0/0/0/1', 'ipv4 address 192.168.0.1 255.255.255.0']
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.l3_interfaces.l3_interfaces import L3_Interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',))]
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=L3_InterfacesArgs.argument_spec,
|
|
||||||
required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = L3_Interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,300 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_lacp
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_lacp
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage Global Link Aggregation Control Protocol (LACP) on IOS-XR devices.
|
|
||||||
description:
|
|
||||||
- This module manages Global Link Aggregation Control Protocol (LACP) on IOS-XR devices.
|
|
||||||
notes:
|
|
||||||
- Tested against IOS-XR 6.1.3.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
author: Nilashish Chakraborty (@nilashishc)
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: The provided configurations.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
system:
|
|
||||||
description: This option sets the default system parameters for LACP bundles.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
priority:
|
|
||||||
description:
|
|
||||||
- The system priority to use in LACP negotiations.
|
|
||||||
- Lower value is higher priority.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
mac:
|
|
||||||
type: dict
|
|
||||||
description:
|
|
||||||
- The system MAC related configuration for LACP.
|
|
||||||
suboptions:
|
|
||||||
address:
|
|
||||||
description:
|
|
||||||
- The system ID to use in LACP negotiations.
|
|
||||||
type: str
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
# Using merged
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#show running-config lacp
|
|
||||||
# Tue Jul 16 17:46:08.147 UTC
|
|
||||||
# % No such configuration item(s)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Merge provided configuration with device configuration
|
|
||||||
iosxr_lacp:
|
|
||||||
config:
|
|
||||||
system:
|
|
||||||
priority: 10
|
|
||||||
mac:
|
|
||||||
address: 00c1.4c00.bd15
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# -----------------------
|
|
||||||
#
|
|
||||||
# "before": {}
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "lacp system priority 10",
|
|
||||||
# "lacp system mac 00c1.4c00.bd15"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "after": {
|
|
||||||
# "system": {
|
|
||||||
# "mac": {
|
|
||||||
# "address": "00c1.4c00.bd15"
|
|
||||||
# },
|
|
||||||
# "priority": 10
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run lacp
|
|
||||||
# Tue Jul 16 17:51:29.365 UTC
|
|
||||||
# lacp system mac 00c1.4c00.bd15
|
|
||||||
# lacp system priority 10
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run lacp
|
|
||||||
# Tue Jul 16 17:53:59.904 UTC
|
|
||||||
# lacp system mac 00c1.4c00.bd15
|
|
||||||
# lacp system priority 10
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Replace device global lacp configuration with the given configuration
|
|
||||||
iosxr_lacp:
|
|
||||||
config:
|
|
||||||
system:
|
|
||||||
priority: 11
|
|
||||||
state: replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# -----------------------
|
|
||||||
# "before": {
|
|
||||||
# "system": {
|
|
||||||
# "mac": {
|
|
||||||
# "address": "00c1.4c00.bd15"
|
|
||||||
# },
|
|
||||||
# "priority": 10
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "no lacp system mac",
|
|
||||||
# "lacp system priority 11"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "after": {
|
|
||||||
# "system": {
|
|
||||||
# "priority": 11
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run lacp
|
|
||||||
# Tue Jul 16 18:02:40.379 UTC
|
|
||||||
# lacp system priority 11
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run lacp
|
|
||||||
# Tue Jul 16 18:37:09.727 UTC
|
|
||||||
# lacp system mac 00c1.4c00.bd15
|
|
||||||
# lacp system priority 11
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete global LACP configurations from the device
|
|
||||||
iosxr_lacp:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# -----------------------
|
|
||||||
# "before": {
|
|
||||||
# "system": {
|
|
||||||
# "mac": {
|
|
||||||
# "address": "00c1.4c00.bd15"
|
|
||||||
# },
|
|
||||||
# "priority": 11
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "no lacp system mac",
|
|
||||||
# "no lacp system priority"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "after": {}
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run lacp
|
|
||||||
# Tue Jul 16 18:39:44.116 UTC
|
|
||||||
# % No such configuration item(s)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: dict
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['lacp system priority 10', 'lacp system mac 00c1.4c00.bd15']
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lacp.lacp import LacpArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.lacp.lacp import Lacp
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',))]
|
|
||||||
module = AnsibleModule(argument_spec=LacpArgs.argument_spec, required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Lacp(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,539 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_lacp_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_lacp_interfaces
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage Link Aggregation Control Protocol (LACP) attributes of interfaces on IOS-XR devices.
|
|
||||||
description:
|
|
||||||
- This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on IOS-XR devices.
|
|
||||||
notes:
|
|
||||||
- Tested against IOS-XR 6.1.3.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
author: Nilashish Chakraborty (@nilashishc)
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A dictionary of LACP interfaces options.
|
|
||||||
type: list
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name/Identifier of the interface or Ether-Bundle.
|
|
||||||
type: str
|
|
||||||
churn_logging:
|
|
||||||
description:
|
|
||||||
- Specifies the parameter for logging of LACP churn events.
|
|
||||||
- Valid only for ether-bundles.
|
|
||||||
- Mode 'actor' logs actor churn events only.
|
|
||||||
- Mode 'partner' logs partner churn events only.
|
|
||||||
- Mode 'both' logs actor and partner churn events only.
|
|
||||||
type: str
|
|
||||||
choices: ['actor', 'partner', 'both']
|
|
||||||
collector_max_delay:
|
|
||||||
description:
|
|
||||||
- Specifies the collector max delay to be signaled to the LACP partner.
|
|
||||||
- Valid only for ether-bundles.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
period:
|
|
||||||
description:
|
|
||||||
- Specifies the rate at which packets are sent or received.
|
|
||||||
- For ether-bundles, this specifies the period to be used
|
|
||||||
by its member links.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
switchover_suppress_flaps:
|
|
||||||
description:
|
|
||||||
- Specifies the time for which to suppress flaps during
|
|
||||||
a LACP switchover.
|
|
||||||
- Valid only for ether-bundles.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
system:
|
|
||||||
description:
|
|
||||||
- This dict object contains configurable options related to LACP
|
|
||||||
system parameters for ether-bundles.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
priority:
|
|
||||||
description:
|
|
||||||
- Specifies the system priority to use in LACP negotiations for
|
|
||||||
the bundle.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
mac:
|
|
||||||
description:
|
|
||||||
- Specifies the system ID to use in LACP negotiations for
|
|
||||||
the bundle, encoded as a MAC address.
|
|
||||||
type: str
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
# Using merged
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh running-config interface
|
|
||||||
# Sun Jul 21 18:01:35.079 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1'
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Merge provided configuration with device configuration
|
|
||||||
iosxr_lacp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether10
|
|
||||||
churn_logging: actor
|
|
||||||
collector_max_delay: 100
|
|
||||||
switchover_suppress_flaps: 500
|
|
||||||
|
|
||||||
- name: Bundle-Ether11
|
|
||||||
system:
|
|
||||||
mac: 00c2.4c00.bd15
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
period: 200
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Jul 21 18:24:52.413 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp churn logging actor
|
|
||||||
# lacp switchover suppress-flaps 500
|
|
||||||
# lacp collector-max-delay 100
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# lacp system mac 00c2.4c00.bd15
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# lacp period 200
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Jul 21 18:24:52.413 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp churn logging actor
|
|
||||||
# lacp switchover suppress-flaps 500
|
|
||||||
# lacp collector-max-delay 100
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# lacp system mac 00c2.4c00.bd15
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# lacp period 200
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Replace LACP configuration of listed interfaces with provided configuration
|
|
||||||
iosxr_lacp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether10
|
|
||||||
churn_logging: partner
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
period: 300
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Jul 21 18:50:21.929 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp churn logging partner
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# lacp system mac 00c2.4c00.bd15
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# lacp period 200
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# lacp period 300
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Jul 21 18:24:52.413 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp churn logging actor
|
|
||||||
# lacp switchover suppress-flaps 500
|
|
||||||
# lacp collector-max-delay 100
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# lacp system mac 00c2.4c00.bd15
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# lacp period 200
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# lacp period 200
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Override all interface LACP configuration with provided configuration
|
|
||||||
iosxr_lacp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether12
|
|
||||||
churn_logging: both
|
|
||||||
collector_max_delay: 100
|
|
||||||
switchover_suppress_flaps: 500
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
period: 300
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr(config-if)#do sh run int
|
|
||||||
# Sun Jul 21 19:32:36.115 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# lacp churn logging both
|
|
||||||
# lacp switchover suppress-flaps 500
|
|
||||||
# lacp collector-max-delay 100
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# lacp period 300
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Jul 21 18:24:52.413 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp churn logging actor
|
|
||||||
# lacp switchover suppress-flaps 500
|
|
||||||
# lacp collector-max-delay 100
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# lacp non-revertive
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# lacp period 200
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# lacp period 300
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Deleted LACP configurations of provided interfaces (Note - This won't delete the interface itself)
|
|
||||||
iosxr_lacp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether10
|
|
||||||
- name: Bundle-Ether11
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Jul 21 19:51:03.499 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['interface Bundle-Ether10', 'lacp churn logging partner', 'lacp period 150']
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.lacp_interfaces.lacp_interfaces import Lacp_interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',))]
|
|
||||||
module = AnsibleModule(argument_spec=Lacp_interfacesArgs.argument_spec, required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Lacp_interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,639 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_lag_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_lag_interfaces
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manages attributes of LAG/Ether-Bundle interfaces on IOS-XR devices.
|
|
||||||
description:
|
|
||||||
- This module manages the attributes of LAG/Ether-Bundle interfaces on IOS-XR devices.
|
|
||||||
notes:
|
|
||||||
- Tested against IOS-XR 6.1.3.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
author: Nilashish Chakraborty (@NilashishC)
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A provided Link Aggregation Group (LAG) configuration.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name/Identifier of the LAG/Ether-Bundle to configure.
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
members:
|
|
||||||
description:
|
|
||||||
- List of member interfaces for the LAG/Ether-Bundle.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
member:
|
|
||||||
description:
|
|
||||||
- Name of the member interface.
|
|
||||||
type: str
|
|
||||||
mode:
|
|
||||||
description:
|
|
||||||
- Specifies the mode of the operation for the member interface.
|
|
||||||
- Mode 'active' runs LACP in active mode.
|
|
||||||
- Mode 'on' does not run LACP over the port.
|
|
||||||
- Mode 'passive' runs LACP in passive mode over the port.
|
|
||||||
- Mode 'inherit' runs LACP as configured in the bundle.
|
|
||||||
choices: ['on', 'active', 'passive', 'inherit']
|
|
||||||
type: str
|
|
||||||
mode:
|
|
||||||
description:
|
|
||||||
- LAG mode.
|
|
||||||
- Mode 'active' runs LACP in active mode over the port.
|
|
||||||
- Mode 'on' does not run LACP over the port.
|
|
||||||
- Mode 'passive' runs LACP in passive mode over the port.
|
|
||||||
choices: ['on', 'active', 'passive']
|
|
||||||
type: str
|
|
||||||
links:
|
|
||||||
description:
|
|
||||||
- This dict contains configurable options related to LAG/Ether-Bundle links.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
max_active:
|
|
||||||
description:
|
|
||||||
- Specifies the limit on the number of links that can be active in the LAG/Ether-Bundle.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
min_active:
|
|
||||||
description:
|
|
||||||
- Specifies the minimum number of active links needed to bring up the LAG/Ether-Bundle.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
load_balancing_hash:
|
|
||||||
description:
|
|
||||||
- Specifies the hash function used for traffic forwarded over the LAG/Ether-Bundle.
|
|
||||||
- Option 'dst-ip' uses the destination IP as the hash function.
|
|
||||||
- Option 'src-ip' uses the source IP as the hash function.
|
|
||||||
type: str
|
|
||||||
choices: ['dst-ip', 'src-ip']
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
# Using merged
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#show run int
|
|
||||||
# Sun Jul 7 19:42:59.416 UTC
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description "GigabitEthernet - 1"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
- name: Merge provided configuration with device configuration
|
|
||||||
iosxr_lag_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether10
|
|
||||||
members:
|
|
||||||
- member: GigabitEthernet0/0/0/1
|
|
||||||
mode: inherit
|
|
||||||
- member: GigabitEthernet0/0/0/3
|
|
||||||
mode: inherit
|
|
||||||
mode: active
|
|
||||||
links:
|
|
||||||
max_active: 5
|
|
||||||
min_active: 2
|
|
||||||
load_balancing_hash: src-ip
|
|
||||||
|
|
||||||
- name: Bundle-Ether12
|
|
||||||
members:
|
|
||||||
- member: GigabitEthernet0/0/0/2
|
|
||||||
mode: passive
|
|
||||||
- member: GigabitEthernet0/0/0/4
|
|
||||||
mode: passive
|
|
||||||
load_balancing_hash: dst-ip
|
|
||||||
state: merged
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#show run int
|
|
||||||
# Sun Jul 7 20:51:17.685 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# bundle maximum-active links 5
|
|
||||||
# bundle minimum-active links 2
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# bundle load-balancing hash dst-ip
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# bundle id 12 mode passive
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# bundle id 12 mode passive
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sho run int
|
|
||||||
# Sun Jul 7 20:58:06.527 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# bundle maximum-active links 5
|
|
||||||
# bundle minimum-active links 2
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# bundle load-balancing hash dst-ip
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# bundle id 12 mode passive
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# bundle id 12 mode passive
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
- name: Replace device configuration of listed Bundles with provided configurations
|
|
||||||
iosxr_lag_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether12
|
|
||||||
members:
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
mode: passive
|
|
||||||
|
|
||||||
- name: Bundle-Ether11
|
|
||||||
members:
|
|
||||||
- name: GigabitEthernet0/0/0/4
|
|
||||||
load_balancing_hash: src-ip
|
|
||||||
state: replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run int
|
|
||||||
# Sun Jul 7 21:22:27.397 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# bundle maximum-active links 5
|
|
||||||
# bundle minimum-active links 2
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# lacp mode passive
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# bundle id 12 mode on
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# bundle id 11 mode on
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run int
|
|
||||||
# Sun Jul 7 21:22:27.397 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# bundle maximum-active links 5
|
|
||||||
# bundle minimum-active links 2
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# lacp mode passive
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# bundle id 12 mode on
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# bundle id 11 mode on
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Overrides all device configuration with provided configuration
|
|
||||||
iosxr_lag_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether10
|
|
||||||
members:
|
|
||||||
- member: GigabitEthernet0/0/0/1
|
|
||||||
mode: inherit
|
|
||||||
- member: GigabitEthernet0/0/0/2
|
|
||||||
mode: inherit
|
|
||||||
mode: active
|
|
||||||
load_balancing_hash: dst-ip
|
|
||||||
state: overridden
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run int
|
|
||||||
# Sun Jul 7 21:43:04.802 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash dst-ip
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run int
|
|
||||||
# Sun Jul 7 21:22:27.397 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# bundle maximum-active links 5
|
|
||||||
# bundle minimum-active links 2
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# lacp mode passive
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# bundle id 12 mode on
|
|
||||||
# !n
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# bundle id 11 mode on
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete attributes of given bundles and removes member interfaces from them (Note - This won't delete the bundles themselves)
|
|
||||||
iosxr_lag_interfaces:
|
|
||||||
config:
|
|
||||||
- name: Bundle-Ether10
|
|
||||||
- name: Bundle-Ether11
|
|
||||||
- name: Bundle-Ether12
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:iosxr01#sh run int
|
|
||||||
# Sun Jul 7 21:49:50.004 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether12
|
|
||||||
# !
|
|
||||||
# interface Loopback888
|
|
||||||
# description test for ansible
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# description 'GigabitEthernet - 1"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# description "GigabitEthernet - 2"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# description "GigabitEthernet - 3"
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# description "GigabitEthernet - 4"
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using deleted (without config)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Aug 18 19:49:51.908 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# lacp mode active
|
|
||||||
# bundle load-balancing hash src-ip
|
|
||||||
# bundle maximum-active links 10
|
|
||||||
# bundle minimum-active links 2
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# bundle load-balancing hash dst-ip
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# bundle id 10 mode inherit
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# bundle id 10 mode passive
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# bundle id 11 mode passive
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# bundle id 11 mode passive
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete attributes of all bundles and removes member interfaces from them (Note - This won't delete the bundles themselves)
|
|
||||||
iosxr_lag_interfaces:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run int
|
|
||||||
# Sun Aug 18 19:54:22.389 UTC
|
|
||||||
# interface Bundle-Ether10
|
|
||||||
# !
|
|
||||||
# interface Bundle-Ether11
|
|
||||||
# !
|
|
||||||
# interface MgmtEth0/0/CPU0/0
|
|
||||||
# ipv4 address 10.8.38.69 255.255.255.0
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/0
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/1
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/2
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/3
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
# interface GigabitEthernet0/0/0/4
|
|
||||||
# shutdown
|
|
||||||
# !
|
|
||||||
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['interface Bundle-Ether10', 'bundle minimum-active links 2', 'bundle load-balancing hash src-ip']
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.lag_interfaces.lag_interfaces import Lag_interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',))]
|
|
||||||
module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec, required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Lag_interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,376 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_lldp_global
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_lldp_global
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage Global Link Layer Discovery Protocol (LLDP) settings on IOS-XR devices.
|
|
||||||
description:
|
|
||||||
- This module manages Global Link Layer Discovery Protocol (LLDP) settings on IOS-XR devices.
|
|
||||||
notes:
|
|
||||||
- Tested against IOS-XR 6.1.3.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
author: Nilashish Chakraborty (@NilashishC)
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: The provided global LLDP configuration.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
holdtime:
|
|
||||||
description:
|
|
||||||
- Specifies the holdtime (in sec) to be sent in packets.
|
|
||||||
type: int
|
|
||||||
reinit:
|
|
||||||
description:
|
|
||||||
- Specifies the delay (in sec) for LLDP initialization on any interface.
|
|
||||||
type: int
|
|
||||||
subinterfaces:
|
|
||||||
description:
|
|
||||||
- Enable or disable LLDP over sub-interfaces.
|
|
||||||
type: bool
|
|
||||||
timer:
|
|
||||||
description:
|
|
||||||
- Specifies the rate at which LLDP packets are sent (in sec).
|
|
||||||
type: int
|
|
||||||
tlv_select:
|
|
||||||
description:
|
|
||||||
- Specifies the LLDP TLVs to enable or disable.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
management_address:
|
|
||||||
description:
|
|
||||||
- Enable or disable management address TLV.
|
|
||||||
type: bool
|
|
||||||
port_description:
|
|
||||||
description:
|
|
||||||
- Enable or disable port description TLV.
|
|
||||||
type: bool
|
|
||||||
system_capabilities:
|
|
||||||
description:
|
|
||||||
- Enable or disable system capabilities TLV.
|
|
||||||
type: bool
|
|
||||||
system_description:
|
|
||||||
description:
|
|
||||||
- Enable or disable system description TLV.
|
|
||||||
type: bool
|
|
||||||
system_name:
|
|
||||||
description:
|
|
||||||
- Enable or disable system name TLV.
|
|
||||||
type: bool
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
# Using merged
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before State
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run lldp
|
|
||||||
# Tue Aug 6 19:27:54.933 UTC
|
|
||||||
# % No such configuration item(s)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Merge provided LLDP configuration with the existing configuration
|
|
||||||
iosxr_lldp_global:
|
|
||||||
config:
|
|
||||||
holdtime: 100
|
|
||||||
reinit: 2
|
|
||||||
timer: 3000
|
|
||||||
subinterfaces: True
|
|
||||||
tlv_select:
|
|
||||||
management_address: False
|
|
||||||
system_description: False
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
# "before": {}
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "lldp subinterfaces enable",
|
|
||||||
# "lldp holdtime 100",
|
|
||||||
# "lldp reinit 2",
|
|
||||||
# "lldp tlv-select system-description disable",
|
|
||||||
# "lldp tlv-select management-address disable",
|
|
||||||
# "lldp timer 3000"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# "after": {
|
|
||||||
# "holdtime": 100,
|
|
||||||
# "reinit": 2,
|
|
||||||
# "subinterfaces": true,
|
|
||||||
# "timer": 3000,
|
|
||||||
# "tlv_select": {
|
|
||||||
# "management_address": false,
|
|
||||||
# "system_description": false
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run lldp
|
|
||||||
# Tue Aug 6 21:31:10.587 UTC
|
|
||||||
# lldp
|
|
||||||
# timer 3000
|
|
||||||
# reinit 2
|
|
||||||
# subinterfaces enable
|
|
||||||
# holdtime 100
|
|
||||||
# tlv-select
|
|
||||||
# management-address disable
|
|
||||||
# system-description disable
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before State
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run lldp
|
|
||||||
# Tue Aug 6 21:31:10.587 UTC
|
|
||||||
# lldp
|
|
||||||
# timer 3000
|
|
||||||
# reinit 2
|
|
||||||
# subinterfaces enable
|
|
||||||
# holdtime 100
|
|
||||||
# tlv-select
|
|
||||||
# management-address disable
|
|
||||||
# system-description disable
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Replace existing LLDP device configuration with provided configuration
|
|
||||||
iosxr_lldp_global:
|
|
||||||
config:
|
|
||||||
holdtime: 100
|
|
||||||
tlv_select:
|
|
||||||
port_description: False
|
|
||||||
system_description: True
|
|
||||||
management_description: True
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
# "before": {
|
|
||||||
# "holdtime": 100,
|
|
||||||
# "reinit": 2,
|
|
||||||
# "subinterfaces": true,
|
|
||||||
# "timer": 3000,
|
|
||||||
# "tlv_select": {
|
|
||||||
# "management_address": false,
|
|
||||||
# "system_description": false
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "no lldp reinit 2",
|
|
||||||
# "no lldp subinterfaces enable",
|
|
||||||
# "no lldp timer 3000",
|
|
||||||
# "no lldp tlv-select management-address disable",
|
|
||||||
# "no lldp tlv-select system-description disable",
|
|
||||||
# "lldp tlv-select port-description disable"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# "after": {
|
|
||||||
# "holdtime": 100,
|
|
||||||
# "tlv_select": {
|
|
||||||
# "port_description": false
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run lldp
|
|
||||||
# Tue Aug 6 21:53:08.407 UTC
|
|
||||||
# lldp
|
|
||||||
# holdtime 100
|
|
||||||
# tlv-select
|
|
||||||
# port-description disable
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run lldp
|
|
||||||
# Tue Aug 6 21:31:10.587 UTC
|
|
||||||
# lldp
|
|
||||||
# timer 3000
|
|
||||||
# reinit 2
|
|
||||||
# subinterfaces enable
|
|
||||||
# holdtime 100
|
|
||||||
# tlv-select
|
|
||||||
# management-address disable
|
|
||||||
# system-description disable
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Deleted existing LLDP configurations from the device
|
|
||||||
iosxr_lldp_global:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
# "before": {
|
|
||||||
# "holdtime": 100,
|
|
||||||
# "reinit": 2,
|
|
||||||
# "subinterfaces": true,
|
|
||||||
# "timer": 3000,
|
|
||||||
# "tlv_select": {
|
|
||||||
# "management_address": false,
|
|
||||||
# "system_description": false
|
|
||||||
# }
|
|
||||||
# },
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "no lldp holdtime 100",
|
|
||||||
# "no lldp reinit 2",
|
|
||||||
# "no lldp subinterfaces enable",
|
|
||||||
# "no lldp timer 3000",
|
|
||||||
# "no lldp tlv-select management-address disable",
|
|
||||||
# "no lldp tlv-select system-description disable"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# "after": {}
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -----------
|
|
||||||
# After state
|
|
||||||
# -----------
|
|
||||||
#
|
|
||||||
# RP/0/0/CPU0:an-iosxr#sh run lldp
|
|
||||||
# Tue Aug 6 21:38:31.187 UTC
|
|
||||||
# lldp
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: dict
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['lldp subinterfaces enable', 'lldp holdtime 100', 'no lldp tlv-select management-address disable']
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lldp_global.lldp_global import Lldp_globalArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.lldp_global.lldp_global import Lldp_global
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',))]
|
|
||||||
module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec, required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Lldp_global(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,588 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_lldp_interfaces
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_lldp_interfaces
|
|
||||||
version_added: 2.9
|
|
||||||
short_description: Manage Link Layer Discovery Protocol (LLDP) attributes of interfaces on IOS-XR devices.
|
|
||||||
description:
|
|
||||||
- This module manages Link Layer Discovery Protocol (LLDP) attributes of interfaces on IOS-XR devices.
|
|
||||||
notes:
|
|
||||||
- Tested against IOS-XR 6.1.3.
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
author: Nilashish Chakraborty (@nilashishc)
|
|
||||||
options:
|
|
||||||
config:
|
|
||||||
description: A dictionary of LLDP interfaces options.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name/Identifier of the interface or Ether-Bundle.
|
|
||||||
type: str
|
|
||||||
destination:
|
|
||||||
description:
|
|
||||||
- Specifies LLDP destination configuration on the interface.
|
|
||||||
suboptions:
|
|
||||||
mac_address:
|
|
||||||
description:
|
|
||||||
- Specifies the LLDP destination mac address on the interface.
|
|
||||||
type: str
|
|
||||||
choices: ['ieee-nearest-bridge', 'ieee-nearest-non-tmpr-bridge']
|
|
||||||
type: dict
|
|
||||||
receive:
|
|
||||||
description:
|
|
||||||
- Enable/disable LLDP RX on an interface.
|
|
||||||
type: bool
|
|
||||||
transmit:
|
|
||||||
description:
|
|
||||||
- Enable/disable LLDP TX on an interface.
|
|
||||||
type: bool
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state of the configuration after module completion.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
# Using merged
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# Before state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 12:40:23.104 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Merge provided configuration with running configuration
|
|
||||||
iosxr_lldp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
destination:
|
|
||||||
mac_address: ieee-nearest-non-tmpr-bridge
|
|
||||||
transmit: False
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/2
|
|
||||||
destination:
|
|
||||||
mac_address: ieee-nearest-bridge
|
|
||||||
receive: False
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "before": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "name": "GigabitEthernet0/0/0/1"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "name": "GigabitEthernet0/0/0/2"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "interface GigabitEthernet0/0/0/2",
|
|
||||||
# "lldp destination mac-address ieee-nearest-non-tmpr-bridge",
|
|
||||||
# "lldp transmit disable",
|
|
||||||
# "interface GigabitEthernet0/0/0/1",
|
|
||||||
# "lldp receive disable",
|
|
||||||
# "lldp destination mac-address ieee-nearest-bridge"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# "after": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/1",
|
|
||||||
# "receive": false
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-non-tmpr-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/2",
|
|
||||||
# "transmit": false
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 12:49:51.517 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# lldp
|
|
||||||
# receive disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# lldp
|
|
||||||
# transmit disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-non-tmpr-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using replaced
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 12:49:51.517 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# lldp
|
|
||||||
# receive disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# lldp
|
|
||||||
# transmit disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-non-tmpr-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Replace existing LLDP configurations of specified interfaces with provided configuration
|
|
||||||
iosxr_lldp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
destination:
|
|
||||||
mac_address: ieee-nearest-non-tmpr-bridge
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
# "before": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/1",
|
|
||||||
# "receive": false
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-non-tmpr-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/2",
|
|
||||||
# "transmit": false
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "interface GigabitEthernet0/0/0/1",
|
|
||||||
# "no lldp receive disable",
|
|
||||||
# "lldp destination mac-address ieee-nearest-non-tmpr-bridge"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "after": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-non-tmpr-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/1"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-non-tmpr-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/2",
|
|
||||||
# "transmit": false
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 13:02:57.062 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# lldp
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-non-tmpr-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# lldp
|
|
||||||
# transmit disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-non-tmpr-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using overridden
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 13:15:40.465 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# lldp
|
|
||||||
# receive disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# lldp
|
|
||||||
# transmit disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-non-tmpr-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Override the LLDP configurations of all the interfaces with provided configurations
|
|
||||||
iosxr_lldp_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
transmit: False
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "before": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/1",
|
|
||||||
# "receive": false
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-non-tmpr-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/2",
|
|
||||||
# "transmit": false
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "interface GigabitEthernet0/0/0/2",
|
|
||||||
# "no lldp destination mac-address ieee-nearest-non-tmpr-bridge",
|
|
||||||
# "no lldp transmit disable",
|
|
||||||
# "interface GigabitEthernet0/0/0/1",
|
|
||||||
# "no lldp destination mac-address ieee-nearest-bridge",
|
|
||||||
# "no lldp receive disable",
|
|
||||||
# "lldp transmit disable"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "after": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "name": "GigabitEthernet0/0/0/1",
|
|
||||||
# "transmit": false
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "name": "GigabitEthernet0/0/0/2"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 13:22:25.604 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# lldp
|
|
||||||
# transmit disable
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Using deleted
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# -------------
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 13:26:21.498 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# lldp
|
|
||||||
# receive disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# lldp
|
|
||||||
# transmit disable
|
|
||||||
# destination mac-address
|
|
||||||
# ieee-nearest-non-tmpr-bridge
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
- name: Delete LLDP configurations of all interfaces (Note - This won't delete the interfaces themselves)
|
|
||||||
iosxr_lldp_interfaces:
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------------------
|
|
||||||
# Module Execution Result
|
|
||||||
# ------------------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "before": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/1",
|
|
||||||
# "receive": false
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "destination": {
|
|
||||||
# "mac_address": "ieee-nearest-non-tmpr-bridge"
|
|
||||||
# },
|
|
||||||
# "name": "GigabitEthernet0/0/0/2",
|
|
||||||
# "transmit": false
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "commands": [
|
|
||||||
# "interface GigabitEthernet0/0/0/1",
|
|
||||||
# "no lldp destination mac-address ieee-nearest-bridge",
|
|
||||||
# "no lldp receive disable",
|
|
||||||
# "interface GigabitEthernet0/0/0/2",
|
|
||||||
# "no lldp destination mac-address ieee-nearest-non-tmpr-bridge",
|
|
||||||
# "no lldp transmit disable"
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# "after": [
|
|
||||||
# {
|
|
||||||
# "name": "TenGigE0/0/0/0"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "name": "GigabitEthernet0/0/0/1"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "name": "GigabitEthernet0/0/0/2"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# ------------
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# RP/0/RP0/CPU0:ios#sh run int
|
|
||||||
# Mon Aug 12 13:30:14.618 UTC
|
|
||||||
# interface TenGigE0/0/0/0
|
|
||||||
# ipv4 address 192.0.2.11 255.255.255.192
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/1
|
|
||||||
# !
|
|
||||||
# interface preconfigure GigabitEthernet0/0/0/2
|
|
||||||
# !
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration as structured data prior to module invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The configuration as structured data after module completion.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: ['interface GigabitEthernet0/0/0/1', 'lldp destination mac-address ieee-nearest-non-tmpr-bridge', 'no lldp transmit disable']
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.lldp_interfaces.lldp_interfaces import Lldp_interfaces
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [('state', 'merged', ('config',)),
|
|
||||||
('state', 'replaced', ('config',)),
|
|
||||||
('state', 'overridden', ('config',))]
|
|
||||||
module = AnsibleModule(argument_spec=Lldp_interfacesArgs.argument_spec, required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
result = Lldp_interfaces(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,728 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# (c) 2017, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_logging
|
|
||||||
version_added: "2.4"
|
|
||||||
author:
|
|
||||||
- "Trishna Guha (@trishnaguha)"
|
|
||||||
- "Kedar Kekan (@kedarX)"
|
|
||||||
short_description: Configuration management of system logging services on network devices
|
|
||||||
description:
|
|
||||||
- This module provides declarative management configuration of system logging (syslog)
|
|
||||||
on Cisco IOS XR devices.
|
|
||||||
requirements:
|
|
||||||
- ncclient >= 0.5.3 when using netconf
|
|
||||||
- lxml >= 4.1.1 when using netconf
|
|
||||||
notes:
|
|
||||||
- This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- Tested against IOS XRv 6.1.3
|
|
||||||
options:
|
|
||||||
dest:
|
|
||||||
description:
|
|
||||||
- Destination for system logging (syslog) messages.
|
|
||||||
choices: ['host', 'console', 'monitor', 'buffered', 'file']
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- When C(dest) = I(file) name indicates file-name
|
|
||||||
- When C(dest) = I(host) name indicates the host-name or ip-address of syslog server.
|
|
||||||
vrf:
|
|
||||||
description:
|
|
||||||
- vrf name when syslog server is configured, C(dest) = C(host)
|
|
||||||
default: default
|
|
||||||
version_added: 2.5
|
|
||||||
size:
|
|
||||||
description:
|
|
||||||
- Size of buffer when C(dest) = C(buffered). The acceptable value is in the range I(307200 to 125000000 bytes). Default 307200
|
|
||||||
- Size of file when C(dest) = C(file). The acceptable value is in the range I(1 to 2097152)KB. Default 2 GB
|
|
||||||
facility:
|
|
||||||
description:
|
|
||||||
- To configure the type of syslog facility in which system logging (syslog) messages are sent to syslog servers
|
|
||||||
Optional config for C(dest) = C(host)
|
|
||||||
default: local7
|
|
||||||
hostnameprefix:
|
|
||||||
description:
|
|
||||||
- To append a hostname prefix to system logging (syslog) messages logged to syslog servers.
|
|
||||||
Optional config for C(dest) = C(host)
|
|
||||||
version_added: 2.5
|
|
||||||
level:
|
|
||||||
description:
|
|
||||||
- Specifies the severity level for the logging.
|
|
||||||
default: debugging
|
|
||||||
aliases: ['severity']
|
|
||||||
aggregate:
|
|
||||||
description: List of syslog logging configuration definitions.
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Existential state of the logging configuration on the node.
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent']
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: configure logging for syslog server host
|
|
||||||
iosxr_logging:
|
|
||||||
dest: host
|
|
||||||
name: 10.10.10.1
|
|
||||||
level: critical
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: add hostnameprefix configuration
|
|
||||||
iosxr_logging:
|
|
||||||
hostnameprefix: host1
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: add facility configuration
|
|
||||||
iosxr_logging:
|
|
||||||
facility: local1
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: configure console logging level
|
|
||||||
iosxr_logging:
|
|
||||||
dest: console
|
|
||||||
level: debugging
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: configure monitor logging level
|
|
||||||
iosxr_logging:
|
|
||||||
dest: monitor
|
|
||||||
level: errors
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: configure syslog to a file
|
|
||||||
iosxr_logging:
|
|
||||||
dest: file
|
|
||||||
name: file_name
|
|
||||||
size: 2048
|
|
||||||
level: errors
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: configure buffered logging with size
|
|
||||||
iosxr_logging:
|
|
||||||
dest: buffered
|
|
||||||
size: 5100000
|
|
||||||
|
|
||||||
- name: Configure logging using aggregate
|
|
||||||
iosxr_logging:
|
|
||||||
aggregate:
|
|
||||||
- { dest: console, level: warning }
|
|
||||||
- { dest: buffered, size: 4800000 }
|
|
||||||
- { dest: file, name: file3, size: 2048}
|
|
||||||
- { dest: host, name: host3, level: critical}
|
|
||||||
|
|
||||||
- name: Delete logging using aggregate
|
|
||||||
iosxr_logging:
|
|
||||||
aggregate:
|
|
||||||
- { dest: console, level: warning }
|
|
||||||
- { dest: buffered, size: 4800000 }
|
|
||||||
- { dest: file, name: file3, size: 2048}
|
|
||||||
- { dest: host, name: host3, level: critical}
|
|
||||||
state: absent
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
description: The list of configuration mode commands to send to the device
|
|
||||||
returned: always (empty list when no commands to send)
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
- logging 10.10.10.1 vrf default severity debugging
|
|
||||||
- logging facility local7
|
|
||||||
- logging hostnameprefix host1
|
|
||||||
- logging console critical
|
|
||||||
- logging buffered 2097153
|
|
||||||
- logging buffered warnings
|
|
||||||
- logging monitor errors
|
|
||||||
- logging file log_file maxfilesize 1024 severity info
|
|
||||||
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">
|
|
||||||
<syslog xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-syslog-cfg">
|
|
||||||
<files>
|
|
||||||
<file xc:operation="delete">
|
|
||||||
<file-name>file1</file-name>
|
|
||||||
<file-log-attributes>
|
|
||||||
<max-file-size>2097152</max-file-size>
|
|
||||||
<severity>2</severity>
|
|
||||||
</file-log-attributes>
|
|
||||||
</file>
|
|
||||||
</files>
|
|
||||||
</syslog>
|
|
||||||
</config>'
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import collections
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, build_xml
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, etree_findall
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import is_netconf, is_cliconf, etree_find
|
|
||||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
|
||||||
|
|
||||||
|
|
||||||
severity_level = {'emergency': '0',
|
|
||||||
'alert': '1',
|
|
||||||
'critical': '2',
|
|
||||||
'error': '3',
|
|
||||||
'warning': '4',
|
|
||||||
'notice': '5',
|
|
||||||
'info': '6',
|
|
||||||
'debug': '7',
|
|
||||||
'disable': '15'}
|
|
||||||
|
|
||||||
severity_transpose = {'emergencies': 'emergency',
|
|
||||||
'alerts': 'alert',
|
|
||||||
'critical': 'critical',
|
|
||||||
'errors': 'error',
|
|
||||||
'warning': 'warning',
|
|
||||||
'notifications': 'notice',
|
|
||||||
'informational': 'info',
|
|
||||||
'debugging': 'debug'}
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigBase(object):
|
|
||||||
def __init__(self, module):
|
|
||||||
self._flag = None
|
|
||||||
self._module = module
|
|
||||||
self._result = {'changed': False, 'warnings': []}
|
|
||||||
self._want = list()
|
|
||||||
self._have = list()
|
|
||||||
|
|
||||||
def validate_size(self, value, type=None):
|
|
||||||
if value:
|
|
||||||
if type == 'buffer':
|
|
||||||
if value and not int(307200) <= value <= int(125000000):
|
|
||||||
self._module.fail_json(msg='buffer size must be between 307200 and 125000000')
|
|
||||||
elif type == 'file':
|
|
||||||
if value and not int(1) <= value <= int(2097152):
|
|
||||||
self._module.fail_json(msg='file size must be between 1 and 2097152')
|
|
||||||
return value
|
|
||||||
|
|
||||||
def map_params_to_obj(self, required_if=None):
|
|
||||||
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]
|
|
||||||
|
|
||||||
d = item.copy()
|
|
||||||
|
|
||||||
if d['dest'] not in ('host', 'file'):
|
|
||||||
d['name'] = None
|
|
||||||
|
|
||||||
if d['dest'] == 'buffered':
|
|
||||||
if d['size'] is not None:
|
|
||||||
d['size'] = str(self.validate_size(d['size'], 'buffer'))
|
|
||||||
else:
|
|
||||||
d['size'] = str(307200)
|
|
||||||
elif d['dest'] == 'file':
|
|
||||||
if d['size'] is not None:
|
|
||||||
d['size'] = str(self.validate_size(d['size'], 'file'))
|
|
||||||
else:
|
|
||||||
d['size'] = str(2097152)
|
|
||||||
else:
|
|
||||||
d['size'] = None
|
|
||||||
|
|
||||||
if self._flag == 'NC':
|
|
||||||
d['level'] = severity_transpose[d['level']]
|
|
||||||
|
|
||||||
self._want.append(d)
|
|
||||||
|
|
||||||
else:
|
|
||||||
params = self._module.params
|
|
||||||
if params['dest'] not in ('host', 'file'):
|
|
||||||
params['name'] = None
|
|
||||||
|
|
||||||
if params['dest'] == 'buffered':
|
|
||||||
if params['size'] is not None:
|
|
||||||
params['size'] = str(self.validate_size(params['size'], 'buffer'))
|
|
||||||
else:
|
|
||||||
params['size'] = str(307200)
|
|
||||||
elif params['dest'] == 'file':
|
|
||||||
if params['size'] is not None:
|
|
||||||
params['size'] = str(self.validate_size(params['size'], 'file'))
|
|
||||||
else:
|
|
||||||
params['size'] = str(2097152)
|
|
||||||
else:
|
|
||||||
params['size'] = None
|
|
||||||
|
|
||||||
if self._flag == 'NC':
|
|
||||||
params['level'] = severity_transpose[params['level']]
|
|
||||||
|
|
||||||
self._want.append({
|
|
||||||
'dest': params['dest'],
|
|
||||||
'name': params['name'],
|
|
||||||
'vrf': params['vrf'],
|
|
||||||
'size': params['size'],
|
|
||||||
'facility': params['facility'],
|
|
||||||
'level': params['level'],
|
|
||||||
'hostnameprefix': params['hostnameprefix'],
|
|
||||||
'state': params['state']
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class CliConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(CliConfiguration, self).__init__(module)
|
|
||||||
self._file_list = set()
|
|
||||||
self._host_list = set()
|
|
||||||
|
|
||||||
def map_obj_to_commands(self):
|
|
||||||
commands = list()
|
|
||||||
for want_item in self._want:
|
|
||||||
dest = want_item['dest']
|
|
||||||
name = want_item['name']
|
|
||||||
size = want_item['size']
|
|
||||||
facility = want_item['facility']
|
|
||||||
level = want_item['level']
|
|
||||||
vrf = want_item['vrf']
|
|
||||||
hostnameprefix = want_item['hostnameprefix']
|
|
||||||
state = want_item['state']
|
|
||||||
del want_item['state']
|
|
||||||
|
|
||||||
have_size = None
|
|
||||||
have_console_level = None
|
|
||||||
have_monitor_level = None
|
|
||||||
have_prefix = None
|
|
||||||
have_facility = None
|
|
||||||
|
|
||||||
for item in self._have:
|
|
||||||
if item['dest'] == 'buffered':
|
|
||||||
have_size = item['size']
|
|
||||||
if item['dest'] == 'console':
|
|
||||||
have_console_level = item['level']
|
|
||||||
if item['dest'] == 'monitor':
|
|
||||||
have_monitor_level = item['level']
|
|
||||||
if item['dest'] is None and item['hostnameprefix'] is not None:
|
|
||||||
have_prefix = item['hostnameprefix']
|
|
||||||
if item['dest'] is None and item['hostnameprefix'] is None and item['facility'] is not None:
|
|
||||||
have_facility = item['facility']
|
|
||||||
|
|
||||||
if state == 'absent':
|
|
||||||
if dest == 'host' and name in self._host_list:
|
|
||||||
commands.append('no logging {0} vrf {1}'.format(name, vrf))
|
|
||||||
elif dest == 'file' and name in self._file_list:
|
|
||||||
commands.append('no logging file {0}'.format(name))
|
|
||||||
elif dest == 'console' and have_console_level is not None:
|
|
||||||
commands.append('no logging {0}'.format(dest))
|
|
||||||
elif dest == 'monitor' and have_monitor_level:
|
|
||||||
commands.append('no logging {0}'.format(dest))
|
|
||||||
elif dest == 'buffered' and have_size:
|
|
||||||
commands.append('no logging {0}'.format(dest))
|
|
||||||
|
|
||||||
if dest is None and hostnameprefix is not None and have_prefix == hostnameprefix:
|
|
||||||
commands.append('no logging hostnameprefix {0}'.format(hostnameprefix))
|
|
||||||
if dest is None and facility is not None and have_facility == facility:
|
|
||||||
commands.append('no logging facility {0}'.format(facility))
|
|
||||||
|
|
||||||
if state == 'present':
|
|
||||||
if dest == 'host' and name not in self._host_list:
|
|
||||||
if level == 'errors' or level == 'informational':
|
|
||||||
level = severity_transpose[level]
|
|
||||||
commands.append('logging {0} vrf {1} severity {2}'.format(name, vrf, level))
|
|
||||||
elif dest == 'file' and name not in self._file_list:
|
|
||||||
if level == 'errors' or level == 'informational':
|
|
||||||
level = severity_transpose[level]
|
|
||||||
commands.append('logging file {0} maxfilesize {1} severity {2}'.format(name, size, level))
|
|
||||||
elif dest == 'buffered' and (have_size is None or (have_size is not None and size != have_size)):
|
|
||||||
commands.append('logging buffered {0}'.format(size))
|
|
||||||
elif dest == 'console' and (have_console_level is None or
|
|
||||||
(have_console_level is not None and have_console_level != level)):
|
|
||||||
commands.append('logging console {0}'.format(level))
|
|
||||||
elif dest == 'monitor' and (have_monitor_level is None or
|
|
||||||
(have_monitor_level is not None and have_monitor_level != level)):
|
|
||||||
commands.append('logging monitor {0}'.format(level))
|
|
||||||
|
|
||||||
if dest is None and hostnameprefix is not None and (have_prefix is None or
|
|
||||||
(have_prefix is not None and hostnameprefix != have_prefix)):
|
|
||||||
commands.append('logging hostnameprefix {0}'.format(hostnameprefix))
|
|
||||||
if dest is None and hostnameprefix is None and facility != have_facility:
|
|
||||||
commands.append('logging facility {0}'.format(facility))
|
|
||||||
|
|
||||||
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 parse_facility(self, line):
|
|
||||||
match = re.search(r'logging facility (\S+)', line, re.M)
|
|
||||||
facility = None
|
|
||||||
if match:
|
|
||||||
facility = match.group(1)
|
|
||||||
|
|
||||||
return facility
|
|
||||||
|
|
||||||
def parse_size(self, line, dest):
|
|
||||||
size = None
|
|
||||||
|
|
||||||
if dest == 'buffered':
|
|
||||||
match = re.search(r'logging buffered (\S+)', line, re.M)
|
|
||||||
if match:
|
|
||||||
try:
|
|
||||||
int_size = int(match.group(1))
|
|
||||||
except ValueError:
|
|
||||||
int_size = None
|
|
||||||
|
|
||||||
if int_size is not None:
|
|
||||||
if isinstance(int_size, int):
|
|
||||||
size = str(match.group(1))
|
|
||||||
return size
|
|
||||||
|
|
||||||
def parse_hostnameprefix(self, line):
|
|
||||||
prefix = None
|
|
||||||
match = re.search(r'logging hostnameprefix (\S+)', line, re.M)
|
|
||||||
if match:
|
|
||||||
prefix = match.group(1)
|
|
||||||
return prefix
|
|
||||||
|
|
||||||
def parse_name(self, line, dest):
|
|
||||||
name = None
|
|
||||||
if dest == 'file':
|
|
||||||
match = re.search(r'logging file (\S+)', line, re.M)
|
|
||||||
if match:
|
|
||||||
name = match.group(1)
|
|
||||||
elif dest == 'host':
|
|
||||||
match = re.search(r'logging (\S+)', line, re.M)
|
|
||||||
if match:
|
|
||||||
name = match.group(1)
|
|
||||||
|
|
||||||
return name
|
|
||||||
|
|
||||||
def parse_level(self, line, dest):
|
|
||||||
level_group = ('emergencies', 'alerts', 'critical', 'errors', 'warning',
|
|
||||||
'notifications', 'informational', 'debugging')
|
|
||||||
|
|
||||||
level = None
|
|
||||||
match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M)
|
|
||||||
if match:
|
|
||||||
if match.group(1) in level_group:
|
|
||||||
level = match.group(1)
|
|
||||||
|
|
||||||
return level
|
|
||||||
|
|
||||||
def parse_dest(self, line, group):
|
|
||||||
dest_group = ('console', 'monitor', 'buffered', 'file')
|
|
||||||
dest = None
|
|
||||||
if group in dest_group:
|
|
||||||
dest = group
|
|
||||||
elif 'vrf' in line:
|
|
||||||
dest = 'host'
|
|
||||||
|
|
||||||
return dest
|
|
||||||
|
|
||||||
def parse_vrf(self, line, dest):
|
|
||||||
vrf = None
|
|
||||||
if dest == 'host':
|
|
||||||
match = re.search(r'logging (\S+) vrf (\S+)', line, re.M)
|
|
||||||
if match:
|
|
||||||
vrf = match.group(2)
|
|
||||||
return vrf
|
|
||||||
|
|
||||||
def map_config_to_obj(self):
|
|
||||||
data = get_config(self._module, config_filter='logging')
|
|
||||||
lines = data.split("\n")
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
match = re.search(r'logging (\S+)', line, re.M)
|
|
||||||
if match:
|
|
||||||
dest = self.parse_dest(line, match.group(1))
|
|
||||||
name = self.parse_name(line, dest)
|
|
||||||
if dest == 'host' and name is not None:
|
|
||||||
self._host_list.add(name)
|
|
||||||
if dest == 'file' and name is not None:
|
|
||||||
self._file_list.add(name)
|
|
||||||
|
|
||||||
self._have.append({
|
|
||||||
'dest': dest,
|
|
||||||
'name': name,
|
|
||||||
'size': self.parse_size(line, dest),
|
|
||||||
'facility': self.parse_facility(line),
|
|
||||||
'level': self.parse_level(line, dest),
|
|
||||||
'vrf': self.parse_vrf(line, dest),
|
|
||||||
'hostnameprefix': self.parse_hostnameprefix(line),
|
|
||||||
})
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_config_to_obj()
|
|
||||||
self.map_obj_to_commands()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
class NCConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(NCConfiguration, self).__init__(module)
|
|
||||||
self._flag = 'NC'
|
|
||||||
self._log_file_meta = collections.OrderedDict()
|
|
||||||
self._log_host_meta = collections.OrderedDict()
|
|
||||||
self._log_console_meta = collections.OrderedDict()
|
|
||||||
self._log_monitor_meta = collections.OrderedDict()
|
|
||||||
self._log_buffered_size_meta = collections.OrderedDict()
|
|
||||||
self._log_buffered_level_meta = collections.OrderedDict()
|
|
||||||
self._log_facility_meta = collections.OrderedDict()
|
|
||||||
self._log_prefix_meta = collections.OrderedDict()
|
|
||||||
|
|
||||||
def map_obj_to_xml_rpc(self):
|
|
||||||
self._log_file_meta.update([
|
|
||||||
('files', {'xpath': 'syslog/files', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('file', {'xpath': 'syslog/files/file', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:name', {'xpath': 'syslog/files/file/file-name', 'operation': 'edit'}),
|
|
||||||
('file-attrib', {'xpath': 'syslog/files/file/file-log-attributes', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:size', {'xpath': 'syslog/files/file/file-log-attributes/max-file-size', 'operation': 'edit'}),
|
|
||||||
('a:level', {'xpath': 'syslog/files/file/file-log-attributes/severity', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_host_meta.update([
|
|
||||||
('host-server', {'xpath': 'syslog/host-server', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrfs', {'xpath': 'syslog/host-server/vrfs', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrf', {'xpath': 'syslog/host-server/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:vrf', {'xpath': 'syslog/host-server/vrfs/vrf/vrf-name', 'operation': 'edit'}),
|
|
||||||
('ipv4s', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('ipv4', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:name', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4/address', 'operation': 'edit'}),
|
|
||||||
('ipv4-sev', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4/ipv4-severity-port', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:level', {'xpath': 'syslog/host-server/vrfs/vrf/ipv4s/ipv4/ipv4-severity-port/severity', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_console_meta.update([
|
|
||||||
('a:enable-console', {'xpath': 'syslog/enable-console-logging', 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('console', {'xpath': 'syslog/console-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:console-level', {'xpath': 'syslog/console-logging/logging-level', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_monitor_meta.update([
|
|
||||||
('monitor', {'xpath': 'syslog/monitor-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:monitor-level', {'xpath': 'syslog/monitor-logging/logging-level', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_buffered_size_meta.update([
|
|
||||||
('buffered', {'xpath': 'syslog/buffered-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:size', {'xpath': 'syslog/buffered-logging/buffer-size', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_buffered_level_meta.update([
|
|
||||||
('buffered', {'xpath': 'syslog/buffered-logging', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:level', {'xpath': 'syslog/buffered-logging/logging-level', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_facility_meta.update([
|
|
||||||
('facility', {'xpath': 'syslog/logging-facilities', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:facility', {'xpath': 'syslog/logging-facilities/facility-level', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
self._log_prefix_meta.update([
|
|
||||||
('a:hostnameprefix', {'xpath': 'syslog/host-name-prefix', 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
])
|
|
||||||
|
|
||||||
state = self._module.params['state']
|
|
||||||
|
|
||||||
_get_filter = build_xml('syslog', opcode="filter")
|
|
||||||
running = get_config(self._module, source='running', config_filter=_get_filter)
|
|
||||||
|
|
||||||
file_ele = etree_findall(running, 'file')
|
|
||||||
file_list = list()
|
|
||||||
if len(file_ele):
|
|
||||||
for file in file_ele:
|
|
||||||
file_name = etree_find(file, 'file-name')
|
|
||||||
file_list.append(file_name.text if file_name is not None else None)
|
|
||||||
vrf_ele = etree_findall(running, 'vrf')
|
|
||||||
host_list = list()
|
|
||||||
for vrf in vrf_ele:
|
|
||||||
host_ele = etree_findall(vrf, 'ipv4')
|
|
||||||
for host in host_ele:
|
|
||||||
host_name = etree_find(host, 'address')
|
|
||||||
host_list.append(host_name.text if host_name is not None else None)
|
|
||||||
|
|
||||||
console_ele = etree_find(running, 'console-logging')
|
|
||||||
console_level = etree_find(console_ele, 'logging-level') if console_ele is not None else None
|
|
||||||
have_console = console_level.text if console_level is not None else None
|
|
||||||
|
|
||||||
monitor_ele = etree_find(running, 'monitor-logging')
|
|
||||||
monitor_level = etree_find(monitor_ele, 'logging-level') if monitor_ele is not None else None
|
|
||||||
have_monitor = monitor_level.text if monitor_level is not None else None
|
|
||||||
|
|
||||||
buffered_ele = etree_find(running, 'buffered-logging')
|
|
||||||
buffered_size = etree_find(buffered_ele, 'buffer-size') if buffered_ele is not None else None
|
|
||||||
have_buffered = buffered_size.text if buffered_size is not None else None
|
|
||||||
|
|
||||||
facility_ele = etree_find(running, 'logging-facilities')
|
|
||||||
facility_level = etree_find(facility_ele, 'facility-level') if facility_ele is not None else None
|
|
||||||
have_facility = facility_level.text if facility_level is not None else None
|
|
||||||
|
|
||||||
prefix_ele = etree_find(running, 'host-name-prefix')
|
|
||||||
have_prefix = prefix_ele.text if prefix_ele is not None else None
|
|
||||||
|
|
||||||
file_params = list()
|
|
||||||
host_params = list()
|
|
||||||
console_params = dict()
|
|
||||||
monitor_params = dict()
|
|
||||||
buffered_params = dict()
|
|
||||||
facility_params = dict()
|
|
||||||
prefix_params = dict()
|
|
||||||
|
|
||||||
opcode = None
|
|
||||||
if state == 'absent':
|
|
||||||
opcode = "delete"
|
|
||||||
for item in self._want:
|
|
||||||
if item['dest'] == 'file' and item['name'] in file_list:
|
|
||||||
item['level'] = severity_level[item['level']]
|
|
||||||
file_params.append(item)
|
|
||||||
elif item['dest'] == 'host' and item['name'] in host_list:
|
|
||||||
item['level'] = severity_level[item['level']]
|
|
||||||
host_params.append(item)
|
|
||||||
elif item['dest'] == 'console' and have_console:
|
|
||||||
console_params.update({'console-level': item['level']})
|
|
||||||
elif item['dest'] == 'monitor' and have_monitor:
|
|
||||||
monitor_params.update({'monitor-level': item['level']})
|
|
||||||
elif item['dest'] == 'buffered' and have_buffered:
|
|
||||||
buffered_params['size'] = str(item['size']) if item['size'] else None
|
|
||||||
buffered_params['level'] = item['level'] if item['level'] else None
|
|
||||||
elif item['dest'] is None and item['hostnameprefix'] is None and \
|
|
||||||
item['facility'] is not None and have_facility:
|
|
||||||
facility_params.update({'facility': item['facility']})
|
|
||||||
elif item['dest'] is None and item['hostnameprefix'] is not None and have_prefix:
|
|
||||||
prefix_params.update({'hostnameprefix': item['hostnameprefix']})
|
|
||||||
elif state == 'present':
|
|
||||||
opcode = 'merge'
|
|
||||||
for item in self._want:
|
|
||||||
if item['dest'] == 'file':
|
|
||||||
item['level'] = severity_level[item['level']]
|
|
||||||
file_params.append(item)
|
|
||||||
elif item['dest'] == 'host':
|
|
||||||
item['level'] = severity_level[item['level']]
|
|
||||||
host_params.append(item)
|
|
||||||
elif item['dest'] == 'console':
|
|
||||||
console_params.update({'console-level': item['level']})
|
|
||||||
elif item['dest'] == 'monitor':
|
|
||||||
monitor_params.update({'monitor-level': item['level']})
|
|
||||||
elif item['dest'] == 'buffered':
|
|
||||||
buffered_params['size'] = str(item['size']) if item['size'] else None
|
|
||||||
buffered_params['level'] = item['level'] if item['level'] else None
|
|
||||||
elif item['dest'] is None and item['hostnameprefix'] is None and \
|
|
||||||
item['facility'] is not None:
|
|
||||||
facility_params.update({'facility': item['facility']})
|
|
||||||
elif item['dest'] is None and item['hostnameprefix'] is not None:
|
|
||||||
prefix_params.update({'hostnameprefix': item['hostnameprefix']})
|
|
||||||
|
|
||||||
self._result['xml'] = []
|
|
||||||
_edit_filter_list = list()
|
|
||||||
if opcode:
|
|
||||||
if len(file_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_file_meta,
|
|
||||||
params=file_params, opcode=opcode))
|
|
||||||
if len(host_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_host_meta,
|
|
||||||
params=host_params, opcode=opcode))
|
|
||||||
if len(console_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_console_meta,
|
|
||||||
params=console_params, opcode=opcode))
|
|
||||||
if len(monitor_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_monitor_meta,
|
|
||||||
params=monitor_params, opcode=opcode))
|
|
||||||
if len(buffered_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_buffered_size_meta,
|
|
||||||
params=buffered_params, opcode=opcode))
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_buffered_level_meta,
|
|
||||||
params=buffered_params, opcode=opcode))
|
|
||||||
if len(facility_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_facility_meta,
|
|
||||||
params=facility_params, opcode=opcode))
|
|
||||||
if len(prefix_params):
|
|
||||||
_edit_filter_list.append(build_xml('syslog', xmap=self._log_prefix_meta,
|
|
||||||
params=prefix_params, opcode=opcode))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
if self._module._diff:
|
|
||||||
self._result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
self._result['xml'] = _edit_filter_list
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_obj_to_xml_rpc()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
""" main entry point for module execution
|
|
||||||
"""
|
|
||||||
element_spec = dict(
|
|
||||||
dest=dict(type='str', choices=['host', 'console', 'monitor', 'buffered', 'file']),
|
|
||||||
name=dict(type='str'),
|
|
||||||
size=dict(type='int'),
|
|
||||||
vrf=dict(type='str', default='default'),
|
|
||||||
facility=dict(type='str', default='local7'),
|
|
||||||
hostnameprefix=dict(type='str'),
|
|
||||||
level=dict(type='str', default='informational', aliases=['severity'],
|
|
||||||
choices=['emergencies', 'alerts', 'critical', 'errors', 'warning',
|
|
||||||
'notifications', 'informational', 'debugging']),
|
|
||||||
state=dict(default='present', choices=['present', 'absent']),
|
|
||||||
)
|
|
||||||
|
|
||||||
aggregate_spec = deepcopy(element_spec)
|
|
||||||
|
|
||||||
# remove default in aggregate spec, to handle common arguments
|
|
||||||
remove_default_spec(aggregate_spec)
|
|
||||||
|
|
||||||
mutually_exclusive = [('dest', 'facility', 'hostnameprefix')]
|
|
||||||
|
|
||||||
required_if = [('dest', 'host', ['name']),
|
|
||||||
('dest', 'file', ['name']),
|
|
||||||
('dest', 'buffered', ['size']),
|
|
||||||
('dest', 'console', ['level']),
|
|
||||||
('dest', 'monitor', ['level'])]
|
|
||||||
|
|
||||||
argument_spec = dict(
|
|
||||||
aggregate=dict(type='list', elements='dict', options=aggregate_spec,
|
|
||||||
mutually_exclusive=mutually_exclusive, required_if=required_if),
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(element_spec)
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
mutually_exclusive=mutually_exclusive,
|
|
||||||
required_if=required_if,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
config_object = None
|
|
||||||
if is_cliconf(module):
|
|
||||||
# Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported
|
|
||||||
# module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
|
|
||||||
# version='2.9')
|
|
||||||
config_object = CliConfiguration(module)
|
|
||||||
elif is_netconf(module):
|
|
||||||
config_object = NCConfiguration(module)
|
|
||||||
|
|
||||||
if config_object:
|
|
||||||
result = config_object.run()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,204 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# (c) 2017, Ansible by Red Hat, Inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_netconf
|
|
||||||
version_added: "2.5"
|
|
||||||
author: "Kedar Kekan (@kedarX)"
|
|
||||||
short_description: Configures NetConf sub-system service on Cisco IOS-XR devices
|
|
||||||
description:
|
|
||||||
- This module provides an abstraction that enables and configures
|
|
||||||
the netconf system service running on Cisco IOS-XR Software.
|
|
||||||
This module can be used to easily enable the Netconf API. Netconf provides
|
|
||||||
a programmatic interface for working with configuration and state
|
|
||||||
resources as defined in RFC 6242.
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
options:
|
|
||||||
netconf_port:
|
|
||||||
description:
|
|
||||||
- This argument specifies the port the netconf service should
|
|
||||||
listen on for SSH connections. The default port as defined
|
|
||||||
in RFC 6242 is 830.
|
|
||||||
required: false
|
|
||||||
default: 830
|
|
||||||
aliases: ['listens_on']
|
|
||||||
netconf_vrf:
|
|
||||||
description:
|
|
||||||
- netconf vrf name
|
|
||||||
required: false
|
|
||||||
default: default
|
|
||||||
aliases: ['vrf']
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Specifies the state of the C(iosxr_netconf) resource on
|
|
||||||
the remote device. If the I(state) argument is set to
|
|
||||||
I(present) the netconf service will be configured. If the
|
|
||||||
I(state) argument is set to I(absent) the netconf service
|
|
||||||
will be removed from the configuration.
|
|
||||||
required: false
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent']
|
|
||||||
notes:
|
|
||||||
- This module works with connection C(network_cli). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- Tested against Cisco IOS XR Software, Version 6.1.3
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: enable netconf service on port 830
|
|
||||||
iosxr_netconf:
|
|
||||||
listens_on: 830
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: disable netconf service
|
|
||||||
iosxr_netconf:
|
|
||||||
state: absent
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
description: Returns the command sent to the remote device
|
|
||||||
returned: when changed is True
|
|
||||||
type: str
|
|
||||||
sample: 'ssh server netconf port 830'
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
|
|
||||||
USE_PERSISTENT_CONNECTION = True
|
|
||||||
|
|
||||||
|
|
||||||
def map_obj_to_commands(updates):
|
|
||||||
want, have = updates
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
if want['state'] == 'absent':
|
|
||||||
if have['state'] == 'present':
|
|
||||||
commands.append('no netconf-yang agent ssh')
|
|
||||||
|
|
||||||
if 'netconf_port' in have:
|
|
||||||
commands.append('no ssh server netconf port %s' % have['netconf_port'])
|
|
||||||
|
|
||||||
if have['netconf_vrf']:
|
|
||||||
for vrf in have['netconf_vrf']:
|
|
||||||
commands.append('no ssh server netconf vrf %s' % vrf)
|
|
||||||
else:
|
|
||||||
if have['state'] == 'absent':
|
|
||||||
commands.append('netconf-yang agent ssh')
|
|
||||||
|
|
||||||
if want['netconf_port'] is not None and (want['netconf_port'] != have.get('netconf_port')):
|
|
||||||
commands.append(
|
|
||||||
'ssh server netconf port %s' % want['netconf_port']
|
|
||||||
)
|
|
||||||
if want['netconf_vrf'] is not None and (want['netconf_vrf'] not in have['netconf_vrf']):
|
|
||||||
commands.append(
|
|
||||||
'ssh server netconf vrf %s' % want['netconf_vrf']
|
|
||||||
)
|
|
||||||
|
|
||||||
return commands
|
|
||||||
|
|
||||||
|
|
||||||
def parse_vrf(config):
|
|
||||||
match = re.search(r'vrf (\w+)', config)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_port(config):
|
|
||||||
match = re.search(r'port (\d+)', config)
|
|
||||||
if match:
|
|
||||||
return int(match.group(1))
|
|
||||||
|
|
||||||
|
|
||||||
def map_config_to_obj(module):
|
|
||||||
obj = {'state': 'absent'}
|
|
||||||
|
|
||||||
netconf_config = get_config(module, config_filter='netconf-yang agent')
|
|
||||||
|
|
||||||
ssh_config = get_config(module, config_filter='ssh server')
|
|
||||||
ssh_config = [config_line for config_line in (line.strip() for line in ssh_config.splitlines()) if config_line]
|
|
||||||
obj['netconf_vrf'] = []
|
|
||||||
for config in ssh_config:
|
|
||||||
if 'netconf port' in config:
|
|
||||||
obj.update({'netconf_port': parse_port(config)})
|
|
||||||
if 'netconf vrf' in config:
|
|
||||||
obj['netconf_vrf'].append(parse_vrf(config))
|
|
||||||
if 'ssh' in netconf_config and ('netconf_port' in obj or obj['netconf_vrf']):
|
|
||||||
obj.update({'state': 'present'})
|
|
||||||
|
|
||||||
if 'ssh' in netconf_config and 'netconf_port' not in obj:
|
|
||||||
obj.update({'netconf_port': 830})
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_netconf_port(value, module):
|
|
||||||
if not 1 <= value <= 65535:
|
|
||||||
module.fail_json(msg='netconf_port must be between 1 and 65535')
|
|
||||||
|
|
||||||
|
|
||||||
def map_params_to_obj(module):
|
|
||||||
obj = {
|
|
||||||
'netconf_port': module.params['netconf_port'],
|
|
||||||
'netconf_vrf': module.params['netconf_vrf'],
|
|
||||||
'state': module.params['state']
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value in iteritems(obj):
|
|
||||||
# validate the param value (if validator func exists)
|
|
||||||
validator = globals().get('validate_%s' % key)
|
|
||||||
if callable(validator):
|
|
||||||
validator(value, module)
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""main entry point for module execution
|
|
||||||
"""
|
|
||||||
argument_spec = dict(
|
|
||||||
netconf_port=dict(type='int', default=830, aliases=['listens_on']),
|
|
||||||
netconf_vrf=dict(aliases=['vrf'], default='default'),
|
|
||||||
state=dict(default='present', choices=['present', 'absent']),
|
|
||||||
)
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
warnings = list()
|
|
||||||
|
|
||||||
result = {'changed': False, 'warnings': warnings}
|
|
||||||
|
|
||||||
want = map_params_to_obj(module)
|
|
||||||
have = map_config_to_obj(module)
|
|
||||||
commands = map_obj_to_commands((want, have))
|
|
||||||
result['commands'] = commands
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
commit = not module.check_mode
|
|
||||||
diff = load_config(module, commands, commit=commit)
|
|
||||||
if diff:
|
|
||||||
result['diff'] = dict(prepared=diff)
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,966 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2019 Red Hat
|
|
||||||
# GNU General Public License v3.0+
|
|
||||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# WARNING #
|
|
||||||
#############################################
|
|
||||||
#
|
|
||||||
# This file is auto generated by the resource
|
|
||||||
# module builder playbook.
|
|
||||||
#
|
|
||||||
# Do not edit this file manually.
|
|
||||||
#
|
|
||||||
# Changes to this file will be over written
|
|
||||||
# by the resource module builder.
|
|
||||||
#
|
|
||||||
# Changes should be made in the model used to
|
|
||||||
# generate this file or in the resource module
|
|
||||||
# builder template.
|
|
||||||
#
|
|
||||||
#############################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
The module file for iosxr_static_routes
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
"metadata_version": "1.1",
|
|
||||||
"status": ["preview"],
|
|
||||||
"supported_by": "network",
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_static_routes
|
|
||||||
version_added: "2.10"
|
|
||||||
short_description: Manage static routes on devices running Cisco IOS-XR.
|
|
||||||
description:
|
|
||||||
- This module manages static routes on devices running Cisco IOS-XR.
|
|
||||||
author: Nilashish Chakraborty (@NilashishC)
|
|
||||||
options:
|
|
||||||
running_config:
|
|
||||||
description:
|
|
||||||
- The module, by default, will connect to the remote device and
|
|
||||||
retrieve the current running-config to use as a base for comparing
|
|
||||||
against the contents of source. There are times when it is not
|
|
||||||
desirable to have the task get the current running-config for
|
|
||||||
every task in a playbook. The I(running_config) argument allows the
|
|
||||||
implementer to pass in the configuration to use as the base
|
|
||||||
config for comparison. This value of this option should be the
|
|
||||||
output received from device by executing command
|
|
||||||
B(show running-config router static).
|
|
||||||
type: str
|
|
||||||
config:
|
|
||||||
description: A dictionary of static route options.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
vrf:
|
|
||||||
description:
|
|
||||||
- The VRF to which the static route(s) belong.
|
|
||||||
type: str
|
|
||||||
address_families:
|
|
||||||
description: A dictionary specifying the address family to which the static route(s) belong.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
afi:
|
|
||||||
description:
|
|
||||||
- Specifies the top level address family indicator.
|
|
||||||
type: str
|
|
||||||
choices: ['ipv4', 'ipv6']
|
|
||||||
required: True
|
|
||||||
safi:
|
|
||||||
description:
|
|
||||||
- Specifies the subsequent address family indicator.
|
|
||||||
type: str
|
|
||||||
choices: ['unicast', 'multicast']
|
|
||||||
required: True
|
|
||||||
routes:
|
|
||||||
description: A dictionary that specifies the static route configurations.
|
|
||||||
elements: dict
|
|
||||||
type: list
|
|
||||||
suboptions:
|
|
||||||
dest:
|
|
||||||
description:
|
|
||||||
- An IPv4 or IPv6 address in CIDR notation that specifies the destination network for the static route.
|
|
||||||
type: str
|
|
||||||
required: True
|
|
||||||
next_hops:
|
|
||||||
description:
|
|
||||||
- Next hops to the specified destination.
|
|
||||||
type: list
|
|
||||||
elements: dict
|
|
||||||
suboptions:
|
|
||||||
forward_router_address:
|
|
||||||
description:
|
|
||||||
- The IP address of the next hop that can be used to reach the destination network.
|
|
||||||
type: str
|
|
||||||
interface:
|
|
||||||
description:
|
|
||||||
- The interface to use to reach the destination.
|
|
||||||
type: str
|
|
||||||
dest_vrf:
|
|
||||||
description:
|
|
||||||
- The destination VRF.
|
|
||||||
type: str
|
|
||||||
admin_distance:
|
|
||||||
description:
|
|
||||||
- The administrative distance for this static route.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
metric:
|
|
||||||
description:
|
|
||||||
- Specifes the metric for this static route.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
description:
|
|
||||||
description:
|
|
||||||
- Specifies the description for this static route.
|
|
||||||
type: str
|
|
||||||
vrflabel:
|
|
||||||
description:
|
|
||||||
- Specifies the VRF label for this static route.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
tag:
|
|
||||||
description:
|
|
||||||
- Specifies a numeric tag for this static route.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
track:
|
|
||||||
description:
|
|
||||||
- Specifies the object to be tracked.
|
|
||||||
- This enables object tracking for static routes.
|
|
||||||
type: str
|
|
||||||
tunnel_id:
|
|
||||||
description:
|
|
||||||
- Specifies a tunnel id for the route.
|
|
||||||
- Refer to vendor documentation for valid values.
|
|
||||||
type: int
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The state the configuration should be left in.
|
|
||||||
type: str
|
|
||||||
choices:
|
|
||||||
- merged
|
|
||||||
- replaced
|
|
||||||
- overridden
|
|
||||||
- deleted
|
|
||||||
- gathered
|
|
||||||
- rendered
|
|
||||||
- parsed
|
|
||||||
default: merged
|
|
||||||
"""
|
|
||||||
EXAMPLES = """
|
|
||||||
|
|
||||||
# Using merged
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#show running-config router static
|
|
||||||
# Sat Feb 22 07:46:30.089 UTC
|
|
||||||
# % No such configuration item(s)
|
|
||||||
#
|
|
||||||
- name: Merge the provided configuration with the exisiting running configuration
|
|
||||||
iosxr_static_routes: &merged
|
|
||||||
config:
|
|
||||||
- address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.16/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.10
|
|
||||||
interface: FastEthernet0/0/0/1
|
|
||||||
description: "LAB"
|
|
||||||
metric: 120
|
|
||||||
tag: 10
|
|
||||||
|
|
||||||
- interface: FastEthernet0/0/0/5
|
|
||||||
track: ip_sla_1
|
|
||||||
|
|
||||||
- dest: 192.0.2.32/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.11
|
|
||||||
admin_distance: 100
|
|
||||||
|
|
||||||
- afi: ipv6
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 2001:db8:1000::/36
|
|
||||||
next_hops:
|
|
||||||
- interface: FastEthernet0/0/0/7
|
|
||||||
description: "DC"
|
|
||||||
|
|
||||||
- interface: FastEthernet0/0/0/8
|
|
||||||
forward_router_address: 2001:db8:2000:2::1
|
|
||||||
|
|
||||||
- vrf: DEV_SITE
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.48/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.12
|
|
||||||
description: "DEV"
|
|
||||||
dest_vrf: test_1
|
|
||||||
|
|
||||||
- dest: 192.0.2.80/28
|
|
||||||
next_hops:
|
|
||||||
- interface: FastEthernet0/0/0/2
|
|
||||||
forward_router_address: 192.0.2.14
|
|
||||||
dest_vrf: test_1
|
|
||||||
track: ip_sla_2
|
|
||||||
vrflabel: 124
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#show running-config router static
|
|
||||||
# Sat Feb 22 07:49:11.754 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using merged to update existing static routes
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#show running-config router static
|
|
||||||
# Sat Feb 22 07:49:11.754 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Update existing static routes configuration using merged
|
|
||||||
iosxr_static_routes: &merged_update
|
|
||||||
config:
|
|
||||||
- vrf: DEV_SITE
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.48/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.12
|
|
||||||
vrflabel: 2301
|
|
||||||
dest_vrf: test_1
|
|
||||||
|
|
||||||
- dest: 192.0.2.80/28
|
|
||||||
next_hops:
|
|
||||||
- interface: FastEthernet0/0/0/2
|
|
||||||
forward_router_address: 192.0.2.14
|
|
||||||
dest_vrf: test_1
|
|
||||||
description: "rt_test_1"
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#show running-config router static
|
|
||||||
# Sat Feb 22 07:49:11.754 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV vrflabel 2301
|
|
||||||
# 192.0.2.80/28 vrf test_1 192.0.2.14 FastEthernet0/0/0/2 description rt_test_1 track ip_sla_2 vrflabel 124
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using replaced to replace all next hop entries for a single destination network
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# --------------
|
|
||||||
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Replace device configurations of static routes with provided configurations
|
|
||||||
iosxr_static_routes: &replaced
|
|
||||||
config:
|
|
||||||
- vrf: DEV_SITE
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.48/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.15
|
|
||||||
interface: FastEthernet0/0/0/3
|
|
||||||
description: "DEV_NEW"
|
|
||||||
dest_vrf: dev_test_2
|
|
||||||
state: replaced
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 08:04:07.085 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf dev_test_2 FastEthernet0/0/0/3 192.0.2.15 description DEV_NEW
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using overridden to override all static route entries on the device
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Overridde all static routes configuration with provided configuration
|
|
||||||
iosxr_static_routes: &overridden
|
|
||||||
config:
|
|
||||||
- vrf: DEV_NEW
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.48/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.15
|
|
||||||
interface: FastEthernet0/0/0/3
|
|
||||||
description: "DEV1"
|
|
||||||
- afi: ipv6
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 2001:db8:3000::/36
|
|
||||||
next_hops:
|
|
||||||
- interface: FastEthernet0/0/0/4
|
|
||||||
forward_router_address: 2001:db8:2000:2::2
|
|
||||||
description: "PROD1"
|
|
||||||
track: ip_sla_1
|
|
||||||
state: overridden
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 08:07:41.516 UTC
|
|
||||||
# router static
|
|
||||||
# vrf DEV_NEW
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 FastEthernet0/0/0/3 192.0.2.15 description DEV1
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:3000::/36 FastEthernet0/0/0/4 2001:db8:2000:2::2 description PROD1 track ip_sla_1
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted to delete a single next hop for a destination network
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Delete a single next_hop from a destination network
|
|
||||||
iosxr_static_routes:
|
|
||||||
config:
|
|
||||||
- address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.16/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.10
|
|
||||||
interface: FastEthernet0/0/0/1
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted to delete a destination network entry
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Delete a destination network entry
|
|
||||||
iosxr_static_routes:
|
|
||||||
config:
|
|
||||||
- vrf: DEV_SITE
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.48/28
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted to delete all destination network entries under a single AFI
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Delete all destination network entries under a single AFI
|
|
||||||
iosxr_static_routes:
|
|
||||||
config:
|
|
||||||
- vrf: DEV_SITE
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 08:16:41.464 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
# Using deleted to remove all static route entries from the device
|
|
||||||
|
|
||||||
# Before state
|
|
||||||
# -------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 07:59:08.669 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Delete static routes configuration
|
|
||||||
iosxr_static_routes: &deleted
|
|
||||||
state: deleted
|
|
||||||
|
|
||||||
# After state
|
|
||||||
# ------------
|
|
||||||
# RP/0/RP0/CPU0:ios#sh running-config router static
|
|
||||||
# Sat Feb 22 08:50:43.038 UTC
|
|
||||||
# % No such configuration item(s)
|
|
||||||
|
|
||||||
# Using gathered to gather static route facts from the device
|
|
||||||
|
|
||||||
- name: Gather static routes facts from the device using iosxr_static_routes module
|
|
||||||
iosxr_static_routes:
|
|
||||||
state: gathered
|
|
||||||
|
|
||||||
# Task output (redacted)
|
|
||||||
# -----------------------
|
|
||||||
# "gathered": [
|
|
||||||
# {
|
|
||||||
# "address_families": [
|
|
||||||
# {
|
|
||||||
# "afi": "ipv4",
|
|
||||||
# "routes": [
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.16/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "description": "LAB",
|
|
||||||
# "forward_router_address": "192.0.2.10",
|
|
||||||
# "interface": "FastEthernet0/0/0/1",
|
|
||||||
# "metric": 120,
|
|
||||||
# "tag": 10
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "interface": "FastEthernet0/0/0/5",
|
|
||||||
# "track": "ip_sla_1"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.32/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "admin_distance": 100,
|
|
||||||
# "forward_router_address": "192.0.2.11"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "safi": "unicast"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "afi": "ipv6",
|
|
||||||
# "routes": [
|
|
||||||
# {
|
|
||||||
# "dest": "2001:db8:1000::/36",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "description": "DC",
|
|
||||||
# "interface": "FastEthernet0/0/0/7"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "forward_router_address": "2001:db8:2000:2::1",
|
|
||||||
# "interface": "FastEthernet0/0/0/8"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "safi": "unicast"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "address_families": [
|
|
||||||
# {
|
|
||||||
# "afi": "ipv4",
|
|
||||||
# "routes": [
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.48/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "description": "DEV",
|
|
||||||
# "dest_vrf": "test_1",
|
|
||||||
# "forward_router_address": "192.0.2.12"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "forward_router_address": "192.0.3.24",
|
|
||||||
# "interface": "GigabitEthernet0/0/0/1",
|
|
||||||
# "vrflabel": 2302
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.80/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "dest_vrf": "test_1",
|
|
||||||
# "forward_router_address": "192.0.2.14",
|
|
||||||
# "interface": "FastEthernet0/0/0/2",
|
|
||||||
# "track": "ip_sla_2",
|
|
||||||
# "vrflabel": 124
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "safi": "unicast"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "vrf": "DEV_SITE"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# Using rendered
|
|
||||||
|
|
||||||
- name: Render platform specific commands (without connecting to the device)
|
|
||||||
iosxr_static_routes:
|
|
||||||
config:
|
|
||||||
- vrf: DEV_SITE
|
|
||||||
address_families:
|
|
||||||
- afi: ipv4
|
|
||||||
safi: unicast
|
|
||||||
routes:
|
|
||||||
- dest: 192.0.2.48/28
|
|
||||||
next_hops:
|
|
||||||
- forward_router_address: 192.0.2.12
|
|
||||||
description: "DEV"
|
|
||||||
dest_vrf: test_1
|
|
||||||
|
|
||||||
- dest: 192.0.2.80/28
|
|
||||||
next_hops:
|
|
||||||
- interface: FastEthernet0/0/0/2
|
|
||||||
forward_router_address: 192.0.2.14
|
|
||||||
dest_vrf: test_1
|
|
||||||
track: ip_sla_2
|
|
||||||
vrflabel: 124
|
|
||||||
|
|
||||||
# Task Output (redacted)
|
|
||||||
# -----------------------
|
|
||||||
# "rendered": [
|
|
||||||
# "router static"s,
|
|
||||||
# "vrf DEV_SITE",
|
|
||||||
# "address-family ipv4 unicast",
|
|
||||||
# "192.0.2.48/28 vrf test_1 192.0.2.12 description DEV",
|
|
||||||
# "192.0.2.80/28 vrf test_1 192.0.2.14 FastEthernet0/0/0/2 track ip_sla_2 vrflabel 124"
|
|
||||||
|
|
||||||
# Using parsed
|
|
||||||
|
|
||||||
# parsed.cfg
|
|
||||||
# ------------
|
|
||||||
# Fri Nov 29 21:10:41.896 UTC
|
|
||||||
# router static
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
|
|
||||||
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
|
|
||||||
# 192.0.2.32/28 192.0.2.11 100
|
|
||||||
# !
|
|
||||||
# address-family ipv6 unicast
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
|
|
||||||
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
|
|
||||||
# !
|
|
||||||
# vrf DEV_SITE
|
|
||||||
# address-family ipv4 unicast
|
|
||||||
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
|
|
||||||
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
# !
|
|
||||||
|
|
||||||
- name: Use parsed state to convert externally supplied device specific static routes commands to structured format
|
|
||||||
iosxr_static_routes:
|
|
||||||
running_config: "{{ lookup('file', '../../fixtures/parsed.cfg') }}"
|
|
||||||
state: parsed
|
|
||||||
|
|
||||||
# Task output (redacted)
|
|
||||||
# -----------------------
|
|
||||||
# "parsed": [
|
|
||||||
# {
|
|
||||||
# "address_families": [
|
|
||||||
# {
|
|
||||||
# "afi": "ipv4",
|
|
||||||
# "routes": [
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.16/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "description": "LAB",
|
|
||||||
# "forward_router_address": "192.0.2.10",
|
|
||||||
# "interface": "FastEthernet0/0/0/1",
|
|
||||||
# "metric": 120,
|
|
||||||
# "tag": 10
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "interface": "FastEthernet0/0/0/5",
|
|
||||||
# "track": "ip_sla_1"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.32/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "admin_distance": 100,
|
|
||||||
# "forward_router_address": "192.0.2.11"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "safi": "unicast"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "afi": "ipv6",
|
|
||||||
# "routes": [
|
|
||||||
# {
|
|
||||||
# "dest": "2001:db8:1000::/36",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "description": "DC",
|
|
||||||
# "interface": "FastEthernet0/0/0/7"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "forward_router_address": "2001:db8:2000:2::1",
|
|
||||||
# "interface": "FastEthernet0/0/0/8"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "safi": "unicast"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "address_families": [
|
|
||||||
# {
|
|
||||||
# "afi": "ipv4",
|
|
||||||
# "routes": [
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.48/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "description": "DEV",
|
|
||||||
# "dest_vrf": "test_1",
|
|
||||||
# "forward_router_address": "192.0.2.12"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "dest": "192.0.2.80/28",
|
|
||||||
# "next_hops": [
|
|
||||||
# {
|
|
||||||
# "dest_vrf": "test_1",
|
|
||||||
# "forward_router_address": "192.0.2.14",
|
|
||||||
# "interface": "FastEthernet0/0/0/2",
|
|
||||||
# "track": "ip_sla_2",
|
|
||||||
# "vrflabel": 124
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "safi": "unicast"
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "vrf": "DEV_SITE"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
"""
|
|
||||||
RETURN = """
|
|
||||||
before:
|
|
||||||
description: The configuration prior to the model invocation.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
after:
|
|
||||||
description: The resulting configuration model invocation.
|
|
||||||
returned: when changed
|
|
||||||
type: list
|
|
||||||
sample: >
|
|
||||||
The configuration returned will always be in the same format
|
|
||||||
of the parameters above.
|
|
||||||
commands:
|
|
||||||
description: The set of commands pushed to the remote device.
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
- router static
|
|
||||||
- vrf dev_site
|
|
||||||
- address-family ipv4 unicast
|
|
||||||
- 192.0.2.48/28 192.0.2.12 FastEthernet0/0/0/1 track ip_sla_10 description dev1
|
|
||||||
- address-family ipv6 unicast
|
|
||||||
- no 2001:db8:1000::/36
|
|
||||||
- 2001:db8:3000::/36 2001:db8:2000:2::2 FastEthernet0/0/0/4 track ip_sla_11 description prod1
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.argspec.static_routes.static_routes import Static_routesArgs
|
|
||||||
from ansible.module_utils.network.iosxr.config.static_routes.static_routes import Static_routes
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main entry point for module execution
|
|
||||||
|
|
||||||
:returns: the result form module invocation
|
|
||||||
"""
|
|
||||||
required_if = [
|
|
||||||
("state", "merged", ("config",)),
|
|
||||||
("state", "replaced", ("config",)),
|
|
||||||
("state", "overridden", ("config",)),
|
|
||||||
("state", "parsed", ("running_config",)),
|
|
||||||
]
|
|
||||||
mutually_exclusive = [("config", "running_config")]
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=Static_routesArgs.argument_spec,
|
|
||||||
required_if=required_if,
|
|
||||||
mutually_exclusive=mutually_exclusive,
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = Static_routes(module).execute_module()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,594 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_system
|
|
||||||
version_added: "2.3"
|
|
||||||
author:
|
|
||||||
- "Peter Sprygada (@privateip)"
|
|
||||||
- "Kedar Kekan (@kedarX)"
|
|
||||||
short_description: Manage the system attributes on Cisco IOS XR devices
|
|
||||||
description:
|
|
||||||
- This module provides declarative management of node system attributes
|
|
||||||
on Cisco IOS XR devices. It provides an option to configure host system
|
|
||||||
parameters or remove those parameters from the device active
|
|
||||||
configuration.
|
|
||||||
requirements:
|
|
||||||
- ncclient >= 0.5.3 when using netconf
|
|
||||||
- lxml >= 4.1.1 when using netconf
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
notes:
|
|
||||||
- This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- Tested against IOS XRv 6.1.3
|
|
||||||
- name-servers I(state=absent) operation with C(netconf) transport is a success, but with rpc-error. This is
|
|
||||||
due to XR platform issue. Recommended to use I(ignore_errors) option with the task as a workaround.
|
|
||||||
options:
|
|
||||||
hostname:
|
|
||||||
description:
|
|
||||||
- Configure the device hostname parameter. This option takes an ASCII string value.
|
|
||||||
vrf:
|
|
||||||
description:
|
|
||||||
- VRF name for domain services
|
|
||||||
version_added: 2.5
|
|
||||||
domain_name:
|
|
||||||
description:
|
|
||||||
- Configure the IP domain name
|
|
||||||
on the remote device to the provided value. Value
|
|
||||||
should be in the dotted name form and will be
|
|
||||||
appended to the C(hostname) to create a fully-qualified
|
|
||||||
domain name.
|
|
||||||
domain_search:
|
|
||||||
description:
|
|
||||||
- Provides the list of domain suffixes to
|
|
||||||
append to the hostname for the purpose of doing name resolution.
|
|
||||||
This argument accepts a list of names and will be reconciled
|
|
||||||
with the current active configuration on the running node.
|
|
||||||
lookup_source:
|
|
||||||
description:
|
|
||||||
- The C(lookup_source) argument provides one or more source
|
|
||||||
interfaces to use for performing DNS lookups. The interface
|
|
||||||
provided in C(lookup_source) must be a valid interface configured
|
|
||||||
on the device.
|
|
||||||
lookup_enabled:
|
|
||||||
description:
|
|
||||||
- Provides administrative control
|
|
||||||
for enabling or disabling DNS lookups. When this argument is
|
|
||||||
set to True, lookups are performed and when it is set to False,
|
|
||||||
lookups are not performed.
|
|
||||||
type: bool
|
|
||||||
name_servers:
|
|
||||||
description:
|
|
||||||
- The C(name_serves) argument accepts a list of DNS name servers by
|
|
||||||
way of either FQDN or IP address to use to perform name resolution
|
|
||||||
lookups. This argument accepts wither a list of DNS servers See
|
|
||||||
examples.
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- State of the configuration
|
|
||||||
values in the device's current active configuration. When set
|
|
||||||
to I(present), the values should be configured in the device active
|
|
||||||
configuration and when set to I(absent) the values should not be
|
|
||||||
in the device active configuration
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent']
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: configure hostname and domain-name (default vrf=default)
|
|
||||||
iosxr_system:
|
|
||||||
hostname: iosxr01
|
|
||||||
domain_name: test.example.com
|
|
||||||
domain_search:
|
|
||||||
- ansible.com
|
|
||||||
- redhat.com
|
|
||||||
- cisco.com
|
|
||||||
- name: remove configuration
|
|
||||||
iosxr_system:
|
|
||||||
hostname: iosxr01
|
|
||||||
domain_name: test.example.com
|
|
||||||
domain_search:
|
|
||||||
- ansible.com
|
|
||||||
- redhat.com
|
|
||||||
- cisco.com
|
|
||||||
state: absent
|
|
||||||
- name: configure hostname and domain-name with vrf
|
|
||||||
iosxr_system:
|
|
||||||
hostname: iosxr01
|
|
||||||
vrf: nondefault
|
|
||||||
domain_name: test.example.com
|
|
||||||
domain_search:
|
|
||||||
- ansible.com
|
|
||||||
- redhat.com
|
|
||||||
- cisco.com
|
|
||||||
- name: configure DNS lookup sources
|
|
||||||
iosxr_system:
|
|
||||||
lookup_source: MgmtEth0/0/CPU0/0
|
|
||||||
lookup_enabled: True
|
|
||||||
- name: configure name servers
|
|
||||||
iosxr_system:
|
|
||||||
name_servers:
|
|
||||||
- 8.8.8.8
|
|
||||||
- 8.8.4.4
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
description: The list of configuration mode commands to send to the device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
- hostname iosxr01
|
|
||||||
- ip domain-name test.example.com
|
|
||||||
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">
|
|
||||||
<ip-domain xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-ip-domain-cfg">
|
|
||||||
<vrfs>
|
|
||||||
<vrf>
|
|
||||||
<vrf-name>default</vrf-name>
|
|
||||||
<lists>
|
|
||||||
<list xc:operation="merge">
|
|
||||||
<order>0</order>
|
|
||||||
<list-name>redhat.com</list-name>
|
|
||||||
</list>
|
|
||||||
</lists>
|
|
||||||
</vrf>
|
|
||||||
</vrfs>
|
|
||||||
</ip-domain>
|
|
||||||
</config>'
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, etree_findall
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import is_cliconf, is_netconf, etree_find
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, build_xml
|
|
||||||
|
|
||||||
|
|
||||||
def diff_list(want, have):
|
|
||||||
adds = set(want).difference(have)
|
|
||||||
removes = set(have).difference(want)
|
|
||||||
return (adds, removes)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigBase(object):
|
|
||||||
def __init__(self, module):
|
|
||||||
self._module = module
|
|
||||||
self._result = {'changed': False, 'warnings': []}
|
|
||||||
self._want = dict()
|
|
||||||
self._have = dict()
|
|
||||||
|
|
||||||
def map_params_to_obj(self):
|
|
||||||
self._want.update({
|
|
||||||
'hostname': self._module.params['hostname'],
|
|
||||||
'vrf': self._module.params['vrf'],
|
|
||||||
'domain_name': self._module.params['domain_name'],
|
|
||||||
'domain_search': self._module.params['domain_search'],
|
|
||||||
'lookup_source': self._module.params['lookup_source'],
|
|
||||||
'lookup_enabled': self._module.params['lookup_enabled'],
|
|
||||||
'name_servers': self._module.params['name_servers']
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class CliConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(CliConfiguration, self).__init__(module)
|
|
||||||
|
|
||||||
def map_obj_to_commands(self):
|
|
||||||
commands = list()
|
|
||||||
state = self._module.params['state']
|
|
||||||
|
|
||||||
def needs_update(x):
|
|
||||||
return self._want.get(x) and (self._want.get(x) != self._have.get(x))
|
|
||||||
|
|
||||||
if state == 'absent':
|
|
||||||
if self._have['hostname'] != 'ios':
|
|
||||||
commands.append('no hostname')
|
|
||||||
if self._have['domain_name']:
|
|
||||||
commands.append('no domain name')
|
|
||||||
if self._have['lookup_source']:
|
|
||||||
commands.append('no domain lookup source-interface {0!s}'.format(self._have['lookup_source']))
|
|
||||||
if not self._have['lookup_enabled']:
|
|
||||||
commands.append('no domain lookup disable')
|
|
||||||
for item in self._have['name_servers']:
|
|
||||||
commands.append('no domain name-server {0!s}'.format(item))
|
|
||||||
for item in self._have['domain_search']:
|
|
||||||
commands.append('no domain list {0!s}'.format(item))
|
|
||||||
|
|
||||||
elif state == 'present':
|
|
||||||
if needs_update('hostname'):
|
|
||||||
commands.append('hostname {0!s}'.format(self._want['hostname']))
|
|
||||||
|
|
||||||
if needs_update('domain_name'):
|
|
||||||
commands.append('domain name {0!s}'.format(self._want['domain_name']))
|
|
||||||
|
|
||||||
if needs_update('lookup_source'):
|
|
||||||
commands.append('domain lookup source-interface {0!s}'.format(self._want['lookup_source']))
|
|
||||||
|
|
||||||
cmd = None
|
|
||||||
if not self._want['lookup_enabled'] and self._have['lookup_enabled']:
|
|
||||||
cmd = 'domain lookup disable'
|
|
||||||
elif self._want['lookup_enabled'] and not self._have['lookup_enabled']:
|
|
||||||
cmd = 'no domain lookup disable'
|
|
||||||
if cmd is not None:
|
|
||||||
commands.append(cmd)
|
|
||||||
|
|
||||||
if self._want['name_servers'] is not None:
|
|
||||||
adds, removes = diff_list(self._want['name_servers'], self._have['name_servers'])
|
|
||||||
for item in adds:
|
|
||||||
commands.append('domain name-server {0!s}'.format(item))
|
|
||||||
for item in removes:
|
|
||||||
commands.append('no domain name-server {0!s}'.format(item))
|
|
||||||
|
|
||||||
if self._want['domain_search'] is not None:
|
|
||||||
adds, removes = diff_list(self._want['domain_search'], self._have['domain_search'])
|
|
||||||
for item in adds:
|
|
||||||
commands.append('domain list {0!s}'.format(item))
|
|
||||||
for item in removes:
|
|
||||||
commands.append('no domain list {0!s}'.format(item))
|
|
||||||
|
|
||||||
self._result['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['commands'] = commands
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def parse_hostname(self, config):
|
|
||||||
match = re.search(r'^hostname (\S+)', config, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_domain_name(self, config):
|
|
||||||
match = re.search(r'^domain name (\S+)', config, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def parse_lookup_source(self, config):
|
|
||||||
match = re.search(r'^domain lookup source-interface (\S+)', config, re.M)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
def map_config_to_obj(self):
|
|
||||||
config = get_config(self._module)
|
|
||||||
self._have.update({
|
|
||||||
'hostname': self.parse_hostname(config),
|
|
||||||
'domain_name': self.parse_domain_name(config),
|
|
||||||
'domain_search': re.findall(r'^domain list (\S+)', config, re.M),
|
|
||||||
'lookup_source': self.parse_lookup_source(config),
|
|
||||||
'lookup_enabled': 'domain lookup disable' not in config,
|
|
||||||
'name_servers': re.findall(r'^domain name-server (\S+)', config, re.M)
|
|
||||||
})
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_config_to_obj()
|
|
||||||
self.map_obj_to_commands()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
class NCConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module):
|
|
||||||
super(NCConfiguration, self).__init__(module)
|
|
||||||
self._system_meta = collections.OrderedDict()
|
|
||||||
self._system_domain_meta = collections.OrderedDict()
|
|
||||||
self._system_server_meta = collections.OrderedDict()
|
|
||||||
self._hostname_meta = collections.OrderedDict()
|
|
||||||
self._lookup_source_meta = collections.OrderedDict()
|
|
||||||
self._lookup_meta = collections.OrderedDict()
|
|
||||||
|
|
||||||
def map_obj_to_xml_rpc(self):
|
|
||||||
self._system_meta.update([
|
|
||||||
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
|
|
||||||
('a:domain_name', {'xpath': 'ip-domain/vrfs/vrf/name', 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
])
|
|
||||||
|
|
||||||
self._system_domain_meta.update([
|
|
||||||
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
|
|
||||||
('lists', {'xpath': 'ip-domain/vrfs/vrf/lists', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('list', {'xpath': 'ip-domain/vrfs/vrf/lists/list', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:order', {'xpath': 'ip-domain/vrfs/vrf/lists/list/order', 'operation': 'edit'}),
|
|
||||||
('a:domain_search', {'xpath': 'ip-domain/vrfs/vrf/lists/list/list-name', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
|
|
||||||
self._system_server_meta.update([
|
|
||||||
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
|
|
||||||
('servers', {'xpath': 'ip-domain/vrfs/vrf/servers', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('server', {'xpath': 'ip-domain/vrfs/vrf/servers/server', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
('a:order', {'xpath': 'ip-domain/vrfs/vrf/servers/server/order', 'operation': 'edit'}),
|
|
||||||
('a:name_servers', {'xpath': 'ip-domain/vrfs/vrf/servers/server/server-address', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
|
|
||||||
self._hostname_meta.update([
|
|
||||||
('a:hostname', {'xpath': 'host-names/host-name', 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
])
|
|
||||||
|
|
||||||
self._lookup_source_meta.update([
|
|
||||||
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
|
|
||||||
('a:lookup_source', {'xpath': 'ip-domain/vrfs/vrf/source-interface', 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
])
|
|
||||||
|
|
||||||
self._lookup_meta.update([
|
|
||||||
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
|
|
||||||
('lookup', {'xpath': 'ip-domain/vrfs/vrf/lookup', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
|
|
||||||
])
|
|
||||||
|
|
||||||
state = self._module.params['state']
|
|
||||||
_get_filter = build_xml('ip-domain', opcode="filter")
|
|
||||||
running = get_config(self._module, source='running', config_filter=_get_filter)
|
|
||||||
_get_filter = build_xml('host-names', opcode="filter")
|
|
||||||
hostname_runn = get_config(self._module, source='running', config_filter=_get_filter)
|
|
||||||
|
|
||||||
hostname_ele = etree_find(hostname_runn, 'host-name')
|
|
||||||
hostname = hostname_ele.text if hostname_ele is not None else None
|
|
||||||
|
|
||||||
vrf_ele = etree_findall(running, 'vrf')
|
|
||||||
vrf_map = {}
|
|
||||||
for vrf in vrf_ele:
|
|
||||||
name_server_list = list()
|
|
||||||
domain_list = list()
|
|
||||||
vrf_name_ele = etree_find(vrf, 'vrf-name')
|
|
||||||
vrf_name = vrf_name_ele.text if vrf_name_ele is not None else None
|
|
||||||
|
|
||||||
domain_name_ele = etree_find(vrf, 'name')
|
|
||||||
domain_name = domain_name_ele.text if domain_name_ele is not None else None
|
|
||||||
|
|
||||||
domain_ele = etree_findall(vrf, 'list-name')
|
|
||||||
for domain in domain_ele:
|
|
||||||
domain_list.append(domain.text)
|
|
||||||
|
|
||||||
server_ele = etree_findall(vrf, 'server-address')
|
|
||||||
for server in server_ele:
|
|
||||||
name_server_list.append(server.text)
|
|
||||||
|
|
||||||
lookup_source_ele = etree_find(vrf, 'source-interface')
|
|
||||||
lookup_source = lookup_source_ele.text if lookup_source_ele is not None else None
|
|
||||||
|
|
||||||
lookup_enabled = False if etree_find(vrf, 'lookup') is not None else True
|
|
||||||
|
|
||||||
vrf_map[vrf_name] = {'domain_name': domain_name,
|
|
||||||
'domain_search': domain_list,
|
|
||||||
'name_servers': name_server_list,
|
|
||||||
'lookup_source': lookup_source,
|
|
||||||
'lookup_enabled': lookup_enabled}
|
|
||||||
|
|
||||||
opcode = None
|
|
||||||
hostname_param = {}
|
|
||||||
lookup_param = {}
|
|
||||||
system_param = {}
|
|
||||||
sys_server_params = list()
|
|
||||||
sys_domain_params = list()
|
|
||||||
add_domain_params = list()
|
|
||||||
del_domain_params = list()
|
|
||||||
add_server_params = list()
|
|
||||||
del_server_params = list()
|
|
||||||
lookup_source_params = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
sys_node = vrf_map[self._want['vrf']]
|
|
||||||
except KeyError:
|
|
||||||
sys_node = {'domain_name': None,
|
|
||||||
'domain_search': [],
|
|
||||||
'name_servers': [],
|
|
||||||
'lookup_source': None,
|
|
||||||
'lookup_enabled': True}
|
|
||||||
|
|
||||||
if state == 'absent':
|
|
||||||
opcode = "delete"
|
|
||||||
|
|
||||||
def needs_update(x):
|
|
||||||
return self._want[x] is not None and self._want[x] == sys_node[x]
|
|
||||||
|
|
||||||
if needs_update('domain_name'):
|
|
||||||
system_param = {'vrf': self._want['vrf'], 'domain_name': self._want['domain_name']}
|
|
||||||
|
|
||||||
if needs_update('hostname'):
|
|
||||||
hostname_param = {'hostname': hostname}
|
|
||||||
|
|
||||||
if not self._want['lookup_enabled'] and not sys_node['lookup_enabled']:
|
|
||||||
lookup_param['vrf'] = self._want['vrf']
|
|
||||||
|
|
||||||
if needs_update('lookup_source'):
|
|
||||||
lookup_source_params['vrf'] = self._want['vrf']
|
|
||||||
lookup_source_params['lookup_source'] = self._want['lookup_source']
|
|
||||||
|
|
||||||
if self._want['domain_search']:
|
|
||||||
domain_param = {}
|
|
||||||
domain_param['domain_name'] = self._want['domain_name']
|
|
||||||
domain_param['vrf'] = self._want['vrf']
|
|
||||||
domain_param['order'] = '0'
|
|
||||||
for domain in self._want['domain_search']:
|
|
||||||
if domain in sys_node['domain_search']:
|
|
||||||
domain_param['domain_search'] = domain
|
|
||||||
sys_domain_params.append(domain_param.copy())
|
|
||||||
|
|
||||||
if self._want['name_servers']:
|
|
||||||
server_param = {}
|
|
||||||
server_param['vrf'] = self._want['vrf']
|
|
||||||
server_param['order'] = '0'
|
|
||||||
for server in self._want['name_servers']:
|
|
||||||
if server in sys_node['name_servers']:
|
|
||||||
server_param['name_servers'] = server
|
|
||||||
sys_server_params.append(server_param.copy())
|
|
||||||
|
|
||||||
elif state == 'present':
|
|
||||||
opcode = "merge"
|
|
||||||
|
|
||||||
def needs_update(x):
|
|
||||||
return self._want[x] is not None and (sys_node[x] is None or
|
|
||||||
(sys_node[x] is not None and self._want[x] != sys_node[x]))
|
|
||||||
|
|
||||||
if needs_update('domain_name'):
|
|
||||||
system_param = {'vrf': self._want['vrf'], 'domain_name': self._want['domain_name']}
|
|
||||||
|
|
||||||
if self._want['hostname'] is not None and self._want['hostname'] != hostname:
|
|
||||||
hostname_param = {'hostname': self._want['hostname']}
|
|
||||||
|
|
||||||
if not self._want['lookup_enabled'] and sys_node['lookup_enabled']:
|
|
||||||
lookup_param['vrf'] = self._want['vrf']
|
|
||||||
|
|
||||||
if needs_update('lookup_source'):
|
|
||||||
lookup_source_params['vrf'] = self._want['vrf']
|
|
||||||
lookup_source_params['lookup_source'] = self._want['lookup_source']
|
|
||||||
|
|
||||||
if self._want['domain_search']:
|
|
||||||
domain_adds, domain_removes = diff_list(self._want['domain_search'], sys_node['domain_search'])
|
|
||||||
domain_param = {}
|
|
||||||
domain_param['domain_name'] = self._want['domain_name']
|
|
||||||
domain_param['vrf'] = self._want['vrf']
|
|
||||||
domain_param['order'] = '0'
|
|
||||||
for domain in domain_adds:
|
|
||||||
if domain not in sys_node['domain_search']:
|
|
||||||
domain_param['domain_search'] = domain
|
|
||||||
add_domain_params.append(domain_param.copy())
|
|
||||||
for domain in domain_removes:
|
|
||||||
if domain in sys_node['domain_search']:
|
|
||||||
domain_param['domain_search'] = domain
|
|
||||||
del_domain_params.append(domain_param.copy())
|
|
||||||
|
|
||||||
if self._want['name_servers']:
|
|
||||||
server_adds, server_removes = diff_list(self._want['name_servers'], sys_node['name_servers'])
|
|
||||||
server_param = {}
|
|
||||||
server_param['vrf'] = self._want['vrf']
|
|
||||||
server_param['order'] = '0'
|
|
||||||
for domain in server_adds:
|
|
||||||
if domain not in sys_node['name_servers']:
|
|
||||||
server_param['name_servers'] = domain
|
|
||||||
add_server_params.append(server_param.copy())
|
|
||||||
for domain in server_removes:
|
|
||||||
if domain in sys_node['name_servers']:
|
|
||||||
server_param['name_servers'] = domain
|
|
||||||
del_server_params.append(server_param.copy())
|
|
||||||
|
|
||||||
self._result['xml'] = []
|
|
||||||
_edit_filter_list = list()
|
|
||||||
if opcode:
|
|
||||||
if hostname_param:
|
|
||||||
_edit_filter_list.append(build_xml('host-names', xmap=self._hostname_meta,
|
|
||||||
params=hostname_param, opcode=opcode))
|
|
||||||
|
|
||||||
if system_param:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_meta,
|
|
||||||
params=system_param, opcode=opcode))
|
|
||||||
|
|
||||||
if lookup_source_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._lookup_source_meta,
|
|
||||||
params=lookup_source_params, opcode=opcode))
|
|
||||||
if lookup_param:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._lookup_meta,
|
|
||||||
params=lookup_param, opcode=opcode))
|
|
||||||
|
|
||||||
if opcode == 'delete':
|
|
||||||
if sys_domain_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_domain_meta,
|
|
||||||
params=sys_domain_params, opcode=opcode))
|
|
||||||
if sys_server_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_server_meta,
|
|
||||||
params=sys_server_params, opcode=opcode))
|
|
||||||
if self._want['vrf'] != 'default':
|
|
||||||
self._result['warnings'] = ["name-servers delete operation with non-default vrf is a success, "
|
|
||||||
"but with rpc-error. Recommended to use 'ignore_errors' option with the task as a workaround"]
|
|
||||||
elif opcode == 'merge':
|
|
||||||
if add_domain_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_domain_meta,
|
|
||||||
params=add_domain_params, opcode=opcode))
|
|
||||||
if del_domain_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_domain_meta,
|
|
||||||
params=del_domain_params, opcode="delete"))
|
|
||||||
|
|
||||||
if add_server_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_server_meta,
|
|
||||||
params=add_server_params, opcode=opcode))
|
|
||||||
if del_server_params:
|
|
||||||
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_server_meta,
|
|
||||||
params=del_server_params, opcode="delete"))
|
|
||||||
|
|
||||||
diff = None
|
|
||||||
if _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)
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
if self._module._diff:
|
|
||||||
self._result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
self._result['xml'] = _edit_filter_list
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_obj_to_xml_rpc()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
""" Main entry point for Ansible module execution
|
|
||||||
"""
|
|
||||||
argument_spec = dict(
|
|
||||||
hostname=dict(),
|
|
||||||
vrf=dict(type='str', default='default'),
|
|
||||||
domain_name=dict(),
|
|
||||||
domain_search=dict(type='list'),
|
|
||||||
|
|
||||||
name_servers=dict(type='list'),
|
|
||||||
lookup_source=dict(),
|
|
||||||
lookup_enabled=dict(type='bool', default=True),
|
|
||||||
|
|
||||||
state=dict(choices=['present', 'absent'], default='present')
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
config_object = None
|
|
||||||
if is_cliconf(module):
|
|
||||||
# Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported
|
|
||||||
# module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
|
|
||||||
# version='2.9')
|
|
||||||
config_object = CliConfiguration(module)
|
|
||||||
elif is_netconf(module):
|
|
||||||
config_object = NCConfiguration(module)
|
|
||||||
|
|
||||||
result = None
|
|
||||||
if config_object:
|
|
||||||
result = config_object.run()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,721 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# (c) 2017, Ansible by Red Hat, inc
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'network'}
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
module: iosxr_user
|
|
||||||
version_added: "2.4"
|
|
||||||
author:
|
|
||||||
- "Trishna Guha (@trishnaguha)"
|
|
||||||
- "Sebastiaan van Doesselaar (@sebasdoes)"
|
|
||||||
- "Kedar Kekan (@kedarX)"
|
|
||||||
short_description: Manage the aggregate of local users on Cisco IOS XR device
|
|
||||||
description:
|
|
||||||
- This module provides declarative management of the local usernames
|
|
||||||
configured on network devices. It allows playbooks to manage
|
|
||||||
either individual usernames or the aggregate of usernames in the
|
|
||||||
current running config. It also supports purging usernames from the
|
|
||||||
configuration that are not explicitly defined.
|
|
||||||
extends_documentation_fragment: iosxr
|
|
||||||
notes:
|
|
||||||
- This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html).
|
|
||||||
- Tested against IOS XRv 6.1.3
|
|
||||||
options:
|
|
||||||
aggregate:
|
|
||||||
description:
|
|
||||||
- The set of username objects to be configured on the remote
|
|
||||||
Cisco IOS XR device. The list entries can either be the username
|
|
||||||
or a hash of username and properties. This argument is mutually
|
|
||||||
exclusive with the C(name) argument.
|
|
||||||
aliases: ['users', 'collection']
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- The username to be configured on the Cisco IOS XR device.
|
|
||||||
This argument accepts a string value and is mutually exclusive
|
|
||||||
with the C(aggregate) argument.
|
|
||||||
Please note that this option is not same as C(provider username).
|
|
||||||
configured_password:
|
|
||||||
description:
|
|
||||||
- The password to be configured on the Cisco IOS XR device. The password
|
|
||||||
needs to be provided in clear text. Password is encrypted on the device
|
|
||||||
when used with I(cli) and by Ansible when used with I(netconf)
|
|
||||||
using the same MD5 hash technique with salt size of 3.
|
|
||||||
Please note that this option is not same as C(provider password).
|
|
||||||
update_password:
|
|
||||||
description:
|
|
||||||
- Since passwords are encrypted in the device running config, this
|
|
||||||
argument will instruct the module when to change the password. When
|
|
||||||
set to C(always), the password will always be updated in the device
|
|
||||||
and when set to C(on_create) the password will be updated only if
|
|
||||||
the username is created.
|
|
||||||
default: always
|
|
||||||
choices: ['on_create', 'always']
|
|
||||||
group:
|
|
||||||
description:
|
|
||||||
- Configures the group for the username in the
|
|
||||||
device running configuration. The argument accepts a string value
|
|
||||||
defining the group name. This argument does not check if the group
|
|
||||||
has been configured on the device.
|
|
||||||
aliases: ['role']
|
|
||||||
groups:
|
|
||||||
version_added: "2.5"
|
|
||||||
description:
|
|
||||||
- Configures the groups for the username in the device running
|
|
||||||
configuration. The argument accepts a list of group names.
|
|
||||||
This argument does not check if the group has been configured
|
|
||||||
on the device. It is similar to the aggregate command for
|
|
||||||
usernames, but lets you configure multiple groups for the user(s).
|
|
||||||
purge:
|
|
||||||
description:
|
|
||||||
- Instructs the module to consider the
|
|
||||||
resource definition absolute. It will remove any previously
|
|
||||||
configured usernames on the device with the exception of the
|
|
||||||
`admin` user and the current defined set of users.
|
|
||||||
type: bool
|
|
||||||
default: false
|
|
||||||
admin:
|
|
||||||
description:
|
|
||||||
- Enters into administration configuration mode for making config
|
|
||||||
changes to the device.
|
|
||||||
- Applicable only when using network_cli transport
|
|
||||||
type: bool
|
|
||||||
default: false
|
|
||||||
version_added: "2.8"
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Configures the state of the username definition
|
|
||||||
as it relates to the device operational configuration. When set
|
|
||||||
to I(present), the username(s) should be configured in the device active
|
|
||||||
configuration and when set to I(absent) the username(s) should not be
|
|
||||||
in the device active configuration
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent']
|
|
||||||
public_key:
|
|
||||||
version_added: "2.5"
|
|
||||||
description:
|
|
||||||
- Configures the contents of the public keyfile to upload to the IOS-XR node.
|
|
||||||
This enables users to login using the accompanying private key. IOS-XR
|
|
||||||
only accepts base64 decoded files, so this will be decoded and uploaded
|
|
||||||
to the node. Do note that this requires an OpenSSL public key file,
|
|
||||||
PuTTy generated files will not work! Mutually exclusive with
|
|
||||||
public_key_contents. If used with multiple users in aggregates, then the
|
|
||||||
same key file is used for all users.
|
|
||||||
public_key_contents:
|
|
||||||
version_added: "2.5"
|
|
||||||
description:
|
|
||||||
- Configures the contents of the public keyfile to upload to the IOS-XR node.
|
|
||||||
This enables users to login using the accompanying private key. IOS-XR
|
|
||||||
only accepts base64 decoded files, so this will be decoded and uploaded
|
|
||||||
to the node. Do note that this requires an OpenSSL public key file,
|
|
||||||
PuTTy generated files will not work! Mutually exclusive with
|
|
||||||
public_key.If used with multiple users in aggregates, then the
|
|
||||||
same key file is used for all users.
|
|
||||||
requirements:
|
|
||||||
- ncclient >= 0.5.3 when using netconf
|
|
||||||
- lxml >= 4.1.1 when using netconf
|
|
||||||
- base64 when using I(public_key_contents) or I(public_key)
|
|
||||||
- paramiko when using I(public_key_contents) or I(public_key)
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- name: create a new user
|
|
||||||
iosxr_user:
|
|
||||||
name: ansible
|
|
||||||
configured_password: mypassword
|
|
||||||
state: present
|
|
||||||
- name: create a new user in admin configuration mode
|
|
||||||
iosxr_user:
|
|
||||||
name: ansible
|
|
||||||
configured_password: mypassword
|
|
||||||
admin: True
|
|
||||||
state: present
|
|
||||||
- name: remove all users except admin
|
|
||||||
iosxr_user:
|
|
||||||
purge: True
|
|
||||||
- name: set multiple users to group sys-admin
|
|
||||||
iosxr_user:
|
|
||||||
aggregate:
|
|
||||||
- name: netop
|
|
||||||
- name: netend
|
|
||||||
group: sysadmin
|
|
||||||
state: present
|
|
||||||
- name: set multiple users to multiple groups
|
|
||||||
iosxr_user:
|
|
||||||
aggregate:
|
|
||||||
- name: netop
|
|
||||||
- name: netend
|
|
||||||
groups:
|
|
||||||
- sysadmin
|
|
||||||
- root-system
|
|
||||||
state: present
|
|
||||||
- name: Change Password for User netop
|
|
||||||
iosxr_user:
|
|
||||||
name: netop
|
|
||||||
configured_password: "{{ new_password }}"
|
|
||||||
update_password: always
|
|
||||||
state: present
|
|
||||||
- name: Add private key authentication for user netop
|
|
||||||
iosxr_user:
|
|
||||||
name: netop
|
|
||||||
state: present
|
|
||||||
public_key_contents: "{{ lookup('file', '/home/netop/.ssh/id_rsa.pub' }}"
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
commands:
|
|
||||||
description: The list of configuration mode commands to send to the device
|
|
||||||
returned: always
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
- username ansible secret password group sysadmin
|
|
||||||
- username admin secret admin
|
|
||||||
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\">
|
|
||||||
<aaa xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-lib-cfg\">
|
|
||||||
<usernames xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-locald-cfg\">
|
|
||||||
<username xc:operation=\"merge\">
|
|
||||||
<name>test7</name>
|
|
||||||
<usergroup-under-usernames>
|
|
||||||
<usergroup-under-username>
|
|
||||||
<name>sysadmin</name>
|
|
||||||
</usergroup-under-username>
|
|
||||||
</usergroup-under-usernames>
|
|
||||||
<secret>$1$ZsXC$zZ50wqhDC543ZWQkkAHLW0</secret>
|
|
||||||
</username>
|
|
||||||
</usernames>
|
|
||||||
</aaa>
|
|
||||||
</config>'
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from functools import partial
|
|
||||||
from copy import deepcopy
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.compat.paramiko import paramiko
|
|
||||||
from ansible.module_utils.network.common.utils import remove_default_spec
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config, is_netconf, is_cliconf
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, build_xml, etree_findall
|
|
||||||
|
|
||||||
try:
|
|
||||||
from base64 import b64decode
|
|
||||||
HAS_B64 = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_B64 = False
|
|
||||||
|
|
||||||
|
|
||||||
class PublicKeyManager(object):
|
|
||||||
def __init__(self, module, result):
|
|
||||||
self._module = module
|
|
||||||
self._result = result
|
|
||||||
|
|
||||||
def convert_key_to_base64(self):
|
|
||||||
""" IOS-XR only accepts base64 decoded files, this converts the public key to a temp file.
|
|
||||||
"""
|
|
||||||
if self._module.params['aggregate']:
|
|
||||||
name = 'aggregate'
|
|
||||||
else:
|
|
||||||
name = self._module.params['name']
|
|
||||||
|
|
||||||
if self._module.params['public_key_contents']:
|
|
||||||
key = self._module.params['public_key_contents']
|
|
||||||
elif self._module.params['public_key']:
|
|
||||||
readfile = open(self._module.params['public_key'], 'r')
|
|
||||||
key = readfile.read()
|
|
||||||
splitfile = key.split()[1]
|
|
||||||
|
|
||||||
base64key = b64decode(splitfile)
|
|
||||||
base64file = open('/tmp/publickey_%s.b64' % (name), 'wb')
|
|
||||||
base64file.write(base64key)
|
|
||||||
base64file.close()
|
|
||||||
|
|
||||||
return '/tmp/publickey_%s.b64' % (name)
|
|
||||||
|
|
||||||
def copy_key_to_node(self, base64keyfile):
|
|
||||||
""" Copy key to IOS-XR node. We use SFTP because older IOS-XR versions don't handle SCP very well.
|
|
||||||
"""
|
|
||||||
provider = self._module.params.get("provider") or {}
|
|
||||||
node = provider.get('host')
|
|
||||||
if node is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
user = provider.get('username')
|
|
||||||
if user is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
password = provider.get('password')
|
|
||||||
ssh_keyfile = provider.get('ssh_keyfile')
|
|
||||||
|
|
||||||
if self._module.params['aggregate']:
|
|
||||||
name = 'aggregate'
|
|
||||||
else:
|
|
||||||
name = self._module.params['name']
|
|
||||||
|
|
||||||
src = base64keyfile
|
|
||||||
dst = '/harddisk:/publickey_%s.b64' % (name)
|
|
||||||
|
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
if not ssh_keyfile:
|
|
||||||
ssh.connect(node, username=user, password=password)
|
|
||||||
else:
|
|
||||||
ssh.connect(node, username=user, allow_agent=True)
|
|
||||||
sftp = ssh.open_sftp()
|
|
||||||
sftp.put(src, dst)
|
|
||||||
sftp.close()
|
|
||||||
ssh.close()
|
|
||||||
|
|
||||||
def addremovekey(self, command):
|
|
||||||
""" Add or remove key based on command
|
|
||||||
"""
|
|
||||||
provider = self._module.params.get("provider") or {}
|
|
||||||
node = provider.get('host')
|
|
||||||
if node is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
user = provider.get('username')
|
|
||||||
if user is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
password = provider.get('password')
|
|
||||||
ssh_keyfile = provider.get('ssh_keyfile')
|
|
||||||
|
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
if not ssh_keyfile:
|
|
||||||
ssh.connect(node, username=user, password=password)
|
|
||||||
else:
|
|
||||||
ssh.connect(node, username=user, allow_agent=True)
|
|
||||||
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command('%s \r' % (command))
|
|
||||||
readmsg = ssh_stdout.read(100) # We need to read a bit to actually apply for some reason
|
|
||||||
if ('already' in readmsg) or ('removed' in readmsg) or ('really' in readmsg):
|
|
||||||
ssh_stdin.write('yes\r')
|
|
||||||
ssh_stdout.read(1) # We need to read a bit to actually apply for some reason
|
|
||||||
ssh.close()
|
|
||||||
|
|
||||||
return readmsg
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if self._module.params['state'] == 'present':
|
|
||||||
if not self._module.check_mode:
|
|
||||||
key = self.convert_key_to_base64()
|
|
||||||
copykeys = self.copy_key_to_node(key)
|
|
||||||
if copykeys is False:
|
|
||||||
self._result['warnings'].append('Please set up your provider before running this playbook')
|
|
||||||
|
|
||||||
if self._module.params['aggregate']:
|
|
||||||
for user in self._module.params['aggregate']:
|
|
||||||
cmdtodo = "admin crypto key import authentication rsa username %s harddisk:/publickey_aggregate.b64" % (user)
|
|
||||||
addremove = self.addremovekey(cmdtodo)
|
|
||||||
if addremove is False:
|
|
||||||
self._result['warnings'].append('Please set up your provider before running this playbook')
|
|
||||||
else:
|
|
||||||
cmdtodo = "admin crypto key import authentication rsa username %s harddisk:/publickey_%s.b64" % \
|
|
||||||
(self._module.params['name'], self._module.params['name'])
|
|
||||||
addremove = self.addremovekey(cmdtodo)
|
|
||||||
if addremove is False:
|
|
||||||
self._result['warnings'].append('Please set up your provider before running this playbook')
|
|
||||||
elif self._module.params['state'] == 'absent':
|
|
||||||
if not self._module.check_mode:
|
|
||||||
if self._module.params['aggregate']:
|
|
||||||
for user in self._module.params['aggregate']:
|
|
||||||
cmdtodo = "admin crypto key zeroize authentication rsa username %s" % (user)
|
|
||||||
addremove = self.addremovekey(cmdtodo)
|
|
||||||
if addremove is False:
|
|
||||||
self._result['warnings'].append('Please set up your provider before running this playbook')
|
|
||||||
else:
|
|
||||||
cmdtodo = "admin crypto key zeroize authentication rsa username %s" % (self._module.params['name'])
|
|
||||||
addremove = self.addremovekey(cmdtodo)
|
|
||||||
if addremove is False:
|
|
||||||
self._result['warnings'].append('Please set up your provider before running this playbook')
|
|
||||||
elif self._module.params['purge'] is True:
|
|
||||||
if not self._module.check_mode:
|
|
||||||
cmdtodo = "admin crypto key zeroize authentication rsa all"
|
|
||||||
addremove = self.addremovekey(cmdtodo)
|
|
||||||
if addremove is False:
|
|
||||||
self._result['warnings'].append('Please set up your provider before running this playbook')
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
def search_obj_in_list(name, lst):
|
|
||||||
for o in lst:
|
|
||||||
if o['name'] == name:
|
|
||||||
return o
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigBase(object):
|
|
||||||
def __init__(self, module, result, flag=None):
|
|
||||||
self._module = module
|
|
||||||
self._result = result
|
|
||||||
self._want = list()
|
|
||||||
self._have = list()
|
|
||||||
|
|
||||||
def get_param_value(self, key, item):
|
|
||||||
# if key doesn't exist in the item, get it from module.params
|
|
||||||
if not item.get(key):
|
|
||||||
value = self._module.params[key]
|
|
||||||
|
|
||||||
# if key does exist, do a type check on it to validate it
|
|
||||||
else:
|
|
||||||
value_type = self._module.argument_spec[key].get('type', 'str')
|
|
||||||
type_checker = self._module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
|
|
||||||
type_checker(item[key])
|
|
||||||
value = item[key]
|
|
||||||
|
|
||||||
# validate the param value (if validator func exists)
|
|
||||||
validator = globals().get('validate_%s' % key)
|
|
||||||
if all((value, validator)):
|
|
||||||
validator(value, self._module)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def map_params_to_obj(self):
|
|
||||||
users = self._module.params['aggregate']
|
|
||||||
|
|
||||||
aggregate = list()
|
|
||||||
if not users:
|
|
||||||
if not self._module.params['name'] and self._module.params['purge']:
|
|
||||||
pass
|
|
||||||
elif not self._module.params['name']:
|
|
||||||
self._module.fail_json(msg='username is required')
|
|
||||||
else:
|
|
||||||
aggregate = [{'name': self._module.params['name']}]
|
|
||||||
else:
|
|
||||||
for item in users:
|
|
||||||
if not isinstance(item, dict):
|
|
||||||
aggregate.append({'name': item})
|
|
||||||
elif 'name' not in item:
|
|
||||||
self._module.fail_json(msg='name is required')
|
|
||||||
else:
|
|
||||||
aggregate.append(item)
|
|
||||||
|
|
||||||
for item in aggregate:
|
|
||||||
get_value = partial(self.get_param_value, item=item)
|
|
||||||
item['configured_password'] = get_value('configured_password')
|
|
||||||
item['group'] = get_value('group')
|
|
||||||
item['groups'] = get_value('groups')
|
|
||||||
item['state'] = get_value('state')
|
|
||||||
self._want.append(item)
|
|
||||||
|
|
||||||
|
|
||||||
class CliConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module, result):
|
|
||||||
super(CliConfiguration, self).__init__(module, result)
|
|
||||||
|
|
||||||
def map_config_to_obj(self):
|
|
||||||
data = get_config(self._module, config_filter='username')
|
|
||||||
users = data.strip().rstrip('!').split('!')
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
user_config = user.strip().splitlines()
|
|
||||||
|
|
||||||
name = user_config[0].strip().split()[1]
|
|
||||||
group = None
|
|
||||||
|
|
||||||
if len(user_config) > 1:
|
|
||||||
group_or_secret = user_config[1].strip().split()
|
|
||||||
if group_or_secret[0] == 'group':
|
|
||||||
group = group_or_secret[1]
|
|
||||||
|
|
||||||
obj = {
|
|
||||||
'name': name,
|
|
||||||
'state': 'present',
|
|
||||||
'configured_password': None,
|
|
||||||
'group': group
|
|
||||||
}
|
|
||||||
self._have.append(obj)
|
|
||||||
|
|
||||||
def map_obj_to_commands(self):
|
|
||||||
commands = list()
|
|
||||||
|
|
||||||
for w in self._want:
|
|
||||||
name = w['name']
|
|
||||||
state = w['state']
|
|
||||||
|
|
||||||
obj_in_have = search_obj_in_list(name, self._have)
|
|
||||||
|
|
||||||
if state == 'absent' and obj_in_have:
|
|
||||||
commands.append('no username ' + name)
|
|
||||||
elif state == 'present' and not obj_in_have:
|
|
||||||
user_cmd = 'username ' + name
|
|
||||||
commands.append(user_cmd)
|
|
||||||
|
|
||||||
if w['configured_password']:
|
|
||||||
commands.append(user_cmd + ' secret ' + w['configured_password'])
|
|
||||||
if w['group']:
|
|
||||||
commands.append(user_cmd + ' group ' + w['group'])
|
|
||||||
elif w['groups']:
|
|
||||||
for group in w['groups']:
|
|
||||||
commands.append(user_cmd + ' group ' + group)
|
|
||||||
|
|
||||||
elif state == 'present' and obj_in_have:
|
|
||||||
user_cmd = 'username ' + name
|
|
||||||
|
|
||||||
if self._module.params['update_password'] == 'always' and w['configured_password']:
|
|
||||||
commands.append(user_cmd + ' secret ' + w['configured_password'])
|
|
||||||
if w['group'] and w['group'] != obj_in_have['group']:
|
|
||||||
commands.append(user_cmd + ' group ' + w['group'])
|
|
||||||
elif w['groups']:
|
|
||||||
for group in w['groups']:
|
|
||||||
commands.append(user_cmd + ' group ' + group)
|
|
||||||
|
|
||||||
if self._module.params['purge']:
|
|
||||||
want_users = [x['name'] for x in self._want]
|
|
||||||
have_users = [x['name'] for x in self._have]
|
|
||||||
for item in set(have_users).difference(set(want_users)):
|
|
||||||
if item != 'admin':
|
|
||||||
commands.append('no username %s' % item)
|
|
||||||
|
|
||||||
if 'no username admin' in commands:
|
|
||||||
self._module.fail_json(msg='cannot delete the `admin` account')
|
|
||||||
|
|
||||||
self._result['commands'] = []
|
|
||||||
if commands:
|
|
||||||
commit = not self._module.check_mode
|
|
||||||
admin = self._module.params['admin']
|
|
||||||
diff = load_config(self._module, commands, commit=commit, admin=admin)
|
|
||||||
if diff:
|
|
||||||
self._result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
self._result['commands'] = commands
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_config_to_obj()
|
|
||||||
self.map_obj_to_commands()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
class NCConfiguration(ConfigBase):
|
|
||||||
def __init__(self, module, result):
|
|
||||||
super(NCConfiguration, self).__init__(module, result)
|
|
||||||
self._locald_meta = collections.OrderedDict()
|
|
||||||
self._locald_group_meta = collections.OrderedDict()
|
|
||||||
|
|
||||||
def generate_md5_hash(self, arg):
|
|
||||||
'''
|
|
||||||
Generate MD5 hash with randomly generated salt size of 3.
|
|
||||||
:param arg:
|
|
||||||
:return passwd:
|
|
||||||
'''
|
|
||||||
cmd = "openssl passwd -salt `openssl rand -base64 3` -1 "
|
|
||||||
return os.popen(cmd + arg).readlines()[0].strip()
|
|
||||||
|
|
||||||
def map_obj_to_xml_rpc(self):
|
|
||||||
self._locald_meta.update([
|
|
||||||
('aaa_locald', {'xpath': 'aaa/usernames', 'tag': True, 'ns': True}),
|
|
||||||
('username', {'xpath': 'aaa/usernames/username', 'tag': True, 'attrib': "operation"}),
|
|
||||||
('a:name', {'xpath': 'aaa/usernames/username/name'}),
|
|
||||||
('a:configured_password', {'xpath': 'aaa/usernames/username/secret', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
|
|
||||||
self._locald_group_meta.update([
|
|
||||||
('aaa_locald', {'xpath': 'aaa/usernames', 'tag': True, 'ns': True}),
|
|
||||||
('username', {'xpath': 'aaa/usernames/username', 'tag': True, 'attrib': "operation"}),
|
|
||||||
('a:name', {'xpath': 'aaa/usernames/username/name'}),
|
|
||||||
('usergroups', {'xpath': 'aaa/usernames/username/usergroup-under-usernames', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('usergroup', {'xpath': 'aaa/usernames/username/usergroup-under-usernames/usergroup-under-username', 'tag': True, 'operation': 'edit'}),
|
|
||||||
('a:group', {'xpath': 'aaa/usernames/username/usergroup-under-usernames/usergroup-under-username/name', 'operation': 'edit'}),
|
|
||||||
])
|
|
||||||
|
|
||||||
state = self._module.params['state']
|
|
||||||
_get_filter = build_xml('aaa', opcode="filter")
|
|
||||||
running = get_config(self._module, source='running', config_filter=_get_filter)
|
|
||||||
|
|
||||||
elements = etree_findall(running, 'username')
|
|
||||||
users = list()
|
|
||||||
for element in elements:
|
|
||||||
name_list = etree_findall(element, 'name')
|
|
||||||
users.append(name_list[0].text)
|
|
||||||
list_size = len(name_list)
|
|
||||||
if list_size == 1:
|
|
||||||
self._have.append({'name': name_list[0].text, 'group': None, 'groups': None})
|
|
||||||
elif list_size == 2:
|
|
||||||
self._have.append({'name': name_list[0].text, 'group': name_list[1].text, 'groups': None})
|
|
||||||
elif list_size > 2:
|
|
||||||
name_iter = iter(name_list)
|
|
||||||
next(name_iter)
|
|
||||||
tmp_list = list()
|
|
||||||
for name in name_iter:
|
|
||||||
tmp_list.append(name.text)
|
|
||||||
|
|
||||||
self._have.append({'name': name_list[0].text, 'group': None, 'groups': tmp_list})
|
|
||||||
|
|
||||||
locald_params = list()
|
|
||||||
locald_group_params = list()
|
|
||||||
opcode = None
|
|
||||||
|
|
||||||
if state == 'absent':
|
|
||||||
opcode = "delete"
|
|
||||||
for want_item in self._want:
|
|
||||||
if want_item['name'] in users:
|
|
||||||
want_item['configured_password'] = None
|
|
||||||
locald_params.append(want_item)
|
|
||||||
elif state == 'present':
|
|
||||||
opcode = "merge"
|
|
||||||
for want_item in self._want:
|
|
||||||
if want_item['name'] not in users:
|
|
||||||
want_item['configured_password'] = self.generate_md5_hash(want_item['configured_password'])
|
|
||||||
locald_params.append(want_item)
|
|
||||||
|
|
||||||
if want_item['group'] is not None:
|
|
||||||
locald_group_params.append(want_item)
|
|
||||||
if want_item['groups'] is not None:
|
|
||||||
for group in want_item['groups']:
|
|
||||||
want_item['group'] = group
|
|
||||||
locald_group_params.append(want_item.copy())
|
|
||||||
else:
|
|
||||||
if self._module.params['update_password'] == 'always' and want_item['configured_password'] is not None:
|
|
||||||
want_item['configured_password'] = self.generate_md5_hash(want_item['configured_password'])
|
|
||||||
locald_params.append(want_item)
|
|
||||||
else:
|
|
||||||
want_item['configured_password'] = None
|
|
||||||
|
|
||||||
obj_in_have = search_obj_in_list(want_item['name'], self._have)
|
|
||||||
if want_item['group'] is not None and want_item['group'] != obj_in_have['group']:
|
|
||||||
locald_group_params.append(want_item)
|
|
||||||
elif want_item['groups'] is not None:
|
|
||||||
for group in want_item['groups']:
|
|
||||||
want_item['group'] = group
|
|
||||||
locald_group_params.append(want_item.copy())
|
|
||||||
|
|
||||||
purge_params = list()
|
|
||||||
if self._module.params['purge']:
|
|
||||||
want_users = [x['name'] for x in self._want]
|
|
||||||
have_users = [x['name'] for x in self._have]
|
|
||||||
for item in set(have_users).difference(set(want_users)):
|
|
||||||
if item != 'admin':
|
|
||||||
purge_params.append({'name': item})
|
|
||||||
|
|
||||||
self._result['xml'] = []
|
|
||||||
_edit_filter_list = list()
|
|
||||||
if opcode is not None:
|
|
||||||
if locald_params:
|
|
||||||
_edit_filter_list.append(build_xml('aaa', xmap=self._locald_meta,
|
|
||||||
params=locald_params, opcode=opcode))
|
|
||||||
|
|
||||||
if locald_group_params:
|
|
||||||
_edit_filter_list.append(build_xml('aaa', xmap=self._locald_group_meta,
|
|
||||||
params=locald_group_params, opcode=opcode))
|
|
||||||
|
|
||||||
if purge_params:
|
|
||||||
_edit_filter_list.append(build_xml('aaa', xmap=self._locald_meta,
|
|
||||||
params=purge_params, opcode="delete"))
|
|
||||||
|
|
||||||
diff = None
|
|
||||||
if _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)
|
|
||||||
|
|
||||||
if diff:
|
|
||||||
if self._module._diff:
|
|
||||||
self._result['diff'] = dict(prepared=diff)
|
|
||||||
|
|
||||||
self._result['xml'] = _edit_filter_list
|
|
||||||
self._result['changed'] = True
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.map_params_to_obj()
|
|
||||||
self.map_obj_to_xml_rpc()
|
|
||||||
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
""" main entry point for module execution
|
|
||||||
"""
|
|
||||||
element_spec = dict(
|
|
||||||
name=dict(),
|
|
||||||
|
|
||||||
configured_password=dict(no_log=True),
|
|
||||||
update_password=dict(default='always', choices=['on_create', 'always']),
|
|
||||||
|
|
||||||
admin=dict(type='bool', default=False),
|
|
||||||
|
|
||||||
public_key=dict(),
|
|
||||||
public_key_contents=dict(),
|
|
||||||
|
|
||||||
group=dict(aliases=['role']),
|
|
||||||
groups=dict(type='list', elements='dict'),
|
|
||||||
|
|
||||||
state=dict(default='present', choices=['present', 'absent'])
|
|
||||||
)
|
|
||||||
aggregate_spec = deepcopy(element_spec)
|
|
||||||
aggregate_spec['name'] = dict(required=True)
|
|
||||||
|
|
||||||
# remove default in aggregate spec, to handle common arguments
|
|
||||||
remove_default_spec(aggregate_spec)
|
|
||||||
|
|
||||||
mutually_exclusive = [('name', 'aggregate'), ('public_key', 'public_key_contents'), ('group', 'groups')]
|
|
||||||
|
|
||||||
argument_spec = dict(
|
|
||||||
aggregate=dict(type='list', elements='dict', options=aggregate_spec, aliases=['users', 'collection'],
|
|
||||||
mutually_exclusive=mutually_exclusive),
|
|
||||||
purge=dict(type='bool', default=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
argument_spec.update(element_spec)
|
|
||||||
argument_spec.update(iosxr_argument_spec)
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec,
|
|
||||||
mutually_exclusive=mutually_exclusive,
|
|
||||||
supports_check_mode=True)
|
|
||||||
|
|
||||||
if (module.params['public_key_contents'] or module.params['public_key']):
|
|
||||||
if not HAS_B64:
|
|
||||||
module.fail_json(
|
|
||||||
msg='library base64 is required but does not appear to be '
|
|
||||||
'installed. It can be installed using `pip install base64`'
|
|
||||||
)
|
|
||||||
if paramiko is None:
|
|
||||||
module.fail_json(
|
|
||||||
msg='library paramiko is required but does not appear to be '
|
|
||||||
'installed. It can be installed using `pip install paramiko`'
|
|
||||||
)
|
|
||||||
|
|
||||||
result = {'changed': False, 'warnings': []}
|
|
||||||
|
|
||||||
config_object = None
|
|
||||||
if is_cliconf(module):
|
|
||||||
# Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported
|
|
||||||
# module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead",
|
|
||||||
# version='2.9')
|
|
||||||
config_object = CliConfiguration(module, result)
|
|
||||||
elif is_netconf(module):
|
|
||||||
config_object = NCConfiguration(module, result)
|
|
||||||
|
|
||||||
if config_object:
|
|
||||||
result = config_object.run()
|
|
||||||
|
|
||||||
if module.params['public_key_contents'] or module.params['public_key']:
|
|
||||||
pubkey_object = PublicKeyManager(module, result)
|
|
||||||
result = pubkey_object.run()
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,106 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2016 Red Hat Inc.
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import iosxr_provider_spec
|
|
||||||
from ansible.plugins.action.network import ActionModule as ActionNetworkModule
|
|
||||||
from ansible.module_utils.network.common.utils import load_provider
|
|
||||||
from ansible.utils.display import Display
|
|
||||||
|
|
||||||
display = Display()
|
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionNetworkModule):
|
|
||||||
|
|
||||||
def run(self, tmp=None, task_vars=None):
|
|
||||||
del tmp # tmp no longer has any effect
|
|
||||||
|
|
||||||
module_name = self._task.action.split('.')[-1]
|
|
||||||
self._config_module = True if module_name == 'iosxr_config' else False
|
|
||||||
force_cli = module_name in ('iosxr_netconf', 'iosxr_config', 'iosxr_command', 'iosxr_facts')
|
|
||||||
persistent_connection = self._play_context.connection.split('.')[-1]
|
|
||||||
warnings = []
|
|
||||||
|
|
||||||
if self._play_context.connection == 'local':
|
|
||||||
provider = load_provider(iosxr_provider_spec, self._task.args)
|
|
||||||
pc = copy.deepcopy(self._play_context)
|
|
||||||
pc.network_os = 'cisco.iosxr.iosxr'
|
|
||||||
if force_cli or provider['transport'] == 'cli':
|
|
||||||
pc.connection = 'ansible.netcommon.network_cli'
|
|
||||||
pc.port = int(provider['port'] or self._play_context.port or 22)
|
|
||||||
elif provider['transport'] == 'netconf':
|
|
||||||
pc.connection = 'ansible.netcommon.netconf'
|
|
||||||
pc.port = int(provider['port'] or self._play_context.port or 830)
|
|
||||||
else:
|
|
||||||
return {'failed': True, 'msg': 'Transport type %s is not valid for this module' % provider['transport']}
|
|
||||||
|
|
||||||
pc.remote_addr = provider['host'] or self._play_context.remote_addr
|
|
||||||
pc.port = int(provider['port'] or self._play_context.port or 22)
|
|
||||||
pc.remote_user = provider['username'] or self._play_context.connection_user
|
|
||||||
pc.password = provider['password'] or self._play_context.password
|
|
||||||
|
|
||||||
connection = self._shared_loader_obj.connection_loader.get('ansible.netcommon.persistent', pc, sys.stdin,
|
|
||||||
task_uuid=self._task._uuid)
|
|
||||||
|
|
||||||
# TODO: Remove below code after ansible minimal is cut out
|
|
||||||
if connection is None:
|
|
||||||
pc.network_os = 'iosxr'
|
|
||||||
if pc.connection.split('.')[-1] == 'netconf':
|
|
||||||
pc.connection = 'netconf'
|
|
||||||
else:
|
|
||||||
pc.connection = 'network_cli'
|
|
||||||
|
|
||||||
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
|
|
||||||
|
|
||||||
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
|
|
||||||
|
|
||||||
command_timeout = int(provider['timeout']) if provider['timeout'] else connection.get_option('persistent_command_timeout')
|
|
||||||
connection.set_options(direct={'persistent_command_timeout': command_timeout})
|
|
||||||
|
|
||||||
socket_path = connection.run()
|
|
||||||
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
|
|
||||||
if not socket_path:
|
|
||||||
return {'failed': True,
|
|
||||||
'msg': 'unable to open shell. Please see: ' +
|
|
||||||
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
|
|
||||||
|
|
||||||
task_vars['ansible_socket'] = socket_path
|
|
||||||
warnings.append(['connection local support for this module is deprecated and will be removed in version 2.14, use connection %s' % pc.connection])
|
|
||||||
elif persistent_connection in ('netconf', 'network_cli'):
|
|
||||||
if force_cli and persistent_connection != 'network_cli':
|
|
||||||
return {'failed': True, 'msg': 'Connection type %s is not valid for module %s' %
|
|
||||||
(self._play_context.connection, module_name)}
|
|
||||||
provider = self._task.args.get('provider', {})
|
|
||||||
if any(provider.values()):
|
|
||||||
display.warning('provider is unnecessary when using {0} and will be ignored'.format(self._play_context.connection))
|
|
||||||
del self._task.args['provider']
|
|
||||||
else:
|
|
||||||
return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
|
|
||||||
|
|
||||||
result = super(ActionModule, self).run(task_vars=task_vars)
|
|
||||||
if warnings:
|
|
||||||
if 'warnings' in result:
|
|
||||||
result['warnings'].extend(warnings)
|
|
||||||
else:
|
|
||||||
result['warnings'] = warnings
|
|
||||||
return result
|
|
@ -1,276 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2017 Red Hat Inc.
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
author: Ansible Networking Team
|
|
||||||
cliconf: iosxr
|
|
||||||
short_description: Use iosxr cliconf to run command on Cisco IOS XR platform
|
|
||||||
description:
|
|
||||||
- This iosxr plugin provides low level abstraction apis for
|
|
||||||
sending and receiving CLI commands from Cisco IOS XR network devices.
|
|
||||||
version_added: "2.4"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleConnectionFailure
|
|
||||||
from ansible.module_utils._text import to_text
|
|
||||||
from ansible.module_utils.common._collections_compat import Mapping
|
|
||||||
from ansible.module_utils.connection import ConnectionError
|
|
||||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
|
||||||
from ansible.module_utils.network.common.utils import to_list
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import sanitize_config, mask_config_blocks_from_diff
|
|
||||||
from ansible.plugins.cliconf import CliconfBase
|
|
||||||
|
|
||||||
|
|
||||||
class Cliconf(CliconfBase):
|
|
||||||
|
|
||||||
def get_device_info(self):
|
|
||||||
device_info = {}
|
|
||||||
|
|
||||||
device_info['network_os'] = 'iosxr'
|
|
||||||
reply = self.get('show version | utility head -n 20')
|
|
||||||
data = to_text(reply, errors='surrogate_or_strict').strip()
|
|
||||||
|
|
||||||
match = re.search(r'Version (\S+)$', data, re.M)
|
|
||||||
if match:
|
|
||||||
device_info['network_os_version'] = match.group(1)
|
|
||||||
|
|
||||||
match = re.search(r'image file is "(.+)"', data)
|
|
||||||
if match:
|
|
||||||
device_info['network_os_image'] = match.group(1)
|
|
||||||
|
|
||||||
model_search_strs = [r'^[Cc]isco (.+) \(revision', r'^[Cc]isco (\S+ \S+).+bytes of .*memory']
|
|
||||||
for item in model_search_strs:
|
|
||||||
match = re.search(item, data, re.M)
|
|
||||||
if match:
|
|
||||||
device_info['network_os_model'] = match.group(1)
|
|
||||||
break
|
|
||||||
|
|
||||||
match = re.search(r'^(.+) uptime', data, re.M)
|
|
||||||
if match:
|
|
||||||
device_info['network_os_hostname'] = match.group(1)
|
|
||||||
|
|
||||||
return device_info
|
|
||||||
|
|
||||||
def configure(self, admin=False, exclusive=False):
|
|
||||||
prompt = to_text(self._connection.get_prompt(), errors='surrogate_or_strict').strip()
|
|
||||||
if not prompt.endswith(')#'):
|
|
||||||
if admin and 'admin-' not in prompt:
|
|
||||||
self.send_command('admin')
|
|
||||||
if exclusive:
|
|
||||||
self.send_command('configure exclusive')
|
|
||||||
return
|
|
||||||
self.send_command('configure terminal')
|
|
||||||
|
|
||||||
def abort(self, admin=False):
|
|
||||||
prompt = to_text(self._connection.get_prompt(), errors='surrogate_or_strict').strip()
|
|
||||||
if prompt.endswith(')#'):
|
|
||||||
self.send_command('abort')
|
|
||||||
if admin and 'admin-' in prompt:
|
|
||||||
self.send_command('exit')
|
|
||||||
|
|
||||||
def get_config(self, source='running', format='text', flags=None):
|
|
||||||
if source not in ['running']:
|
|
||||||
raise ValueError("fetching configuration from %s is not supported" % source)
|
|
||||||
|
|
||||||
lookup = {'running': 'running-config'}
|
|
||||||
|
|
||||||
cmd = 'show {0} '.format(lookup[source])
|
|
||||||
cmd += ' '.join(to_list(flags))
|
|
||||||
cmd = cmd.strip()
|
|
||||||
|
|
||||||
return self.send_command(cmd)
|
|
||||||
|
|
||||||
def edit_config(self, candidate=None, commit=True, admin=False, exclusive=False, replace=None, comment=None, label=None):
|
|
||||||
operations = self.get_device_operations()
|
|
||||||
self.check_edit_config_capability(operations, candidate, commit, replace, comment)
|
|
||||||
|
|
||||||
resp = {}
|
|
||||||
results = []
|
|
||||||
requests = []
|
|
||||||
|
|
||||||
self.configure(admin=admin, exclusive=exclusive)
|
|
||||||
|
|
||||||
if replace:
|
|
||||||
candidate = 'load {0}'.format(replace)
|
|
||||||
|
|
||||||
for line in to_list(candidate):
|
|
||||||
if not isinstance(line, Mapping):
|
|
||||||
line = {'command': line}
|
|
||||||
cmd = line['command']
|
|
||||||
results.append(self.send_command(**line))
|
|
||||||
requests.append(cmd)
|
|
||||||
|
|
||||||
# Before any commit happend, we can get a real configuration
|
|
||||||
# diff from the device and make it available by the iosxr_config module.
|
|
||||||
# This information can be usefull either in check mode or normal mode.
|
|
||||||
resp['show_commit_config_diff'] = self.get('show commit changes diff')
|
|
||||||
|
|
||||||
if commit:
|
|
||||||
self.commit(comment=comment, label=label, replace=replace)
|
|
||||||
else:
|
|
||||||
self.discard_changes()
|
|
||||||
|
|
||||||
self.abort(admin=admin)
|
|
||||||
|
|
||||||
resp['request'] = requests
|
|
||||||
resp['response'] = results
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'):
|
|
||||||
diff = {}
|
|
||||||
device_operations = self.get_device_operations()
|
|
||||||
option_values = self.get_option_values()
|
|
||||||
|
|
||||||
if candidate is None and device_operations['supports_generate_diff']:
|
|
||||||
raise ValueError("candidate configuration is required to generate diff")
|
|
||||||
|
|
||||||
if diff_match not in option_values['diff_match']:
|
|
||||||
raise ValueError("'match' value %s in invalid, valid values are %s" % (diff_match, ', '.join(option_values['diff_match'])))
|
|
||||||
|
|
||||||
if diff_replace not in option_values['diff_replace']:
|
|
||||||
raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace'])))
|
|
||||||
|
|
||||||
# prepare candidate configuration
|
|
||||||
sanitized_candidate = sanitize_config(candidate)
|
|
||||||
candidate_obj = NetworkConfig(indent=1)
|
|
||||||
candidate_obj.load(sanitized_candidate)
|
|
||||||
|
|
||||||
if running and diff_match != 'none':
|
|
||||||
# running configuration
|
|
||||||
running = mask_config_blocks_from_diff(running, candidate, "ansible")
|
|
||||||
running = sanitize_config(running)
|
|
||||||
|
|
||||||
running_obj = NetworkConfig(indent=1, contents=running, ignore_lines=diff_ignore_lines)
|
|
||||||
configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace)
|
|
||||||
|
|
||||||
else:
|
|
||||||
configdiffobjs = candidate_obj.items
|
|
||||||
|
|
||||||
diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else ''
|
|
||||||
return diff
|
|
||||||
|
|
||||||
def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True, output=None, check_all=False):
|
|
||||||
if output:
|
|
||||||
raise ValueError("'output' value %s is not supported for get" % output)
|
|
||||||
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
|
|
||||||
|
|
||||||
def commit(self, comment=None, label=None, replace=None):
|
|
||||||
cmd_obj = {}
|
|
||||||
if replace:
|
|
||||||
cmd_obj['command'] = 'commit replace'
|
|
||||||
cmd_obj['prompt'] = 'This commit will replace or remove the entire running configuration'
|
|
||||||
cmd_obj['answer'] = 'yes'
|
|
||||||
else:
|
|
||||||
if comment and label:
|
|
||||||
cmd_obj['command'] = 'commit label {0} comment {1}'.format(label, comment)
|
|
||||||
elif comment:
|
|
||||||
cmd_obj['command'] = 'commit comment {0}'.format(comment)
|
|
||||||
elif label:
|
|
||||||
cmd_obj['command'] = 'commit label {0}'.format(label)
|
|
||||||
else:
|
|
||||||
cmd_obj['command'] = 'commit show-error'
|
|
||||||
# In some cases even a normal commit, i.e., !replace,
|
|
||||||
# throws a prompt and we need to handle it before
|
|
||||||
# proceeding further
|
|
||||||
cmd_obj['prompt'] = '(C|c)onfirm'
|
|
||||||
cmd_obj['answer'] = 'y'
|
|
||||||
|
|
||||||
self.send_command(**cmd_obj)
|
|
||||||
|
|
||||||
def run_commands(self, commands=None, check_rc=True):
|
|
||||||
if commands is None:
|
|
||||||
raise ValueError("'commands' value is required")
|
|
||||||
responses = list()
|
|
||||||
for cmd in to_list(commands):
|
|
||||||
if not isinstance(cmd, Mapping):
|
|
||||||
cmd = {'command': cmd}
|
|
||||||
|
|
||||||
output = cmd.pop('output', None)
|
|
||||||
if output:
|
|
||||||
raise ValueError("'output' value %s is not supported for run_commands" % output)
|
|
||||||
|
|
||||||
try:
|
|
||||||
out = self.send_command(**cmd)
|
|
||||||
except AnsibleConnectionFailure as e:
|
|
||||||
if check_rc:
|
|
||||||
raise
|
|
||||||
out = getattr(e, 'err', e)
|
|
||||||
|
|
||||||
if out is not None:
|
|
||||||
try:
|
|
||||||
out = to_text(out, errors='surrogate_or_strict').strip()
|
|
||||||
except UnicodeError:
|
|
||||||
raise ConnectionError(message=u'Failed to decode output from %s: %s' % (cmd, to_text(out)))
|
|
||||||
|
|
||||||
try:
|
|
||||||
out = json.loads(out)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
responses.append(out)
|
|
||||||
return responses
|
|
||||||
|
|
||||||
def discard_changes(self):
|
|
||||||
self.send_command('abort')
|
|
||||||
|
|
||||||
def get_device_operations(self):
|
|
||||||
return {
|
|
||||||
'supports_diff_replace': True,
|
|
||||||
'supports_commit': True,
|
|
||||||
'supports_rollback': False,
|
|
||||||
'supports_defaults': False,
|
|
||||||
'supports_onbox_diff': False,
|
|
||||||
'supports_commit_comment': True,
|
|
||||||
'supports_multiline_delimiter': False,
|
|
||||||
'supports_diff_match': True,
|
|
||||||
'supports_diff_ignore_lines': True,
|
|
||||||
'supports_generate_diff': True,
|
|
||||||
'supports_replace': True,
|
|
||||||
'supports_admin': True,
|
|
||||||
'supports_commit_label': True
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_option_values(self):
|
|
||||||
return {
|
|
||||||
'format': ['text'],
|
|
||||||
'diff_match': ['line', 'strict', 'exact', 'none'],
|
|
||||||
'diff_replace': ['line', 'block', 'config'],
|
|
||||||
'output': []
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_capabilities(self):
|
|
||||||
result = super(Cliconf, self).get_capabilities()
|
|
||||||
result['rpc'] += ['commit', 'discard_changes', 'get_diff', 'configure', 'exit']
|
|
||||||
result['device_operations'] = self.get_device_operations()
|
|
||||||
result.update(self.get_option_values())
|
|
||||||
return json.dumps(result)
|
|
||||||
|
|
||||||
def set_cli_prompt_context(self):
|
|
||||||
"""
|
|
||||||
Make sure we are in the operational cli mode
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self._connection.connected:
|
|
||||||
self._update_cli_prompt_context(config_context=')#', exit_command='abort')
|
|
@ -1,65 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com>
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
|
||||||
|
|
||||||
# Standard files documentation fragment
|
|
||||||
DOCUMENTATION = r'''
|
|
||||||
options:
|
|
||||||
provider:
|
|
||||||
description:
|
|
||||||
- B(Deprecated)
|
|
||||||
- "Starting with Ansible 2.5 we recommend using C(connection: network_cli)."
|
|
||||||
- For more information please see the L(Network Guide, ../network/getting_started/network_differences.html#multiple-communication-protocols).
|
|
||||||
- HORIZONTALLINE
|
|
||||||
- A dict object containing connection details.
|
|
||||||
type: dict
|
|
||||||
suboptions:
|
|
||||||
host:
|
|
||||||
description:
|
|
||||||
- Specifies the DNS host name or address for connecting to the remote
|
|
||||||
device over the specified transport. The value of host is used as
|
|
||||||
the destination address for the transport.
|
|
||||||
type: str
|
|
||||||
required: true
|
|
||||||
port:
|
|
||||||
description:
|
|
||||||
- Specifies the port to use when building the connection to the remote device.
|
|
||||||
type: int
|
|
||||||
default: 22
|
|
||||||
username:
|
|
||||||
description:
|
|
||||||
- Configures the username to use to authenticate the connection to
|
|
||||||
the remote device. This value is used to authenticate
|
|
||||||
the SSH session. If the value is not specified in the task, the
|
|
||||||
value of environment variable C(ANSIBLE_NET_USERNAME) will be used instead.
|
|
||||||
type: str
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- Specifies the password to use to authenticate the connection to
|
|
||||||
the remote device. This value is used to authenticate
|
|
||||||
the SSH session. If the value is not specified in the task, the
|
|
||||||
value of environment variable C(ANSIBLE_NET_PASSWORD) will be used instead.
|
|
||||||
type: str
|
|
||||||
timeout:
|
|
||||||
description:
|
|
||||||
- Specifies the timeout in seconds for communicating with the network device
|
|
||||||
for either connecting or sending commands. If the timeout is
|
|
||||||
exceeded before the operation is completed, the module will error.
|
|
||||||
type: int
|
|
||||||
default: 10
|
|
||||||
ssh_keyfile:
|
|
||||||
description:
|
|
||||||
- Specifies the SSH key to use to authenticate the connection to
|
|
||||||
the remote device. This value is the path to the
|
|
||||||
key used to authenticate the SSH session. If the value is not specified
|
|
||||||
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
|
|
||||||
will be used instead.
|
|
||||||
type: path
|
|
||||||
notes:
|
|
||||||
- For more information on using Ansible to manage network devices see the :ref:`Ansible Network Guide <network_guide>`
|
|
||||||
- For more information on using Ansible to manage Cisco devices see the `Cisco integration page <https://www.ansible.com/integrations/networks/cisco>`_.
|
|
||||||
'''
|
|
@ -1,214 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2017 Red Hat Inc.
|
|
||||||
# (c) 2017 Kedar Kekan (kkekan@redhat.com)
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
---
|
|
||||||
author: Ansible Networking Team
|
|
||||||
netconf: iosxr
|
|
||||||
short_description: Use iosxr netconf plugin to run netconf commands on Cisco IOSXR platform
|
|
||||||
description:
|
|
||||||
- This iosxr plugin provides low level abstraction apis for
|
|
||||||
sending and receiving netconf commands from Cisco iosxr network devices.
|
|
||||||
version_added: "2.9"
|
|
||||||
options:
|
|
||||||
ncclient_device_handler:
|
|
||||||
type: str
|
|
||||||
default: iosxr
|
|
||||||
description:
|
|
||||||
- Specifies the ncclient device handler name for Cisco iosxr network os. To
|
|
||||||
identify the ncclient device handler name refer ncclient library documentation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from ansible.module_utils._text import to_native
|
|
||||||
from ansible.module_utils.network.common.netconf import remove_namespaces
|
|
||||||
from ansible.module_utils.network.iosxr.iosxr import build_xml, etree_find
|
|
||||||
from ansible.errors import AnsibleConnectionFailure
|
|
||||||
from ansible.plugins.netconf import NetconfBase, ensure_ncclient
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ncclient import manager
|
|
||||||
from ncclient.operations import RPCError
|
|
||||||
from ncclient.transport.errors import SSHUnknownHostError
|
|
||||||
from ncclient.xml_ import to_xml
|
|
||||||
HAS_NCCLIENT = True
|
|
||||||
except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError
|
|
||||||
HAS_NCCLIENT = False
|
|
||||||
|
|
||||||
|
|
||||||
class Netconf(NetconfBase):
|
|
||||||
def get_device_info(self):
|
|
||||||
device_info = {}
|
|
||||||
device_info['network_os'] = 'iosxr'
|
|
||||||
install_meta = collections.OrderedDict()
|
|
||||||
install_meta.update([
|
|
||||||
('boot-variables', {'xpath': 'install/boot-variables', 'tag': True}),
|
|
||||||
('boot-variable', {'xpath': 'install/boot-variables/boot-variable', 'tag': True, 'lead': True}),
|
|
||||||
('software', {'xpath': 'install/software', 'tag': True}),
|
|
||||||
('alias-devices', {'xpath': 'install/software/alias-devices', 'tag': True}),
|
|
||||||
('alias-device', {'xpath': 'install/software/alias-devices/alias-device', 'tag': True}),
|
|
||||||
('m:device-name', {'xpath': 'install/software/alias-devices/alias-device/device-name', 'value': 'disk0:'}),
|
|
||||||
])
|
|
||||||
|
|
||||||
install_filter = build_xml('install', install_meta, opcode='filter')
|
|
||||||
try:
|
|
||||||
reply = self.get(install_filter)
|
|
||||||
resp = remove_namespaces(re.sub(r'<\?xml version="1.0" encoding="UTF-8"\?>', '', reply))
|
|
||||||
ele_boot_variable = etree_find(resp, 'boot-variable/boot-variable')
|
|
||||||
if ele_boot_variable is not None:
|
|
||||||
device_info['network_os_image'] = re.split('[:|,]', ele_boot_variable.text)[1]
|
|
||||||
ele_package_name = etree_find(reply, 'package-name')
|
|
||||||
if ele_package_name is not None:
|
|
||||||
device_info['network_os_package'] = ele_package_name.text
|
|
||||||
device_info['network_os_version'] = re.split('-', ele_package_name.text)[-1]
|
|
||||||
|
|
||||||
hostname_filter = build_xml('host-names', opcode='filter')
|
|
||||||
reply = self.get(hostname_filter)
|
|
||||||
resp = remove_namespaces(re.sub(r'<\?xml version="1.0" encoding="UTF-8"\?>', '', reply))
|
|
||||||
hostname_ele = etree_find(resp.strip(), 'host-name')
|
|
||||||
device_info['network_os_hostname'] = hostname_ele.text if hostname_ele is not None else None
|
|
||||||
except Exception as exc:
|
|
||||||
self._connection.queue_message('vvvv', 'Fail to retrieve device info %s' % exc)
|
|
||||||
return device_info
|
|
||||||
|
|
||||||
def get_capabilities(self):
|
|
||||||
result = dict()
|
|
||||||
result['rpc'] = self.get_base_rpc()
|
|
||||||
result['network_api'] = 'netconf'
|
|
||||||
result['device_info'] = self.get_device_info()
|
|
||||||
result['server_capabilities'] = [c for c in self.m.server_capabilities]
|
|
||||||
result['client_capabilities'] = [c for c in self.m.client_capabilities]
|
|
||||||
result['session_id'] = self.m.session_id
|
|
||||||
result['device_operations'] = self.get_device_operations(result['server_capabilities'])
|
|
||||||
return json.dumps(result)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@ensure_ncclient
|
|
||||||
def guess_network_os(obj):
|
|
||||||
"""
|
|
||||||
Guess the remote network os name
|
|
||||||
:param obj: Netconf connection class object
|
|
||||||
:return: Network OS name
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
m = manager.connect(
|
|
||||||
host=obj._play_context.remote_addr,
|
|
||||||
port=obj._play_context.port or 830,
|
|
||||||
username=obj._play_context.remote_user,
|
|
||||||
password=obj._play_context.password,
|
|
||||||
key_filename=obj.key_filename,
|
|
||||||
hostkey_verify=obj.get_option('host_key_checking'),
|
|
||||||
look_for_keys=obj.get_option('look_for_keys'),
|
|
||||||
allow_agent=obj._play_context.allow_agent,
|
|
||||||
timeout=obj.get_option('persistent_connect_timeout'),
|
|
||||||
# We need to pass in the path to the ssh_config file when guessing
|
|
||||||
# the network_os so that a jumphost is correctly used if defined
|
|
||||||
ssh_config=obj._ssh_config
|
|
||||||
)
|
|
||||||
except SSHUnknownHostError as exc:
|
|
||||||
raise AnsibleConnectionFailure(to_native(exc))
|
|
||||||
|
|
||||||
guessed_os = None
|
|
||||||
for c in m.server_capabilities:
|
|
||||||
if re.search('IOS-XR', c):
|
|
||||||
guessed_os = 'iosxr'
|
|
||||||
break
|
|
||||||
|
|
||||||
m.close_session()
|
|
||||||
return guessed_os
|
|
||||||
|
|
||||||
# TODO: change .xml to .data_xml, when ncclient supports data_xml on all platforms
|
|
||||||
def get(self, filter=None, remove_ns=False):
|
|
||||||
if isinstance(filter, list):
|
|
||||||
filter = tuple(filter)
|
|
||||||
try:
|
|
||||||
resp = self.m.get(filter=filter)
|
|
||||||
if remove_ns:
|
|
||||||
response = remove_namespaces(resp)
|
|
||||||
else:
|
|
||||||
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
|
|
||||||
return response
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
def get_config(self, source=None, filter=None, remove_ns=False):
|
|
||||||
if isinstance(filter, list):
|
|
||||||
filter = tuple(filter)
|
|
||||||
try:
|
|
||||||
resp = self.m.get_config(source=source, filter=filter)
|
|
||||||
if remove_ns:
|
|
||||||
response = remove_namespaces(resp)
|
|
||||||
else:
|
|
||||||
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
|
|
||||||
return response
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
def edit_config(self, config=None, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None, remove_ns=False):
|
|
||||||
if config is None:
|
|
||||||
raise ValueError('config value must be provided')
|
|
||||||
try:
|
|
||||||
resp = self.m.edit_config(config, format=format, target=target, default_operation=default_operation, test_option=test_option,
|
|
||||||
error_option=error_option)
|
|
||||||
if remove_ns:
|
|
||||||
response = remove_namespaces(resp)
|
|
||||||
else:
|
|
||||||
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
|
|
||||||
return response
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
def commit(self, confirmed=False, timeout=None, persist=None, remove_ns=False):
|
|
||||||
try:
|
|
||||||
resp = self.m.commit(confirmed=confirmed, timeout=timeout, persist=persist)
|
|
||||||
if remove_ns:
|
|
||||||
response = remove_namespaces(resp)
|
|
||||||
else:
|
|
||||||
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
|
|
||||||
return response
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
def validate(self, source="candidate", remove_ns=False):
|
|
||||||
try:
|
|
||||||
resp = self.m.validate(source=source)
|
|
||||||
if remove_ns:
|
|
||||||
response = remove_namespaces(resp)
|
|
||||||
else:
|
|
||||||
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
|
|
||||||
return response
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
||||||
|
|
||||||
def discard_changes(self, remove_ns=False):
|
|
||||||
try:
|
|
||||||
resp = self.m.discard_changes()
|
|
||||||
if remove_ns:
|
|
||||||
response = remove_namespaces(resp)
|
|
||||||
else:
|
|
||||||
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
|
|
||||||
return response
|
|
||||||
except RPCError as exc:
|
|
||||||
raise Exception(to_xml(exc.xml))
|
|
@ -1,52 +0,0 @@
|
|||||||
#
|
|
||||||
# (c) 2016 Red Hat Inc.
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.plugins.terminal import TerminalBase
|
|
||||||
from ansible.errors import AnsibleConnectionFailure
|
|
||||||
|
|
||||||
|
|
||||||
class TerminalModule(TerminalBase):
|
|
||||||
|
|
||||||
terminal_stdout_re = [
|
|
||||||
re.compile(br"[\r\n]*[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
|
|
||||||
re.compile(br']]>]]>[\r\n]?')
|
|
||||||
]
|
|
||||||
|
|
||||||
terminal_stderr_re = [
|
|
||||||
re.compile(br"% ?Error"),
|
|
||||||
re.compile(br"% ?Bad secret"),
|
|
||||||
re.compile(br"% ?This command is not authorized"),
|
|
||||||
re.compile(br"invalid input", re.I),
|
|
||||||
re.compile(br"(?:incomplete|ambiguous) command", re.I),
|
|
||||||
re.compile(br"(?<!\()connection timed out(?!\))", re.I),
|
|
||||||
re.compile(br"[^\r\n]+ not found", re.I),
|
|
||||||
re.compile(br"'[^']' +returned error code: ?\d+"),
|
|
||||||
re.compile(br"Failed to commit", re.I)
|
|
||||||
]
|
|
||||||
|
|
||||||
def on_open_shell(self):
|
|
||||||
try:
|
|
||||||
for cmd in (b'terminal length 0', b'terminal width 512', b'terminal exec prompt no-timestamp'):
|
|
||||||
self._exec_cli_command(cmd)
|
|
||||||
except AnsibleConnectionFailure:
|
|
||||||
raise AnsibleConnectionFailure('unable to set terminal parameters')
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
testcase: "[^_].*"
|
|
||||||
test_items: []
|
|
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Collect all cli test cases
|
|
||||||
find:
|
|
||||||
paths: "{{ role_path }}/tests/cli"
|
|
||||||
patterns: "{{ testcase }}.yaml"
|
|
||||||
use_regex: true
|
|
||||||
register: test_cases
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Set test_items
|
|
||||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Run test case (connection=network_cli)
|
|
||||||
include: "{{ test_case_to_run }}"
|
|
||||||
vars:
|
|
||||||
ansible_connection: network_cli
|
|
||||||
with_items: "{{ test_items }}"
|
|
||||||
loop_control:
|
|
||||||
loop_var: test_case_to_run
|
|
@ -1,2 +0,0 @@
|
|||||||
---
|
|
||||||
- { include: cli.yaml, tags: ['cli'] }
|
|
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Populate the device with ACLs
|
|
||||||
iosxr_config:
|
|
||||||
lines: |
|
|
||||||
ipv4 access-list acl_1
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv4 access-list acl_2
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv4 access-list acl_3
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv6 access-list acl6_1
|
|
||||||
10 permit ipv6 any any
|
|
||||||
ipv6 access-list acl6_2
|
|
||||||
10 permit ipv6 any any
|
|
||||||
ipv6 access-list acl6_3
|
|
||||||
10 permit ipv6 any any
|
|
||||||
|
|
||||||
- name: Setup ACL interfaces configuration for GigabitEthernet0/0/0/0
|
|
||||||
iosxr_config:
|
|
||||||
lines: |
|
|
||||||
ipv4 access-group acl_1 ingress
|
|
||||||
ipv4 access-group acl_2 egress
|
|
||||||
ipv6 access-group acl6_1 ingress
|
|
||||||
ipv6 access-group acl6_2 egress
|
|
||||||
parents: interface GigabitEthernet0/0/0/0
|
|
||||||
|
|
||||||
- name: Setup ACL interfaces configuration for GigabitEthernet0/0/0/1
|
|
||||||
iosxr_config:
|
|
||||||
lines: ipv4 access-group acl_1 egress
|
|
||||||
parents: interface GigabitEthernet0/0/0/1
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Remove/Default Resources
|
|
||||||
cli_config:
|
|
||||||
config: "{{ lines }}"
|
|
||||||
vars:
|
|
||||||
lines: |
|
|
||||||
default interface GigabitEthernet0/0/0/0
|
|
||||||
default interface GigabitEthernet0/0/0/1
|
|
||||||
no ipv4 access-list acl_1
|
|
||||||
no ipv4 access-list acl_2
|
|
||||||
no ipv6 access-list acl6_1
|
|
||||||
no ipv6 access-list acl6_2
|
|
||||||
no ipv6 access-list acl6_3
|
|
||||||
|
|
||||||
- name: Initialize interfaces
|
|
||||||
iosxr_config:
|
|
||||||
lines: shutdown
|
|
||||||
parents: "{{ item }}"
|
|
||||||
loop:
|
|
||||||
- interface GigabitEthernet0/0/0/0
|
|
||||||
- interface GigabitEthernet0/0/0/1
|
|
||||||
|
|
||||||
# To make sure our assertions are not affected by
|
|
||||||
# spill overs from previous tests
|
|
||||||
- name: Remove unwanted interfaces from config
|
|
||||||
iosxr_config:
|
|
||||||
lines:
|
|
||||||
- "no interface GigabitEthernet{{ item }}"
|
|
||||||
loop:
|
|
||||||
- 0/0/0/2
|
|
||||||
- 0/0/0/3
|
|
||||||
- 0/0/0/4
|
|
||||||
- 0/0/0/5
|
|
||||||
ignore_errors: yes
|
|
@ -1,85 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "Start iosxr_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Delete ACL attributes of GigabitEthernet0/0/0/1
|
|
||||||
iosxr_acl_interfaces: &deleted
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "'interface GigabitEthernet0/0/0/1' in result.commands"
|
|
||||||
- "'no ipv4 access-group acl_1 egress' in result.commands"
|
|
||||||
- "result.commands|length == 2"
|
|
||||||
|
|
||||||
- name: Delete ACL attributes of GigabitEthernet0/0/0/1 (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == False"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Delete a single ACL attached to GigabitEthernet0/0/0/0
|
|
||||||
iosxr_acl_interfaces: &deleted_1
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "'interface GigabitEthernet0/0/0/0' in result.commands"
|
|
||||||
- "'no ipv4 access-group acl_2 egress' in result.commands"
|
|
||||||
- "result.commands|length == 2"
|
|
||||||
|
|
||||||
- name: Delete a single ACL attached to GigabitEthernet0/0/0/0 (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *deleted_1
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == False"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0
|
|
||||||
iosxr_acl_interfaces: &deleted_2
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "'interface GigabitEthernet0/0/0/0' in result.commands"
|
|
||||||
- "'no ipv6 access-group acl6_1 ingress' in result.commands"
|
|
||||||
- "'no ipv6 access-group acl6_2 egress' in result.commands"
|
|
||||||
- "result.commands|length == 3"
|
|
||||||
|
|
||||||
- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0 (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *deleted_2
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == False"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "Start iosxr_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Delete all ACL interfaces configuration from the device
|
|
||||||
iosxr_acl_interfaces: &deleted_3
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that the correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that the after dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Delete ACL attributes of all interfaces (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *deleted_3
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that the before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces empty_config integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- name: Merged with empty config should give appropriate error message
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state merged'
|
|
||||||
|
|
||||||
- name: Replaced with empty config should give appropriate error message
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
state: replaced
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state replaced'
|
|
||||||
|
|
||||||
- name: Overridden with empty config should give appropriate error message
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
state: overridden
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state overridden'
|
|
||||||
|
|
||||||
- name: Rendered with empty config should give appropriate error message
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
state: rendered
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state rendered'
|
|
||||||
|
|
||||||
- name: Parsed with empty config should give appropriate error message
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
running_config:
|
|
||||||
state: parsed
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of running_config parameter must not be empty for state parsed'
|
|
@ -1,14 +0,0 @@
|
|||||||
interface MgmtEth0/0/CPU0/0
|
|
||||||
ipv4 address dhcp
|
|
||||||
!
|
|
||||||
interface GigabitEthernet0/0/0/0
|
|
||||||
shutdown
|
|
||||||
ipv4 access-group acl_1 ingress
|
|
||||||
ipv4 access-group acl_2 egress
|
|
||||||
ipv6 access-group acl6_1 ingress
|
|
||||||
ipv6 access-group acl6_2 egress
|
|
||||||
!
|
|
||||||
interface GigabitEthernet0/0/0/1
|
|
||||||
shutdown
|
|
||||||
ipv4 access-group acl_1 egress
|
|
||||||
!
|
|
@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces gathered integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- name: Populate the device with ACLs
|
|
||||||
iosxr_config:
|
|
||||||
lines: |
|
|
||||||
ipv4 access-list acl_1
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv4 access-list acl_2
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv6 access-list acl6_1
|
|
||||||
10 permit ipv6 any any
|
|
||||||
ipv6 access-list acl6_2
|
|
||||||
10 permit ipv6 any any
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Merge the provided configuration with the existing running configuration
|
|
||||||
iosxr_acl_interfaces: &merged
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Gather ACL interfaces facts using gathered state
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
state: gathered
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that facts were correctly generated
|
|
||||||
assert:
|
|
||||||
that: "{{ merged['after'] | symmetric_difference(result['gathered']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,115 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces merged integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- name: Populate the device with ACLs
|
|
||||||
iosxr_config:
|
|
||||||
lines: |
|
|
||||||
ipv4 access-list acl_1
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv4 access-list acl_2
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv6 access-list acl6_1
|
|
||||||
10 permit ipv6 any any
|
|
||||||
ipv6 access-list acl6_2
|
|
||||||
10 permit ipv6 any any
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Merge the provided configuration with the existing running configuration
|
|
||||||
iosxr_acl_interfaces: &merged
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dicts was correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Update acl_interfaces configuration using merged
|
|
||||||
iosxr_acl_interfaces: &merged_update
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that: "{{ merged['update_before'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['update_commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dicts was correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['update_after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Update acl_interfaces configuration using merged (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *merged_update
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,64 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces overridden integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Overridde all interface ACL configuration with provided configuration
|
|
||||||
iosxr_acl_interfaces: &overridden
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: in
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: out
|
|
||||||
state: overridden
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that before dicts are correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Overridde all interface LACP configuration with provided configuration (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *overridden
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that before dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces parsed integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- name: Parse externally provided ACL interfaces config to agnostic model
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
|
|
||||||
state: parsed
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that config was correctly parsed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['parsed']) |length == 0 }}"
|
|
@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces rendered integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- name: Render platform specific commands from task input using rendered state
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
state: rendered
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were rendered
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['commands'] | symmetric_difference(result['rendered']) |length == 0 }}"
|
|
@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces replaced integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Replace device configurations of listed interface with provided configurations
|
|
||||||
iosxr_acl_interfaces: &replaced
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: in
|
|
||||||
state: replaced
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that before dicts are correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT)
|
|
||||||
iosxr_acl_interfaces: *replaced
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that before dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,102 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces round trip integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- name: Populate the device with ACLs
|
|
||||||
iosxr_config:
|
|
||||||
lines: |
|
|
||||||
ipv4 access-list acl_1
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv4 access-list acl_2
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv4 access-list acl_3
|
|
||||||
10 permit ipv4 any any
|
|
||||||
ipv6 access-list acl6_1
|
|
||||||
10 permit ipv6 any any
|
|
||||||
ipv6 access-list acl6_2
|
|
||||||
10 permit ipv6 any any
|
|
||||||
ipv6 access-list acl6_3
|
|
||||||
10 permit ipv6 any any
|
|
||||||
|
|
||||||
- name: Apply the provided configuration (base config)
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
state: merged
|
|
||||||
|
|
||||||
- name: Gather interfaces facts
|
|
||||||
iosxr_facts:
|
|
||||||
gather_subset:
|
|
||||||
- "!all"
|
|
||||||
- "!min"
|
|
||||||
gather_network_resources:
|
|
||||||
- acl_interfaces
|
|
||||||
|
|
||||||
- name: Apply the provided configuration (config to be reverted)
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config:
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
state: overridden
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that changes were applied
|
|
||||||
assert:
|
|
||||||
that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Revert back to base config using facts round trip
|
|
||||||
iosxr_acl_interfaces:
|
|
||||||
config: "{{ ansible_facts['network_resources']['acl_interfaces'] }}"
|
|
||||||
state: overridden
|
|
||||||
register: revert
|
|
||||||
|
|
||||||
- name: Assert that config was reverted
|
|
||||||
assert:
|
|
||||||
that: "{{ merged['after'] | symmetric_difference(revert['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,205 +0,0 @@
|
|||||||
---
|
|
||||||
merged:
|
|
||||||
before:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
|
|
||||||
commands:
|
|
||||||
- interface GigabitEthernet0/0/0/0
|
|
||||||
- ipv4 access-group acl_1 ingress
|
|
||||||
- ipv4 access-group acl_2 egress
|
|
||||||
- ipv6 access-group acl6_1 ingress
|
|
||||||
- ipv6 access-group acl6_2 egress
|
|
||||||
- interface GigabitEthernet0/0/0/1
|
|
||||||
- ipv4 access-group acl_1 egress
|
|
||||||
|
|
||||||
update_commands:
|
|
||||||
- interface GigabitEthernet0/0/0/1
|
|
||||||
- ipv4 access-group acl_2 egress
|
|
||||||
- ipv4 access-group acl_1 ingress
|
|
||||||
|
|
||||||
after:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: in
|
|
||||||
- name: acl_2
|
|
||||||
direction: out
|
|
||||||
afi: ipv4
|
|
||||||
|
|
||||||
- acls:
|
|
||||||
- name: acl6_1
|
|
||||||
direction: in
|
|
||||||
- name: acl6_2
|
|
||||||
direction: out
|
|
||||||
afi: ipv6
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
afi: ipv4
|
|
||||||
|
|
||||||
update_before:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- access_groups:
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl_1
|
|
||||||
- direction: out
|
|
||||||
name: acl_2
|
|
||||||
afi: ipv4
|
|
||||||
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl6_1
|
|
||||||
- direction: out
|
|
||||||
name: acl6_2
|
|
||||||
afi: ipv6
|
|
||||||
name: GigabitEthernet0/0/0/0
|
|
||||||
|
|
||||||
- access_groups:
|
|
||||||
- acls:
|
|
||||||
- direction: out
|
|
||||||
name: acl_1
|
|
||||||
afi: ipv4
|
|
||||||
name: GigabitEthernet0/0/0/1
|
|
||||||
|
|
||||||
update_after:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- access_groups:
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl_1
|
|
||||||
- direction: out
|
|
||||||
name: acl_2
|
|
||||||
afi: ipv4
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl6_1
|
|
||||||
- direction: out
|
|
||||||
name: acl6_2
|
|
||||||
afi: ipv6
|
|
||||||
name: GigabitEthernet0/0/0/0
|
|
||||||
|
|
||||||
- access_groups:
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl_1
|
|
||||||
- direction: out
|
|
||||||
name: acl_2
|
|
||||||
afi: ipv4
|
|
||||||
name: GigabitEthernet0/0/0/1
|
|
||||||
|
|
||||||
|
|
||||||
replaced:
|
|
||||||
commands:
|
|
||||||
- interface GigabitEthernet0/0/0/0
|
|
||||||
- no ipv4 access-group acl_1 ingress
|
|
||||||
- no ipv4 access-group acl_2 egress
|
|
||||||
- no ipv6 access-group acl6_1 ingress
|
|
||||||
- no ipv6 access-group acl6_2 egress
|
|
||||||
- ipv6 access-group acl6_3 ingress
|
|
||||||
|
|
||||||
after:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- acls:
|
|
||||||
- name: acl_1
|
|
||||||
direction: out
|
|
||||||
afi: ipv4
|
|
||||||
|
|
||||||
overridden:
|
|
||||||
commands:
|
|
||||||
- interface GigabitEthernet0/0/0/0
|
|
||||||
- no ipv4 access-group acl_1 ingress
|
|
||||||
- no ipv4 access-group acl_2 egress
|
|
||||||
- no ipv6 access-group acl6_1 ingress
|
|
||||||
- no ipv6 access-group acl6_2 egress
|
|
||||||
- ipv6 access-group acl6_3 ingress
|
|
||||||
- interface GigabitEthernet0/0/0/1
|
|
||||||
- no ipv4 access-group acl_1 egress
|
|
||||||
- ipv4 access-group acl_2 ingress
|
|
||||||
- ipv6 access-group acl6_3 egress
|
|
||||||
|
|
||||||
after:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
access_groups:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: in
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
access_groups:
|
|
||||||
- acls:
|
|
||||||
- name: acl_2
|
|
||||||
direction: in
|
|
||||||
afi: ipv4
|
|
||||||
|
|
||||||
- acls:
|
|
||||||
- name: acl6_3
|
|
||||||
direction: out
|
|
||||||
afi: ipv6
|
|
||||||
|
|
||||||
|
|
||||||
deleted:
|
|
||||||
commands:
|
|
||||||
- interface GigabitEthernet0/0/0/0
|
|
||||||
- no ipv4 access-group acl_1 ingress
|
|
||||||
- no ipv4 access-group acl_2 egress
|
|
||||||
- no ipv6 access-group acl6_1 ingress
|
|
||||||
- no ipv6 access-group acl6_2 egress
|
|
||||||
- interface GigabitEthernet0/0/0/1
|
|
||||||
- no ipv4 access-group acl_1 egress
|
|
||||||
|
|
||||||
after:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/0
|
|
||||||
|
|
||||||
- name: GigabitEthernet0/0/0/1
|
|
||||||
|
|
||||||
round_trip:
|
|
||||||
after:
|
|
||||||
- name: MgmtEth0/0/CPU0/0
|
|
||||||
- access_groups:
|
|
||||||
- acls:
|
|
||||||
- direction: out
|
|
||||||
name: acl_1
|
|
||||||
afi: ipv4
|
|
||||||
name: GigabitEthernet0/0/0/0
|
|
||||||
- access_groups:
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl_1
|
|
||||||
- direction: out
|
|
||||||
name: acl_2
|
|
||||||
afi: ipv4
|
|
||||||
- acls:
|
|
||||||
- direction: in
|
|
||||||
name: acl6_1
|
|
||||||
- direction: out
|
|
||||||
name: acl6_2
|
|
||||||
afi: ipv6
|
|
||||||
name: GigabitEthernet0/0/0/1
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
testcase: "[^_].*"
|
|
||||||
test_items: []
|
|
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Collect all cli test cases
|
|
||||||
find:
|
|
||||||
paths: "{{ role_path }}/tests/cli"
|
|
||||||
patterns: "{{ testcase }}.yaml"
|
|
||||||
use_regex: true
|
|
||||||
register: test_cases
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Set test_items
|
|
||||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Run test case (connection=network_cli)
|
|
||||||
include: "{{ test_case_to_run }}"
|
|
||||||
vars:
|
|
||||||
ansible_connection: network_cli
|
|
||||||
with_items: "{{ test_items }}"
|
|
||||||
loop_control:
|
|
||||||
loop_var: test_case_to_run
|
|
@ -1,2 +0,0 @@
|
|||||||
---
|
|
||||||
- { include: cli.yaml, tags: ['cli'] }
|
|
@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Setup
|
|
||||||
iosxr_config:
|
|
||||||
lines: |
|
|
||||||
ipv6 access-list acl6_1
|
|
||||||
10 deny tcp 2001:db8:1234::/48 range ftp telnet any syn ttl range 180 250 authen routing log
|
|
||||||
20 permit icmpv6 any any router-advertisement precedence network destopts
|
|
||||||
ipv4 access-list acl_1
|
|
||||||
16 remark TEST_ACL_1_REMARK
|
|
||||||
21 permit tcp host 192.0.2.10 range pop3 121 198.51.100.0 0.0.0.15 rst
|
|
||||||
23 deny icmp any 198.51.100.0 0.0.0.15 reassembly-timeout dscp lt af12
|
|
||||||
ipv4 access-list acl_2
|
|
||||||
10 remark TEST_ACL_2_REMARK
|
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Remove ACLs
|
|
||||||
cli_config:
|
|
||||||
config: "{{ lines }}"
|
|
||||||
vars:
|
|
||||||
lines: |
|
|
||||||
no ipv4 access-list acl_1
|
|
||||||
no ipv4 access-list acl_2
|
|
||||||
no ipv4 access-list acl_3
|
|
||||||
no ipv4 access-list acl_3
|
|
||||||
no ipv6 access-list acl6_1
|
|
||||||
no ipv6 access-list acl6_2
|
|
||||||
no ipv6 access-list acl6_3
|
|
||||||
ignore_errors: yes
|
|
@ -1,116 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "Start iosxr_lag_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate_config.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Delete a single ACE
|
|
||||||
iosxr_acls: &deleted_1
|
|
||||||
config:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
aces:
|
|
||||||
- sequence: 23
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- '"ipv4 access-list acl_1" in result.commands'
|
|
||||||
- '"no 23" in result.commands'
|
|
||||||
- "result.commands|length == 2"
|
|
||||||
|
|
||||||
- name: Delete a single ACE (IDEMPOTENT)
|
|
||||||
iosxr_acls: *deleted_1
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert: &unchanged
|
|
||||||
that:
|
|
||||||
- "result.changed == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Delete a single ACL
|
|
||||||
iosxr_acls: &deleted_2
|
|
||||||
config:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- '"no ipv6 access-list acl6_1" in result.commands'
|
|
||||||
- "result.commands|length == 1"
|
|
||||||
|
|
||||||
- name: Delete a single ACL (IDEMPOTENT)
|
|
||||||
iosxr_acls: *deleted_2
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert: *unchanged
|
|
||||||
|
|
||||||
- name: Delete all ACLs under one AFI
|
|
||||||
iosxr_acls: &deleted_3
|
|
||||||
config:
|
|
||||||
- afi: ipv4
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- '"no ipv4 access-list acl_1" in result.commands'
|
|
||||||
- '"no ipv4 access-list acl_2" in result.commands'
|
|
||||||
- "result.commands|length == 2"
|
|
||||||
|
|
||||||
- name: Delete all ACLs under one AFI (IDEMPOTENT)
|
|
||||||
iosxr_acls: *deleted_3
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert: *unchanged
|
|
||||||
|
|
||||||
- include_tasks: _populate_config.yaml
|
|
||||||
|
|
||||||
- name: Delete all ACLs from the device
|
|
||||||
iosxr_acls: &deleted_4
|
|
||||||
state: deleted
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that the correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that the after dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Delete all ACLs from the device (IDEMPOTENT)
|
|
||||||
iosxr_lag_interfaces: *deleted_4
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result.changed == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that the before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,57 +0,0 @@
|
|||||||
- debug:
|
|
||||||
msg: "START iosxr_acls empty_config integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- name: Merged with empty config should give appropriate error message
|
|
||||||
iosxr_acls:
|
|
||||||
config:
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state merged'
|
|
||||||
|
|
||||||
- name: Replaced with empty config should give appropriate error message
|
|
||||||
iosxr_acls:
|
|
||||||
config:
|
|
||||||
state: replaced
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state replaced'
|
|
||||||
|
|
||||||
- name: Overridden with empty config should give appropriate error message
|
|
||||||
iosxr_acls:
|
|
||||||
config:
|
|
||||||
state: overridden
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state overridden'
|
|
||||||
|
|
||||||
- name: Rendered with empty config should give appropriate error message
|
|
||||||
iosxr_acls:
|
|
||||||
config:
|
|
||||||
state: rendered
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of config parameter must not be empty for state rendered'
|
|
||||||
|
|
||||||
- name: Parsed with empty config should give appropriate error message
|
|
||||||
iosxr_acls:
|
|
||||||
running_config:
|
|
||||||
state: parsed
|
|
||||||
register: result
|
|
||||||
ignore_errors: True
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- result.msg == 'value of running_config parameter must not be empty for state parsed'
|
|
@ -1,8 +0,0 @@
|
|||||||
ipv4 access-list acl_1
|
|
||||||
10 remark TEST_ACL_2_REMARK
|
|
||||||
ipv4 access-list acl_2
|
|
||||||
11 deny tcp 2001:db8:1234::/48 range ftp telnet any syn ttl range 180 250 authen routing log
|
|
||||||
21 permit icmpv6 any any router-advertisement precedence network packet-length eq 576 destopts
|
|
||||||
ipv6 access-list acl6_1
|
|
||||||
10 deny tcp 2001:db8:1234::/48 range ftp telnet any syn ttl range 180 250 routing authen log
|
|
||||||
20 permit icmpv6 any any router-advertisement precedence network packet-length eq 576 destopts
|
|
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acls gathered integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate_config.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Gather ACL interfaces facts using gathered state
|
|
||||||
iosxr_acls:
|
|
||||||
state: gathered
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that facts were correctly generated
|
|
||||||
assert:
|
|
||||||
that: "{{ merged['after'] | symmetric_difference(result['gathered']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,168 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acls merged integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Merge the provided configuration with the exisiting running configuration
|
|
||||||
iosxr_acls: &merged
|
|
||||||
config:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
aces:
|
|
||||||
- sequence: 10
|
|
||||||
grant: deny
|
|
||||||
protocol: tcp
|
|
||||||
source:
|
|
||||||
prefix: 2001:db8:1234::/48
|
|
||||||
port_protocol:
|
|
||||||
range:
|
|
||||||
start: ftp
|
|
||||||
end: telnet
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol_options:
|
|
||||||
tcp:
|
|
||||||
syn: True
|
|
||||||
ttl:
|
|
||||||
range:
|
|
||||||
start: 180
|
|
||||||
end: 250
|
|
||||||
routing: True
|
|
||||||
authen: True
|
|
||||||
log: True
|
|
||||||
|
|
||||||
- sequence: 20
|
|
||||||
grant: permit
|
|
||||||
protocol: icmpv6
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol_options:
|
|
||||||
icmpv6:
|
|
||||||
router_advertisement: True
|
|
||||||
precedence: network
|
|
||||||
destopts: True
|
|
||||||
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
aces:
|
|
||||||
- sequence: 16
|
|
||||||
remark: TEST_ACL_1_REMARK
|
|
||||||
|
|
||||||
- sequence: 21
|
|
||||||
grant: permit
|
|
||||||
protocol: tcp
|
|
||||||
source:
|
|
||||||
host: 192.0.2.10
|
|
||||||
port_protocol:
|
|
||||||
range:
|
|
||||||
start: pop3
|
|
||||||
end: 121
|
|
||||||
destination:
|
|
||||||
address: 198.51.100.0
|
|
||||||
wildcard_bits: 0.0.0.15
|
|
||||||
protocol_options:
|
|
||||||
tcp:
|
|
||||||
rst: True
|
|
||||||
|
|
||||||
- sequence: 23
|
|
||||||
grant: deny
|
|
||||||
protocol: icmp
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
prefix: 198.51.100.0/28
|
|
||||||
protocol_options:
|
|
||||||
icmp:
|
|
||||||
reassembly_timeout: True
|
|
||||||
dscp:
|
|
||||||
lt: af12
|
|
||||||
|
|
||||||
- name: acl_2
|
|
||||||
aces:
|
|
||||||
- sequence: 10
|
|
||||||
remark: TEST_ACL_2_REMARK
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dicts was correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
|
|
||||||
iosxr_acls: *merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that before dicts were correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Update existing ACEs
|
|
||||||
iosxr_acls: &update
|
|
||||||
config:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
aces:
|
|
||||||
- sequence: 21
|
|
||||||
source:
|
|
||||||
prefix: 198.51.100.32/28
|
|
||||||
port_protocol:
|
|
||||||
range:
|
|
||||||
start: pop3
|
|
||||||
end: 121
|
|
||||||
protocol_options:
|
|
||||||
tcp:
|
|
||||||
syn: True
|
|
||||||
|
|
||||||
- sequence: 23
|
|
||||||
protocol_options:
|
|
||||||
icmp:
|
|
||||||
router_advertisement: True
|
|
||||||
dscp:
|
|
||||||
eq: af23
|
|
||||||
state: merged
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- '"ipv4 access-list acl_1" in result.commands'
|
|
||||||
- '"21 permit tcp 198.51.100.32 0.0.0.15 range pop3 121 198.51.100.0 0.0.0.15 syn" in result.commands'
|
|
||||||
- '"23 deny icmp any 198.51.100.0 0.0.0.15 router-advertisement dscp eq af23" in result.commands'
|
|
||||||
- "result.commands|length == 3"
|
|
||||||
|
|
||||||
- name: Update existing ACEs (IDEMPOTENT)
|
|
||||||
iosxr_acls: *update
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that the previous task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,68 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acls overridden integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate_config.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Overridde all ACLs configuration with provided configuration
|
|
||||||
iosxr_acls: &overridden
|
|
||||||
config:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
aces:
|
|
||||||
- sequence: 10
|
|
||||||
grant: permit
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol: tcp
|
|
||||||
|
|
||||||
- name: acl_2
|
|
||||||
aces:
|
|
||||||
- sequence: 20
|
|
||||||
grant: permit
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol: igmp
|
|
||||||
state: overridden
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that before dicts are correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Overridde all interface LAG interface configuration with provided configuration (IDEMPOTENT)
|
|
||||||
iosxr_acls: *overridden
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that before dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acls parsed integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- name: Parse externally provided ACL config to agnostic model
|
|
||||||
iosxr_acls:
|
|
||||||
running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
|
|
||||||
state: parsed
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that config was correctly parsed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ parsed | symmetric_difference(result['parsed']) |length == 0 }}"
|
|
@ -1,93 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acls rendered integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- name: Render platform specific commands from task input using rendered state
|
|
||||||
iosxr_acls:
|
|
||||||
config:
|
|
||||||
- afi: ipv6
|
|
||||||
acls:
|
|
||||||
- name: acl6_1
|
|
||||||
aces:
|
|
||||||
- sequence: 10
|
|
||||||
grant: deny
|
|
||||||
protocol: tcp
|
|
||||||
source:
|
|
||||||
prefix: 2001:db8:1234::/48
|
|
||||||
port_protocol:
|
|
||||||
range:
|
|
||||||
start: ftp
|
|
||||||
end: telnet
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol_options:
|
|
||||||
tcp:
|
|
||||||
syn: True
|
|
||||||
ttl:
|
|
||||||
range:
|
|
||||||
start: 180
|
|
||||||
end: 250
|
|
||||||
routing: True
|
|
||||||
authen: True
|
|
||||||
log: True
|
|
||||||
|
|
||||||
- sequence: 20
|
|
||||||
grant: permit
|
|
||||||
protocol: icmpv6
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol_options:
|
|
||||||
icmpv6:
|
|
||||||
router_advertisement: True
|
|
||||||
precedence: network
|
|
||||||
destopts: True
|
|
||||||
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_1
|
|
||||||
aces:
|
|
||||||
- sequence: 16
|
|
||||||
remark: TEST_ACL_1_REMARK
|
|
||||||
|
|
||||||
- sequence: 21
|
|
||||||
grant: permit
|
|
||||||
protocol: tcp
|
|
||||||
source:
|
|
||||||
host: 192.0.2.10
|
|
||||||
port_protocol:
|
|
||||||
range:
|
|
||||||
start: pop3
|
|
||||||
end: 121
|
|
||||||
destination:
|
|
||||||
address: 198.51.100.0
|
|
||||||
wildcard_bits: 0.0.0.15
|
|
||||||
protocol_options:
|
|
||||||
tcp:
|
|
||||||
rst: True
|
|
||||||
|
|
||||||
- sequence: 23
|
|
||||||
grant: deny
|
|
||||||
protocol: icmp
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
prefix: 198.51.100.0/28
|
|
||||||
protocol_options:
|
|
||||||
icmp:
|
|
||||||
reassembly_timeout: True
|
|
||||||
dscp:
|
|
||||||
lt: af12
|
|
||||||
|
|
||||||
- name: acl_2
|
|
||||||
aces:
|
|
||||||
- sequence: 10
|
|
||||||
remark: TEST_ACL_2_REMARK
|
|
||||||
state: rendered
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were rendered
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['commands'] | symmetric_difference(result['rendered']) |length == 0 }}"
|
|
@ -1,68 +0,0 @@
|
|||||||
---
|
|
||||||
- debug:
|
|
||||||
msg: "START iosxr_acl_interfaces replaced integration tests on connection={{ ansible_connection }}"
|
|
||||||
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
||||||
|
|
||||||
- include_tasks: _populate_config.yaml
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: Replace device configurations of listed ACL with provided configurations
|
|
||||||
iosxr_acls: &replaced
|
|
||||||
config:
|
|
||||||
- afi: ipv4
|
|
||||||
acls:
|
|
||||||
- name: acl_2
|
|
||||||
aces:
|
|
||||||
- sequence: 11
|
|
||||||
grant: permit
|
|
||||||
protocol: igmp
|
|
||||||
source:
|
|
||||||
host: 198.51.100.130
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
ttl:
|
|
||||||
eq: 100
|
|
||||||
|
|
||||||
- sequence: 12
|
|
||||||
grant: deny
|
|
||||||
source:
|
|
||||||
any: True
|
|
||||||
destination:
|
|
||||||
any: True
|
|
||||||
protocol: icmp
|
|
||||||
state: replaced
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that correct set of commands were generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that before dicts are correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Assert that after dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
|
||||||
|
|
||||||
- name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT)
|
|
||||||
iosxr_acls: *replaced
|
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: Assert that task was idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "result['changed'] == false"
|
|
||||||
- "result.commands|length == 0"
|
|
||||||
|
|
||||||
- name: Assert that before dict is correctly generated
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}"
|
|
||||||
|
|
||||||
always:
|
|
||||||
- include_tasks: _remove_config.yaml
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue