mirror of https://github.com/ansible/ansible.git
Add iosxr_acls RM (#66207)
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>pull/67709/head
parent
3acd8f6f7f
commit
b818436c5f
@ -0,0 +1,644 @@
|
|||||||
|
#
|
||||||
|
# -*- 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
|
@ -0,0 +1,442 @@
|
|||||||
|
#
|
||||||
|
# -*- 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.iosxr.argspec.acls.acls import AclsArgs
|
||||||
|
from ansible.module_utils.network.common.utils \
|
||||||
|
import (
|
||||||
|
to_list,
|
||||||
|
search_obj_in_list,
|
||||||
|
dict_diff,
|
||||||
|
remove_empties,
|
||||||
|
dict_merge,
|
||||||
|
)
|
||||||
|
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
|
@ -0,0 +1,381 @@
|
|||||||
|
#
|
||||||
|
# -*- 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
|
||||||
|
|
||||||
|
import re
|
||||||
|
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)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
testcase: "[^_].*"
|
||||||
|
test_items: []
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,2 @@
|
|||||||
|
---
|
||||||
|
- { include: cli.yaml, tags: ['cli'] }
|
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,116 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,57 @@
|
|||||||
|
- 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'
|
@ -0,0 +1,8 @@
|
|||||||
|
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
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,168 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
- 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 }}"
|
@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
- 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 }}"
|
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
- 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
|
@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
- debug:
|
||||||
|
msg: "START iosxr_acls round trip integration tests on connection={{ ansible_connection }}"
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- include_tasks: _remove_config.yaml
|
||||||
|
|
||||||
|
- name: Apply the provided configuration (base config)
|
||||||
|
iosxr_acls:
|
||||||
|
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: merged
|
||||||
|
register: base_config
|
||||||
|
|
||||||
|
- name: Gather interfaces facts
|
||||||
|
iosxr_facts:
|
||||||
|
gather_subset:
|
||||||
|
- "!all"
|
||||||
|
- "!min"
|
||||||
|
gather_network_resources:
|
||||||
|
- acls
|
||||||
|
|
||||||
|
- name: Apply the provided configuration (config to be reverted)
|
||||||
|
iosxr_acls:
|
||||||
|
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 changes were applied
|
||||||
|
assert:
|
||||||
|
that: "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}"
|
||||||
|
|
||||||
|
- name: Revert back to base config using facts round trip
|
||||||
|
iosxr_acls:
|
||||||
|
config: "{{ ansible_facts['network_resources']['acls'] }}"
|
||||||
|
state: overridden
|
||||||
|
register: revert
|
||||||
|
|
||||||
|
- name: Assert that config was reverted
|
||||||
|
assert:
|
||||||
|
that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}"
|
||||||
|
|
||||||
|
always:
|
||||||
|
- include_tasks: _remove_config.yaml
|
@ -0,0 +1,338 @@
|
|||||||
|
---
|
||||||
|
merged:
|
||||||
|
before: []
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- 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
|
||||||
|
|
||||||
|
after:
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- remark: TEST_ACL_1_REMARK
|
||||||
|
sequence: 16
|
||||||
|
|
||||||
|
- destination:
|
||||||
|
address: 198.51.100.0
|
||||||
|
wildcard_bits: 0.0.0.15
|
||||||
|
grant: permit
|
||||||
|
protocol: tcp
|
||||||
|
protocol_options:
|
||||||
|
tcp:
|
||||||
|
rst: true
|
||||||
|
sequence: 21
|
||||||
|
source:
|
||||||
|
host: 192.0.2.10
|
||||||
|
port_protocol:
|
||||||
|
range:
|
||||||
|
end: '121'
|
||||||
|
start: pop3
|
||||||
|
|
||||||
|
- destination:
|
||||||
|
address: 198.51.100.0
|
||||||
|
wildcard_bits: 0.0.0.15
|
||||||
|
dscp:
|
||||||
|
lt: af12
|
||||||
|
grant: deny
|
||||||
|
protocol: icmp
|
||||||
|
protocol_options:
|
||||||
|
icmp:
|
||||||
|
reassembly_timeout: true
|
||||||
|
sequence: 23
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl_1
|
||||||
|
|
||||||
|
- aces:
|
||||||
|
- remark: TEST_ACL_2_REMARK
|
||||||
|
sequence: 10
|
||||||
|
name: acl_2
|
||||||
|
afi: ipv4
|
||||||
|
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- authen: true
|
||||||
|
destination:
|
||||||
|
any: true
|
||||||
|
grant: deny
|
||||||
|
log: true
|
||||||
|
protocol: tcp
|
||||||
|
protocol_options:
|
||||||
|
tcp:
|
||||||
|
syn: true
|
||||||
|
routing: true
|
||||||
|
sequence: 10
|
||||||
|
source:
|
||||||
|
port_protocol:
|
||||||
|
range:
|
||||||
|
end: telnet
|
||||||
|
start: ftp
|
||||||
|
prefix: 2001:db8:1234::/48
|
||||||
|
ttl:
|
||||||
|
range:
|
||||||
|
end: 250
|
||||||
|
start: 180
|
||||||
|
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
destopts: true
|
||||||
|
grant: permit
|
||||||
|
precedence: network
|
||||||
|
protocol: icmpv6
|
||||||
|
protocol_options:
|
||||||
|
icmpv6:
|
||||||
|
router_advertisement: true
|
||||||
|
sequence: 20
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl6_1
|
||||||
|
afi: ipv6
|
||||||
|
|
||||||
|
|
||||||
|
replaced:
|
||||||
|
commands:
|
||||||
|
- ipv4 access-list acl_2
|
||||||
|
- no 10
|
||||||
|
- 11 permit igmp host 198.51.100.130 any ttl eq 100
|
||||||
|
- 12 deny icmp any any
|
||||||
|
|
||||||
|
after:
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- remark: TEST_ACL_1_REMARK
|
||||||
|
sequence: 16
|
||||||
|
- destination:
|
||||||
|
address: 198.51.100.0
|
||||||
|
wildcard_bits: 0.0.0.15
|
||||||
|
grant: permit
|
||||||
|
protocol: tcp
|
||||||
|
protocol_options:
|
||||||
|
tcp:
|
||||||
|
rst: true
|
||||||
|
sequence: 21
|
||||||
|
source:
|
||||||
|
host: 192.0.2.10
|
||||||
|
port_protocol:
|
||||||
|
range:
|
||||||
|
end: '121'
|
||||||
|
start: pop3
|
||||||
|
- destination:
|
||||||
|
address: 198.51.100.0
|
||||||
|
wildcard_bits: 0.0.0.15
|
||||||
|
dscp:
|
||||||
|
lt: af12
|
||||||
|
grant: deny
|
||||||
|
protocol: icmp
|
||||||
|
protocol_options:
|
||||||
|
icmp:
|
||||||
|
reassembly_timeout: true
|
||||||
|
sequence: 23
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl_1
|
||||||
|
|
||||||
|
- aces:
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
grant: permit
|
||||||
|
protocol: igmp
|
||||||
|
sequence: 11
|
||||||
|
source:
|
||||||
|
host: 198.51.100.130
|
||||||
|
ttl:
|
||||||
|
eq: 100
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
grant: deny
|
||||||
|
protocol: icmp
|
||||||
|
sequence: 12
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl_2
|
||||||
|
afi: ipv4
|
||||||
|
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- authen: true
|
||||||
|
destination:
|
||||||
|
any: true
|
||||||
|
grant: deny
|
||||||
|
log: true
|
||||||
|
protocol: tcp
|
||||||
|
protocol_options:
|
||||||
|
tcp:
|
||||||
|
syn: true
|
||||||
|
routing: true
|
||||||
|
sequence: 10
|
||||||
|
source:
|
||||||
|
port_protocol:
|
||||||
|
range:
|
||||||
|
end: telnet
|
||||||
|
start: ftp
|
||||||
|
prefix: 2001:db8:1234::/48
|
||||||
|
ttl:
|
||||||
|
range:
|
||||||
|
end: 250
|
||||||
|
start: 180
|
||||||
|
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
destopts: true
|
||||||
|
grant: permit
|
||||||
|
precedence: network
|
||||||
|
protocol: icmpv6
|
||||||
|
protocol_options:
|
||||||
|
icmpv6:
|
||||||
|
router_advertisement: true
|
||||||
|
sequence: 20
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl6_1
|
||||||
|
afi: ipv6
|
||||||
|
|
||||||
|
overridden:
|
||||||
|
commands:
|
||||||
|
- no ipv6 access-list acl6_1
|
||||||
|
- ipv4 access-list acl_1
|
||||||
|
- no 16
|
||||||
|
- no 21
|
||||||
|
- no 23
|
||||||
|
- 10 permit tcp any any
|
||||||
|
- ipv4 access-list acl_2
|
||||||
|
- no 10
|
||||||
|
- 20 permit igmp any any
|
||||||
|
|
||||||
|
after:
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
grant: permit
|
||||||
|
protocol: tcp
|
||||||
|
sequence: 10
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl_1
|
||||||
|
|
||||||
|
- aces:
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
grant: permit
|
||||||
|
protocol: igmp
|
||||||
|
sequence: 20
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl_2
|
||||||
|
afi: ipv4
|
||||||
|
|
||||||
|
deleted:
|
||||||
|
commands:
|
||||||
|
- no ipv4 access-list acl_1
|
||||||
|
- no ipv4 access-list acl_2
|
||||||
|
- no ipv6 access-list acl6_1
|
||||||
|
|
||||||
|
after: []
|
||||||
|
|
||||||
|
parsed:
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- remark: TEST_ACL_2_REMARK
|
||||||
|
sequence: 10
|
||||||
|
name: acl_1
|
||||||
|
- aces:
|
||||||
|
- authen: true
|
||||||
|
destination:
|
||||||
|
any: true
|
||||||
|
grant: deny
|
||||||
|
log: true
|
||||||
|
protocol: tcp
|
||||||
|
protocol_options:
|
||||||
|
tcp:
|
||||||
|
syn: true
|
||||||
|
routing: true
|
||||||
|
sequence: 11
|
||||||
|
source:
|
||||||
|
port_protocol:
|
||||||
|
range:
|
||||||
|
end: telnet
|
||||||
|
start: ftp
|
||||||
|
prefix: 2001:db8:1234::/48
|
||||||
|
ttl:
|
||||||
|
range:
|
||||||
|
end: 250
|
||||||
|
start: 180
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
destopts: true
|
||||||
|
grant: permit
|
||||||
|
packet_length:
|
||||||
|
eq: 576
|
||||||
|
precedence: network
|
||||||
|
protocol: icmpv6
|
||||||
|
protocol_options:
|
||||||
|
icmpv6:
|
||||||
|
router_advertisement: true
|
||||||
|
sequence: 21
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl_2
|
||||||
|
afi: ipv4
|
||||||
|
- acls:
|
||||||
|
- aces:
|
||||||
|
- authen: true
|
||||||
|
destination:
|
||||||
|
any: true
|
||||||
|
grant: deny
|
||||||
|
log: true
|
||||||
|
protocol: tcp
|
||||||
|
protocol_options:
|
||||||
|
tcp:
|
||||||
|
syn: true
|
||||||
|
routing: true
|
||||||
|
sequence: 10
|
||||||
|
source:
|
||||||
|
port_protocol:
|
||||||
|
range:
|
||||||
|
end: telnet
|
||||||
|
start: ftp
|
||||||
|
prefix: 2001:db8:1234::/48
|
||||||
|
ttl:
|
||||||
|
range:
|
||||||
|
end: 250
|
||||||
|
start: 180
|
||||||
|
- destination:
|
||||||
|
any: true
|
||||||
|
destopts: true
|
||||||
|
grant: permit
|
||||||
|
packet_length:
|
||||||
|
eq: 576
|
||||||
|
precedence: network
|
||||||
|
protocol: icmpv6
|
||||||
|
protocol_options:
|
||||||
|
icmpv6:
|
||||||
|
router_advertisement: true
|
||||||
|
sequence: 20
|
||||||
|
source:
|
||||||
|
any: true
|
||||||
|
name: acl6_1
|
||||||
|
afi: ipv6
|
||||||
|
|
||||||
|
round_trip:
|
||||||
|
after:
|
||||||
|
- members:
|
||||||
|
- member: GigabitEthernet0/0/0/8
|
||||||
|
mode: passive
|
||||||
|
- member: GigabitEthernet0/0/0/9
|
||||||
|
mode: active
|
||||||
|
name: Bundle-Ether10
|
||||||
|
|
||||||
|
- mode: active
|
||||||
|
name: Bundle-Ether11
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
ipv4 access-list acl_2
|
||||||
|
10 deny ipv4 any any
|
||||||
|
20 permit tcp host 192.168.1.100 any
|
||||||
|
ipv6 access-list acl6_1
|
||||||
|
10 deny icmpv6 any any
|
@ -0,0 +1,247 @@
|
|||||||
|
#
|
||||||
|
# (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
|
||||||
|
|
||||||
|
from units.compat.mock import patch
|
||||||
|
from ansible.modules.network.iosxr import iosxr_acls
|
||||||
|
from units.modules.utils import set_module_args
|
||||||
|
from .iosxr_module import TestIosxrModule, load_fixture
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
|
class TestIosxrAclsModule(TestIosxrModule):
|
||||||
|
module = iosxr_acls
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestIosxrAclsModule, self).setUp()
|
||||||
|
|
||||||
|
self.mock_get_config = patch(
|
||||||
|
'ansible.module_utils.network.common.network.Config.get_config')
|
||||||
|
self.get_config = self.mock_get_config.start()
|
||||||
|
|
||||||
|
self.mock_load_config = patch(
|
||||||
|
'ansible.module_utils.network.common.network.Config.load_config')
|
||||||
|
self.load_config = self.mock_load_config.start()
|
||||||
|
|
||||||
|
self.mock_get_resource_connection_config = patch(
|
||||||
|
'ansible.module_utils.network.common.cfg.base.get_resource_connection'
|
||||||
|
)
|
||||||
|
self.get_resource_connection_config = self.mock_get_resource_connection_config.start(
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_get_resource_connection_facts = patch(
|
||||||
|
'ansible.module_utils.network.common.facts.facts.get_resource_connection'
|
||||||
|
)
|
||||||
|
self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start(
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_execute_show_command = patch(
|
||||||
|
'ansible.module_utils.network.iosxr.facts.acls.acls.AclsFacts.get_device_data'
|
||||||
|
)
|
||||||
|
self.execute_show_command = self.mock_execute_show_command.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestIosxrAclsModule, self).tearDown()
|
||||||
|
self.mock_get_resource_connection_config.stop()
|
||||||
|
self.mock_get_resource_connection_facts.stop()
|
||||||
|
self.mock_get_config.stop()
|
||||||
|
self.mock_load_config.stop()
|
||||||
|
self.mock_execute_show_command.stop()
|
||||||
|
|
||||||
|
def load_fixtures(self, commands=None):
|
||||||
|
def load_from_file(*args, **kwargs):
|
||||||
|
return load_fixture('iosxr_acls_config.cfg')
|
||||||
|
|
||||||
|
self.execute_show_command.side_effect = load_from_file
|
||||||
|
|
||||||
|
def test_iosxr_acls_merged(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_1",
|
||||||
|
aces=[
|
||||||
|
dict(sequence="10",
|
||||||
|
grant="permit",
|
||||||
|
protocol="ospf",
|
||||||
|
source=dict(prefix="192.168.1.0/24"),
|
||||||
|
destination=dict(any="true"),
|
||||||
|
log="true")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="merged"))
|
||||||
|
commands = [
|
||||||
|
'ipv4 access-list acl_1',
|
||||||
|
'10 permit ospf 192.168.1.0 0.0.0.255 any log'
|
||||||
|
]
|
||||||
|
result = self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_iosxr_acls_merged_idempotent(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_2",
|
||||||
|
aces=[
|
||||||
|
dict(sequence="10",
|
||||||
|
grant="deny",
|
||||||
|
protocol='ipv4',
|
||||||
|
destination=dict(any='true'),
|
||||||
|
source=dict(any="true")),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="merged"))
|
||||||
|
result = self.execute_module(changed=False, commands=[])
|
||||||
|
|
||||||
|
def test_iosxr_acls_replaced(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_2",
|
||||||
|
aces=[
|
||||||
|
dict(sequence="30",
|
||||||
|
grant="permit",
|
||||||
|
protocol="ospf",
|
||||||
|
source=dict(prefix="10.0.0.0/8"),
|
||||||
|
destination=dict(any="true"),
|
||||||
|
log="true")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="replaced"))
|
||||||
|
commands = [
|
||||||
|
'ipv4 access-list acl_2', 'no 10', 'no 20',
|
||||||
|
'30 permit ospf 10.0.0.0 0.255.255.255 any log'
|
||||||
|
]
|
||||||
|
result = self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_iosxr_acls_replaced_idempotent(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_2",
|
||||||
|
aces=[
|
||||||
|
dict(sequence="10",
|
||||||
|
grant="deny",
|
||||||
|
protocol='ipv4',
|
||||||
|
destination=dict(any='true'),
|
||||||
|
source=dict(any="true")),
|
||||||
|
dict(sequence="20",
|
||||||
|
grant="permit",
|
||||||
|
protocol="tcp",
|
||||||
|
destination=dict(any='true'),
|
||||||
|
source=dict(host="192.168.1.100")),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="replaced"))
|
||||||
|
result = self.execute_module(changed=False, commands=[])
|
||||||
|
|
||||||
|
def test_iosxr_acls_overridden(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_2",
|
||||||
|
aces=[
|
||||||
|
dict(sequence="40",
|
||||||
|
grant="permit",
|
||||||
|
protocol="ospf",
|
||||||
|
source=dict(any="true"),
|
||||||
|
destination=dict(any="true"),
|
||||||
|
log="true")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="overridden"))
|
||||||
|
commands = [
|
||||||
|
'no ipv6 access-list acl6_1', 'ipv4 access-list acl_2', 'no 10',
|
||||||
|
'no 20', '40 permit ospf any any log'
|
||||||
|
]
|
||||||
|
result = self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_iosxr_acls_overridden_idempotent(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_2",
|
||||||
|
aces=[
|
||||||
|
dict(sequence="10",
|
||||||
|
grant="deny",
|
||||||
|
protocol='ipv4',
|
||||||
|
destination=dict(any='true'),
|
||||||
|
source=dict(any="true")),
|
||||||
|
dict(sequence="20",
|
||||||
|
grant="permit",
|
||||||
|
protocol="tcp",
|
||||||
|
destination=dict(any='true'),
|
||||||
|
source=dict(host="192.168.1.100")),
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
dict(afi='ipv6',
|
||||||
|
acls=[
|
||||||
|
dict(name="acl6_1",
|
||||||
|
aces=[
|
||||||
|
dict(
|
||||||
|
sequence="10",
|
||||||
|
grant="deny",
|
||||||
|
protocol="icmpv6",
|
||||||
|
destination=dict(any='true'),
|
||||||
|
source=dict(any='true'),
|
||||||
|
)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="overridden"))
|
||||||
|
result = self.execute_module(changed=False, commands=[])
|
||||||
|
|
||||||
|
def test_iosxr_acls_deletedaces(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[dict(name="acl_2", aces=[dict(sequence="20")])])
|
||||||
|
],
|
||||||
|
state="deleted"))
|
||||||
|
commands = ['ipv4 access-list acl_2', 'no 20']
|
||||||
|
result = self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_iosxr_acls_deletedacls(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[dict(afi="ipv6", acls=[dict(name="acl6_1")])],
|
||||||
|
state="deleted"))
|
||||||
|
commands = ['no ipv6 access-list acl6_1']
|
||||||
|
result = self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_iosxr_acls_deletedafis(self):
|
||||||
|
set_module_args(dict(config=[dict(afi="ipv4")], state="deleted"))
|
||||||
|
commands = ['no ipv4 access-list acl_2']
|
||||||
|
result = self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_eos_acls_rendered(self):
|
||||||
|
set_module_args(
|
||||||
|
dict(config=[
|
||||||
|
dict(afi="ipv4",
|
||||||
|
acls=[
|
||||||
|
dict(name="acl_2",
|
||||||
|
aces=[
|
||||||
|
dict(grant="permit",
|
||||||
|
source=dict(any="true"),
|
||||||
|
destination=dict(any="true"),
|
||||||
|
protocol='igmp')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
state="rendered"))
|
||||||
|
commands = ['ipv4 access-list acl_2', 'permit igmp any any']
|
||||||
|
result = self.execute_module(changed=False)
|
||||||
|
self.assertEqual(sorted(result['rendered']), sorted(commands),
|
||||||
|
result['rendered'])
|
Loading…
Reference in New Issue