Migrated to cisco.nxos

pull/68298/head
Ansible Core Team 5 years ago committed by Matt Martz
parent ae8fb5e371
commit 3149db0869

@ -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)
#############################################
# 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 nxos_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 nxos_acl_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'access_groups': {
'elements': 'dict',
'options': {
'acls': {
'elements': 'dict',
'options': {
'direction': {
'choices': ['in', 'out'],
'required': True,
'type': 'str'
},
'name': {
'required': True,
'type': 'str'
},
'port': {
'type': 'bool'
}
},
'type': 'list'
},
'afi': {
'choices': ['ipv4', 'ipv6'],
'required': True,
'type': 'str'
}
},
'type': 'list'
},
'name': {
'required': True,
'type': 'str'
}
},
'type': 'list'
},
'running_config': {
'type': 'str'
},
'state': {
'choices': [
'deleted', 'gathered', 'merged', 'overridden', 'rendered',
'replaced', 'parsed'
],
'default':
'merged',
'type':
'str'
}
} # pylint: disable=C0301

@ -1,425 +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 nxos_acls module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class AclsArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_acls module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'acls': {
'elements': 'dict',
'options': {
'aces': {
'elements': 'dict',
'mutually_exclusive': [['grant', 'remark']],
'options': {
'destination': {
'mutually_exclusive':
[['address', 'any', 'host', 'prefix'],
[
'wildcard_bits', 'any', 'host',
'prefix'
]],
'options': {
'address': {
'type': 'str'
},
'any': {
'type': 'bool'
},
'host': {
'type': 'str'
},
'port_protocol': {
'mutually_exclusive': [[
'eq', 'lt', 'neq', 'gt',
'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'
},
'prefix': {
'type': 'str'
},
'wildcard_bits': {
'type': 'str'
}
},
'required_together':
[['address', 'wildcard_bits']],
'type': 'dict'
},
'dscp': {
'type': 'str'
},
'fragments': {
'type': 'bool'
},
'grant': {
'choices': ['permit', 'deny'],
'type': 'str'
},
'log': {
'type': 'bool'
},
'precedence': {
'type': 'str'
},
'protocol': {
'type': 'str'
},
'protocol_options': {
'mutually_exclusive':
[['icmp', 'igmp', 'tcp']],
'options': {
'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'
},
'message_code': {
'type': 'int'
},
'message_type': {
'type': 'int'
},
'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': {
'mutually_exclusive': [[
'dvmrp', 'host_query',
'host_report'
]],
'options': {
'dvmrp': {
'type': 'bool'
},
'host_query': {
'type': 'bool'
},
'host_report': {
'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', 'host', 'any',
'prefix'
]],
'options': {
'address': {
'type': 'str'
},
'any': {
'type': 'bool'
},
'host': {
'type': 'str'
},
'port_protocol': {
'mutually_exclusive':
[['eq', 'lt', 'neq', 'range'],
['eq', 'gt', 'neq', 'range']],
'options': {
'eq': {
'type': 'str'
},
'gt': {
'type': 'str'
},
'lt': {
'type': 'str'
},
'neq': {
'type': 'str'
},
'range': {
'options': {
'end': {
'type': 'str'
},
'start': {
'type': 'str'
}
},
'type': 'dict'
}
},
'type':
'dict'
},
'prefix': {
'type': 'str'
},
'wildcard_bits': {
'type': 'str'
}
},
'required_together':
[['address', 'wildcard_bits']],
'type':
'dict'
}
},
'type': 'list'
},
'name': {
'required': True,
'type': 'str'
}
},
'type': 'list'
},
'afi': {
'choices': ['ipv4', 'ipv6'],
'required': True,
'type': 'str'
}
},
'type': 'list'
},
'running_config': {
'type': 'str'
},
'state': {
'choices': [
'deleted', 'gathered', 'merged', 'overridden', 'rendered',
'replaced', 'parsed'
],
'default':
'merged',
'type':
'str'
}
} # pylint: disable=C0301

@ -1,56 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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.
#
#############################################
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
"""
The arg spec for the nxos_bfd_interfaces module
"""
class Bfd_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_bfd_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'name': {'type': 'str'},
'bfd': {
'choices': ['enable', 'disable'], 'type': 'str'},
'echo': {
'choices': ['enable', 'disable'], 'type': 'str'},
},
'type': 'list'
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -1,21 +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 nxos facts module.
"""
class FactsArgs(object):
""" The arg spec for the nxos facts module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'gather_subset': dict(default=['!config'], type='list'),
'gather_network_resources': dict(type='list'),
}

@ -1,54 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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.
#
#############################################
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
"""
The arg spec for the nxos_hsrp_interfaces module
"""
class Hsrp_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_hsrp_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'type': 'list',
'elements': 'dict',
'options': {
'name': {'type': 'str'},
'bfd': {
'choices': ['enable', 'disable'], 'type': 'str'},
},
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -1,80 +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 nxos_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class InterfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'description': {
'type': 'str'
},
'duplex': {
'choices': ['full', 'half', 'auto'],
'type': 'str'
},
'enabled': {
'type': 'bool'
},
'fabric_forwarding_anycast_gateway': {
'type': 'bool'
},
'ip_forward': {
'type': 'bool'
},
'mode': {
'choices': ['layer2', 'layer3'],
'type': 'str'
},
'mtu': {
'type': 'str'
},
'name': {
'required': True,
'type': 'str'
},
'speed': {
'type': 'str'
}
},
'type': 'list'
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -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)
#############################################
# 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 nxos_l2_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class L2_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_l2_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'access': {
'options': {
'vlan': {
'type': 'int'
}
},
'type': 'dict'
},
'mode': {
'type': 'str',
'choices': ['access', 'trunk']
},
'name': {
'required': True,
'type': 'str'
},
'trunk': {
'options': {
'allowed_vlans': {
'type': 'str'
},
'native_vlan': {
'type': 'int'
}
},
'type': 'dict'
}
},
'type': 'list'
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -1,90 +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 nxos_l3_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class L3_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_l3_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'dot1q': {
'type': 'int'
},
'ipv4': {
'elements': 'dict',
'options': {
'address': {
'type': 'str'
},
'secondary': {
'type': 'bool'
},
'tag': {
'type': 'int'
}
},
'type': 'list'
},
'ipv6': {
'elements': 'dict',
'options': {
'address': {
'type': 'str'
},
'tag': {
'type': 'int'
}
},
'type': 'list'
},
'name': {
'required': True,
'type': 'str'
},
'redirects': {
'type': 'bool',
},
'unreachables': {
'type': 'bool',
},
},
'type': 'list'
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -1,69 +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 nxos_lacp module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class LacpArgs(object):
"""The arg spec for the nxos_lacp module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'system': {
'options': {
'mac': {
'options': {
'address': {
'type': 'str'
},
'role': {
'choices': ['primary', 'secondary'],
'type': 'str'
}
},
'type': 'dict'
},
'priority': {
'type': 'int'
}
},
'type': 'dict'
}
},
'type': 'dict'
},
'state': {
'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'
}
}

@ -1,90 +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 nxos_lacp_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Lacp_interfacesArgs(object):
"""The arg spec for the nxos_lacp_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'convergence': {
'options': {
'graceful': {
'type': 'bool'
},
'vpc': {
'type': 'bool'
}
},
'type': 'dict'
},
'links': {
'options': {
'max': {
'type': 'int'
},
'min': {
'type': 'int'
}
},
'type': 'dict'
},
'mode': {
'choices': ['delay'],
'type': 'str'
},
'name': {
'required': True,
'type': 'str'
},
'port_priority': {
'type': 'int'
},
'rate': {
'choices': ['fast', 'normal'],
'type': 'str'
},
'suspend_individual': {
'type': 'bool'
}
},
'type': 'list'
},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
}

@ -1,49 +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 nxos_lag_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Lag_interfacesArgs(object):
"""The arg spec for the nxos_lag_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'elements': 'dict',
'options': {'members': {'elements': 'dict',
'options': {'member': {'type': 'str'},
'mode': {'type': 'str',
'choices': ['active', 'on', 'passive']},
'force': {'type': 'bool'}},
'type': 'list'},
'name': {'required': True, 'type': 'str'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -1,109 +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 nxos_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 nxos_lldp_global module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'holdtime': {
'type': 'int'
},
'port_id': {
'choices': [0, 1],
'type': 'int'
},
'reinit': {
'type': 'int'
},
'timer': {
'type': 'int'
},
'tlv_select': {
'options': {
'dcbxp': {
'type': 'bool'
},
'management_address': {
'options': {
'v4': {
'type': 'bool'
},
'v6': {
'type': 'bool'
}
},
'type': 'dict'
},
'port': {
'options': {
'description': {
'type': 'bool'
},
'vlan': {
'type': 'bool'
}
},
'type': 'dict'
},
'power_management': {
'type': 'bool'
},
'system': {
'options': {
'capabilities': {
'type': 'bool'
},
'description': {
'type': 'bool'
},
'name': {
'type': 'bool'
}
},
'type': 'dict'
}
},
'type': 'dict'
}
},
'type': 'dict'
},
'state': {
'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'
}
}

@ -1,75 +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 nxos_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 nxos_lldp_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'name': {
'required': True,
'type': 'str'
},
'receive': {
'type': 'bool'
},
'tlv_set': {
'options': {
'management_address': {
'type': 'str'
},
'vlan': {
'type': 'int'
}
},
'type': 'dict'
},
'transmit': {
'type': 'bool'
}
},
'type': 'list'
},
'running_config': {
'type': 'str'
},
'state': {
'choices': ['deleted', 'gathered', 'merged', 'overridden', 'rendered',
'replaced', 'parsed'],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -1,90 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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 nxos_telemetry module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class TelemetryArgs(object): # pylint: disable=R0903
"""The arg spec for the nxos_telemetry module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'options': {
'certificate': {
'options': {
'hostname': {'type': 'str'},
'key': {'type': 'str'}},
'type': 'dict'},
'compression': {'choices': ['gzip'], 'type': 'str'},
'source_interface': {'type': 'str'},
'vrf': {'type': 'str'},
'destination_groups': {
'options': {
'destination': {
'options': {
'encoding': {'choices': ['GPB', 'JSON'],
'type': 'str'},
'ip': {'type': 'str'},
'port': {'type': 'int'},
'protocol': {'choices': ['HTTP', 'TCP', 'UDP', 'gRPC'],
'type': 'str'}},
'type': 'dict'},
'id': {'type': 'int'}},
'type': 'list'},
'sensor_groups': {
'options': {
'data_source': {'choices': ['NX-API', 'DME', 'YANG'],
'type': 'str'},
'id': {'type': 'int'},
'path': {
'options': {
'depth': {'type': 'str'},
'filter_condition': {'type': 'str'},
'name': {'type': 'str'},
'query_condition': {'type': 'str'}},
'type': 'dict'}},
'type': 'list'},
'subscriptions': {
'options': {
'destination_group': {'type': 'int'},
'id': {'type': 'int'},
'sensor_group': {
'options': {
'id': {'type': 'int'},
'sample_interval': {'type': 'int'}},
'type': 'dict'}},
'type': 'list'}},
'type': 'dict'},
'state': {
'choices': ['merged', 'replaced', 'deleted'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301

@ -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 nxos_vlans module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class VlansArgs(object):
"""The arg spec for the nxos_vlans module
"""
def __init__(self, **kwargs):
pass
argument_spec = {'config': {'elements': 'dict',
'options': {'enabled': {'type': 'bool'},
'mapped_vni': {'type': 'int'},
'mode': {'choices': ['ce', 'fabricpath'],
'type': 'str'},
'name': {'type': 'str'},
'vlan_id': {'required': True, 'type': 'int'},
'state': {'choices': ['active', 'suspend'],
'type': 'str'}},
'type': 'list'},
'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'}}

@ -1,145 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# Telemetry Command Reference File
from __future__ import absolute_import, division, print_function
__metaclass__ = type
TMS_GLOBAL = '''
# The cmd_ref is a yaml formatted list of module commands.
# A leading underscore denotes a non-command variable; e.g. _template.
# TMS does not have convenient global json data so this cmd_ref uses raw cli configs.
---
_template: # _template holds common settings for all commands
# Enable feature telemetry if disabled
feature: telemetry
# Common get syntax for TMS commands
get_command: show run telemetry all
# Parent configuration for TMS commands
context:
- telemetry
certificate:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
kind: dict
getval: certificate (?P<key>\\S+) (?P<hostname>\\S+)$
setval: certificate {key} {hostname}
default:
key: ~
hostname: ~
compression:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
kind: str
getval: use-compression (\\S+)$
setval: 'use-compression {0}'
default: ~
context: &dpcontext
- telemetry
- destination-profile
source_interface:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
kind: str
getval: source-interface (\\S+)$
setval: 'source-interface {0}'
default: ~
context: *dpcontext
vrf:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
kind: str
getval: use-vrf (\\S+)$
setval: 'use-vrf {0}'
default: ~
context: *dpcontext
'''
TMS_DESTGROUP = '''
# The cmd_ref is a yaml formatted list of module commands.
# A leading underscore denotes a non-command variable; e.g. _template.
# TBD: Use Structured Where Possible
---
_template: # _template holds common settings for all commands
# Enable feature telemetry if disabled
feature: telemetry
# Common get syntax for TMS commands
get_command: show run telemetry all
# Parent configuration for TMS commands
context:
- telemetry
destination:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
multiple: true
kind: dict
getval: ip address (?P<ip>\\S+) port (?P<port>\\S+) protocol (?P<protocol>\\S+) encoding (?P<encoding>\\S+)
setval: ip address {ip} port {port} protocol {protocol} encoding {encoding}
default:
ip: ~
port: ~
protocol: ~
encoding: ~
'''
TMS_SENSORGROUP = '''
# The cmd_ref is a yaml formatted list of module commands.
# A leading underscore denotes a non-command variable; e.g. _template.
# TBD: Use Structured Where Possible
---
_template: # _template holds common settings for all commands
# Enable feature telemetry if disabled
feature: telemetry
# Common get syntax for TMS commands
get_command: show run telemetry all
# Parent configuration for TMS commands
context:
- telemetry
data_source:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
kind: str
getval: data-source (\\S+)$
setval: 'data-source {0}'
default: ~
path:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
multiple: true
kind: dict
getval: path (?P<name>(\\S+|".*"))( depth (?P<depth>\\S+))?( query-condition (?P<query_condition>\\S+))?( filter-condition (?P<filter_condition>\\S+))?$
setval: path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition}
default:
name: ~
depth: ~
query_condition: ~
filter_condition: ~
'''
TMS_SUBSCRIPTION = '''
# The cmd_ref is a yaml formatted list of module commands.
# A leading underscore denotes a non-command variable; e.g. _template.
# TBD: Use Structured Where Possible
---
_template: # _template holds common settings for all commands
# Enable feature telemetry if disabled
feature: telemetry
# Common get syntax for TMS commands
get_command: show run telemetry all
# Parent configuration for TMS commands
context:
- telemetry
destination_group:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
multiple: true
kind: int
getval: dst-grp (\\S+)$
setval: 'dst-grp {0}'
default: ~
sensor_group:
_exclude: ['N3K', 'N5K', 'N6k', 'N7k']
multiple: true
kind: dict
getval: snsr-grp (?P<id>\\S+) sample-interval (?P<sample_interval>\\S+)$
setval: snsr-grp {id} sample-interval {sample_interval}
default:
id: ~
sample_interval: ~
'''

@ -1,303 +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 nxos_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.common.utils import to_list, remove_empties, dict_diff
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list, get_interface_type, normalize_interface
class Acl_interfaces(ConfigBase):
"""
The nxos_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 edit_config(self, commands):
"""Wrapper method for `_connection.edit_config()`
This exists solely to allow the unit test framework to mock device connection calls.
"""
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
commands = list()
state = self._module.params['state']
action_states = ['merged', 'replaced', 'deleted', 'overridden']
if state == 'gathered':
result['gathered'] = self.get_acl_interfaces_facts()
elif state == 'rendered':
result['rendered'] = self.set_config({})
# no need to fetch facts for rendered
elif state == 'parsed':
result['parsed'] = self.set_config({})
# no need to fetch facts for parsed
else:
existing_acl_interfaces_facts = self.get_acl_interfaces_facts()
commands.extend(self.set_config(existing_acl_interfaces_facts))
if commands and state in action_states:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['before'] = existing_acl_interfaces_facts
result['commands'] = commands
changed_acl_interfaces_facts = self.get_acl_interfaces_facts()
if result['changed']:
result['after'] = 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
"""
config = self._module.params['config']
want = []
if config:
for w in config:
if get_interface_type(w['name']) == 'loopback':
self._module.fail_json(
msg='This module works with ethernet, management or port-channe')
w.update({'name': normalize_interface(w['name'])})
want.append(remove_empties(w))
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 == 'overridden':
commands = (self._state_overridden(want, have))
elif state == 'deleted':
commands = (self._state_deleted(want, have))
elif state == 'rendered':
commands = self._state_rendered(want)
elif state == 'parsed':
want = self._module.params['running_config']
commands = self._state_parsed(want)
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(w, have))
elif state == 'replaced':
commands.extend(self._state_replaced(w, have))
return commands
def _state_parsed(self, want):
return self.get_acl_interfaces_facts(want)
def _state_rendered(self, want):
commands = []
for w in want:
commands.extend(self.set_commands(w, {}))
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
"""
new_commands = []
del_dict = {'name': want['name'], 'access_groups': []}
obj_in_have = search_obj_in_list(want['name'], have, 'name')
if obj_in_have != want:
commands = []
if obj_in_have and 'access_groups' in obj_in_have.keys():
for ag in obj_in_have['access_groups']:
want_afi = []
if want.get('access_groups'):
want_afi = search_obj_in_list(
ag['afi'], want['access_groups'], 'afi')
if not want_afi:
# whatever in have is not in want
del_dict['access_groups'].append(ag)
else:
del_acl = []
for acl in ag['acls']:
if want_afi.get('acls'):
if acl not in want_afi['acls']:
del_acl.append(acl)
else:
del_acl.append(acl)
afi = want_afi['afi']
del_dict['access_groups'].append(
{'afi': afi, 'acls': del_acl})
commands.extend(self._state_deleted([del_dict], have))
commands.extend(self._state_merged(want, have))
new_commands.append(commands[0])
commands = [commands[i]
for i in range(1, len(commands)) if commands[i] != commands[0]]
new_commands.extend(commands)
return new_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 = []
want_intf = [w['name'] for w in want]
for h in have:
if h['name'] not in want_intf:
commands.extend(self._state_deleted([h], have))
for w in want:
commands.extend(self._state_replaced(w, 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
"""
return self.set_commands(want, have)
def set_commands(self, want, have, deleted=False):
commands = []
have_name = search_obj_in_list(want['name'], have, 'name')
if have_name and have_name.get('access_groups'):
if want.get('access_groups'):
for w_afi in want['access_groups']:
ip = 'ipv6'
if w_afi['afi'] == 'ipv4':
ip = 'ip'
have_afi = search_obj_in_list(
w_afi['afi'], have_name['access_groups'], 'afi')
if have_afi:
new_acls = []
if deleted:
if w_afi.get('acls') and have_afi.get('acls'):
new_acls = [
acl for acl in w_afi.get('acls') if acl in have_afi.get('acls')]
elif 'acls' not in w_afi.keys():
new_acls = have_afi.get('acls')
else:
if w_afi.get('acls'):
new_acls = [
acl for acl in w_afi['acls'] if acl not in have_afi['acls']]
commands.extend(self.process_acl(
new_acls, ip, deleted))
else:
if not deleted:
if w_afi.get('acls'):
commands.extend(
self.process_acl(w_afi['acls'], ip))
else:
# only name is given to delete
if deleted and 'access_groups' in have_name.keys():
commands.extend(self.process_access_group(have_name, True))
else:
if not deleted: # and 'access_groups' in have_name.keys():
commands.extend(self.process_access_group(want))
if len(commands) > 0:
commands.insert(0, 'interface ' + want['name'])
return commands
def process_access_group(self, item, deleted=False):
commands = []
for ag in item['access_groups']:
ip = 'ipv6'
if ag['afi'] == 'ipv4':
ip = 'ip'
if ag.get('acls'):
commands.extend(self.process_acl(
ag['acls'], ip, deleted))
return commands
def process_acl(self, acls, ip, deleted=False):
commands = []
no = ''
if deleted:
no = 'no '
for acl in acls:
port = ''
if acl.get('port'):
port = ' port'
ag = ' access-group '
if ip == 'ipv6':
ag = ' traffic-filter '
commands.append(no + ip + port + ag +
acl['name'] + ' ' + acl['direction'])
return commands
def _state_deleted(self, main_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 main_want:
for want in main_want:
commands.extend(self.set_commands(want, have, deleted=True))
else:
for h in have:
commands.extend(self.set_commands(h, have, deleted=True))
return commands

@ -1,690 +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 nxos_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
import socket
import re
from copy import deepcopy
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, remove_empties, dict_diff
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.argspec.acls.acls import AclsArgs
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list, get_interface_type, normalize_interface
class Acls(ConfigBase):
"""
The nxos_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 edit_config(self, commands):
"""Wrapper method for `_connection.edit_config()`
This exists solely to allow the unit test framework to mock device connection calls.
"""
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
commands = list()
state = self._module.params['state']
action_states = ['merged', 'replaced', 'deleted', 'overridden']
if state == 'gathered':
result['gathered'] = self.get_acls_facts()
elif state == 'rendered':
result['rendered'] = self.set_config({})
elif state == 'parsed':
result['parsed'] = self.set_config({})
else:
existing_acls_facts = self.get_acls_facts()
commands.extend(self.set_config(existing_acls_facts))
if commands and state in action_states:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['before'] = existing_acls_facts
result['commands'] = commands
changed_acls_facts = self.get_acls_facts()
if result['changed']:
result['after'] = 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
"""
config = self._module.params['config']
want = []
if config:
for w in config:
want.append(remove_empties(w))
have = existing_acls_facts
if want:
want = self.convert_values(want)
resp = self.set_state(want, have)
return to_list(resp)
def convert_values(self, want):
'''
This method is used to map and convert the user given values with what will actually be present in the device configuation
'''
port_protocol = {
515: 'lpd',
517: 'talk',
7: 'echo',
9: 'discard',
12: 'exec',
13: 'login',
14: 'cmd',
109: 'pop2',
19: 'chargen',
20: 'ftp-data',
21: 'ftp',
23: 'telnet',
25: 'smtp',
540: 'uucp',
543: 'klogin',
544: 'kshell',
37: 'time',
43: 'whois',
49: 'tacacs',
179: 'bgp',
53: 'domain',
194: 'irc',
70: 'gopher',
79: 'finger',
80: 'www',
101: 'hostname',
3949: 'drip',
110: 'pop3',
111: 'sunrpc',
496: 'pim-auto-rp',
113: 'ident',
119: 'nntp'
}
protocol = {
1: 'icmp',
2: 'igmp',
4: 'ip',
6: 'tcp',
103: 'pim',
108: 'pcp',
47: 'gre',
17: 'udp',
50: 'esp',
51: 'ahp',
88: 'eigrp',
89: 'ospf',
94: 'nos'
}
precedence = {
0: 'routine',
1: 'priority',
2: 'immediate',
3: 'flash',
4: 'flash-override',
5: 'critical',
6: 'internet',
7: 'network'
}
dscp = {
10: 'AF11',
12: 'AF12',
14: 'AF13',
18: 'AF21',
20: 'AF22',
22: 'AF23',
26: 'AF31',
28: 'AF32',
30: 'AF33',
34: 'AF41',
36: 'AF42',
38: 'AF43',
8: 'CS1',
16: 'CS2',
24: 'CS3',
32: 'CS4',
40: 'CS5',
48: 'CS6',
56: 'CS7',
0: 'Default',
46: 'EF'
}
# port_pro_num = list(protocol.keys())
for afi in want:
if 'acls' in afi.keys():
for acl in afi['acls']:
if 'aces' in acl.keys():
for ace in acl['aces']:
if 'dscp' in ace.keys():
if ace['dscp'].isdigit():
ace['dscp'] = dscp[int(ace['dscp'])]
ace['dscp'] = ace['dscp'].lower()
if 'precedence' in ace.keys():
if ace['precedence'].isdigit():
ace['precedence'] = precedence[int(
ace['precedence'])]
if 'protocol' in ace.keys(
) and ace['protocol'].isdigit() and int(
ace['protocol']) in protocol.keys():
ace['protocol'] = protocol[int(
ace['protocol'])]
# convert number to name
if 'protocol' in ace.keys(
) and ace['protocol'] in ['tcp', 'udp']:
for end in ['source', 'destination']:
if 'port_protocol' in ace[end].keys():
key = list(ace[end]
['port_protocol'].keys())[0]
# key could be eq,gt,lt,neq or range
if key != 'range':
val = ace[end]['port_protocol'][
key]
if val.isdigit() and int(val) in port_protocol.keys(
):
ace[end]['port_protocol'][
key] = port_protocol[int(
val)]
else:
st = int(ace[end]['port_protocol']
['range']['start'])
end = int(ace[end]['port_protocol']
['range']['end'])
if st in port_protocol.keys():
ace[end]['port_protocol'][
'range'][
'start'] = port_protocol[
st]
if end in port_protocol.keys():
ace[end]['port_protocol'][
'range'][
'end'] = port_protocol[
end]
return want
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 == 'overridden':
commands = (self._state_overridden(want, have))
elif state == 'deleted':
commands = (self._state_deleted(want, have))
elif state == 'rendered':
commands = self._state_rendered(want)
elif state == 'parsed':
want = self._module.params['running_config']
commands = self._state_parsed(want)
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(w, have))
elif state == 'replaced':
commands.extend(self._state_replaced(w, have))
if state != 'parsed':
commands = [c.strip() for c in commands]
return commands
def _state_parsed(self, want):
return self.get_acls_facts(want)
def _state_rendered(self, want):
commands = []
for w in want:
commands.extend(self.set_commands(w, {}))
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 = []
have_afi = search_obj_in_list(want['afi'], have, 'afi')
del_dict = {'acls': []}
want_names = []
if have_afi != want:
if have_afi:
del_dict.update({'afi': have_afi['afi'], 'acls': []})
if want.get('acls'):
want_names = [w['name'] for w in want['acls']]
have_names = [h['name'] for h in have_afi['acls']]
want_acls = want.get('acls')
for w in want_acls:
acl_commands = []
if w['name'] not in have_names:
# creates new ACL in replaced state
merge_dict = {'afi': want['afi'], 'acls': [w]}
commands.extend(
self._state_merged(merge_dict, have))
else:
# acl in want exists in have
have_name = search_obj_in_list(
w['name'], have_afi['acls'], 'name')
have_aces = have_name.get('aces') if have_name.get(
'aces') else []
merge_aces = []
del_aces = []
w_aces = w.get('aces') if w.get('aces') else []
for ace in have_aces:
if ace not in w_aces:
del_aces.append(ace)
for ace in w_aces:
if ace not in have_aces:
merge_aces.append(ace)
merge_dict = {
'afi': want['afi'],
'acls': [{
'name': w['name'],
'aces': merge_aces
}]
}
del_dict = {
'afi': want['afi'],
'acls': [{
'name': w['name'],
'aces': del_aces
}]
}
if del_dict['acls']:
acl_commands.extend(
self._state_deleted([del_dict], have))
acl_commands.extend(
self._state_merged(merge_dict, have))
for i in range(1, len(acl_commands)):
if acl_commands[i] == acl_commands[0]:
acl_commands[i] = ''
commands.extend(acl_commands)
else:
acls = []
# no acls given in want, so delete all have acls
for acl in have_afi['acls']:
acls.append({'name': acl['name']})
del_dict['acls'] = acls
if del_dict['acls']:
commands.extend(self._state_deleted([del_dict], have))
else:
# want_afi is not present in have
commands.extend(self._state_merged(want, have))
commands = list(filter(None, 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 = []
want_afi = [w['afi'] for w in want]
for h in have:
if h['afi'] in want_afi:
w = search_obj_in_list(h['afi'], want, 'afi')
for h_acl in h['acls']:
w_acl = search_obj_in_list(h_acl['name'], w['acls'],
'name')
if not w_acl:
del_dict = {
'afi': h['afi'],
'acls': [{
'name': h_acl['name']
}]
}
commands.extend(self._state_deleted([del_dict], have))
else:
# if afi is not in want
commands.extend(self._state_deleted([{'afi': h['afi']}], have))
for w in want:
commands.extend(self._state_replaced(w, 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
"""
return self.set_commands(want, have)
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: # and have != want:
for w in want:
ip = 'ipv6' if w['afi'] == 'ipv6' else 'ip'
acl_names = []
have_afi = search_obj_in_list(w['afi'], have, 'afi')
# if want['afi] not in have, ignore
if have_afi:
if w.get('acls'):
for acl in w['acls']:
if 'aces' in acl.keys():
have_name = search_obj_in_list(
acl['name'], have_afi['acls'], 'name')
if have_name:
ace_commands = []
flag = 0
for ace in acl['aces']:
if list(ace.keys()) == ['sequence']:
# only sequence number is specified to be deleted
if 'aces' in have_name.keys():
for h_ace in have_name['aces']:
if h_ace[
'sequence'] == ace[
'sequence']:
ace_commands.append(
'no ' +
str(ace['sequence']
))
flag = 1
else:
if 'aces' in have_name.keys():
for h_ace in have_name['aces']:
# when want['ace'] does not have seq number
if 'sequence' not in ace.keys(
):
del h_ace['sequence']
if ace == h_ace:
ace_commands.append(
'no ' +
self.process_ace(
ace))
flag = 1
if flag:
ace_commands.insert(
0,
ip + ' access-list ' + acl['name'])
commands.extend(ace_commands)
else:
# only name given
for h in have_afi['acls']:
if h['name'] == acl['name']:
acl_names.append(acl['name'])
for name in acl_names:
commands.append('no ' + ip + ' access-list ' +
name)
else:
# 'only afi is given'
if have_afi.get('acls'):
for h in have_afi['acls']:
acl_names.append(h['name'])
for name in acl_names:
commands.append('no ' + ip + ' access-list ' +
name)
else:
v6 = []
v4 = []
v6_local = v4_local = None
for h in have:
if h['afi'] == 'ipv6':
v6 = (acl['name'] for acl in h['acls'])
if 'match_local_traffic' in h.keys():
v6_local = True
else:
v4 = (acl['name'] for acl in h['acls'])
if 'match_local_traffic' in h.keys():
v4_local = True
self.no_commands(v4, commands, v4_local, 'ip')
self.no_commands(v6, commands, v6_local, 'ipv6')
for name in v6:
commands.append('no ipv6 access-list ' + name)
if v4_local:
commands.append('no ipv6 access-list match-local-traffic')
return commands
def no_commands(self, v_list, commands, match_local, ip):
for name in v_list:
commands.append('no ' + ip + ' access-list ' + name)
if match_local:
commands.append('no ' + ip + ' access-list match-local-traffic')
def set_commands(self, want, have):
commands = []
have_afi = search_obj_in_list(want['afi'], have, 'afi')
ip = ''
if 'v6' in want['afi']:
ip = 'ipv6 '
else:
ip = 'ip '
if have_afi:
if want.get('acls'):
for w_acl in want['acls']:
have_acl = search_obj_in_list(w_acl['name'],
have_afi['acls'], 'name')
name = w_acl['name']
flag = 0
ace_commands = []
if have_acl != w_acl:
if have_acl:
ace_list = []
if w_acl.get('aces') and have_acl.get('aces'):
# case 1 --> sequence number not given in want --> new ace
# case 2 --> new sequence number in want --> new ace
# case 3 --> existing sequence number given --> update rule (only for merged state.
# For replaced and overridden, rule is deleted in the state's config)
ace_list = [
item for item in w_acl['aces']
if 'sequence' not in item.keys()
] # case 1
want_seq = [
item['sequence'] for item in w_acl['aces']
if 'sequence' in item.keys()
]
have_seq = [
item['sequence']
for item in have_acl['aces']
]
new_seq = list(set(want_seq) - set(have_seq))
common_seq = list(
set(want_seq).intersection(set(have_seq)))
temp_list = [
item for item in w_acl['aces']
if 'sequence' in item.keys()
and item['sequence'] in new_seq
] # case 2
ace_list.extend(temp_list)
for w in w_acl['aces']:
self.argument_spec = AclsArgs.argument_spec
params = utils.validate_config(
self.argument_spec, {
'config': [{
'afi':
want['afi'],
'acls': [{
'name': name,
'aces': ace_list
}]
}]
})
if 'sequence' in w.keys(
) and w['sequence'] in common_seq:
temp_obj = search_obj_in_list(
w['sequence'], have_acl['aces'],
'sequence') # case 3
if temp_obj != w:
for key, val in w.items():
temp_obj[key] = val
ace_list.append(temp_obj)
if self._module.params[
'state'] == 'merged':
ace_commands.append(
'no ' + str(w['sequence']))
# remove existing rule to update it
elif w_acl.get('aces'):
# 'have' has ACL defined without any ACE
ace_list = [item for item in w_acl['aces']]
for w_ace in ace_list:
ace_commands.append(
self.process_ace(w_ace).strip())
flag = 1
if flag:
ace_commands.insert(0,
ip + 'access-list ' + name)
else:
commands.append(ip + 'access-list ' + name)
if 'aces' in w_acl.keys():
for w_ace in w_acl['aces']:
commands.append(
self.process_ace(w_ace).strip())
commands.extend(ace_commands)
else:
if want.get('acls'):
for w_acl in want['acls']:
name = w_acl['name']
commands.append(ip + 'access-list ' + name)
if 'aces' in w_acl.keys():
for w_ace in w_acl['aces']:
commands.append(self.process_ace(w_ace).strip())
return commands
def process_ace(self, w_ace):
command = ''
ace_keys = w_ace.keys()
if 'remark' in ace_keys:
command += 'remark ' + w_ace['remark'] + ' '
else:
command += w_ace['grant'] + ' '
if 'protocol' in ace_keys:
command += w_ace['protocol'] + ' '
src = self.get_address(w_ace['source'], w_ace['protocol'])
dest = self.get_address(w_ace['destination'],
w_ace['protocol'])
command += src + dest
if 'protocol_options' in ace_keys:
pro = list(w_ace['protocol_options'].keys())[0]
if pro != w_ace['protocol']:
self._module.fail_json(
msg='protocol and protocol_options mismatch')
flags = ''
for k in w_ace['protocol_options'][pro].keys():
k = re.sub('_', '-', k)
flags += k + ' '
command += flags
if 'dscp' in ace_keys:
command += 'dscp ' + w_ace['dscp'] + ' '
if 'fragments' in ace_keys:
command += 'fragments '
if 'precedence' in ace_keys:
command += 'precedence ' + w_ace['precedence'] + ' '
if 'log' in ace_keys:
command += 'log '
if 'sequence' in ace_keys:
command = str(w_ace['sequence']) + ' ' + command
return command
def get_address(self, endpoint, pro=''):
ret_addr = ''
keys = list(endpoint.keys())
if 'address' in keys:
if 'wildcard_bits' not in keys:
self._module.fail_json(
msg='wildcard bits not specified for address')
else:
ret_addr = endpoint['address'] + \
' ' + endpoint['wildcard_bits'] + ' '
elif 'any' in keys:
ret_addr = 'any '
elif 'host' in keys:
ret_addr = 'host ' + endpoint['host'] + ' '
elif 'prefix' in keys:
ret_addr = endpoint['prefix'] + ' '
if pro in ['tcp', 'udp']:
if 'port_protocol' in keys:
options = self.get_options(endpoint['port_protocol'])
ret_addr += options
return ret_addr
def get_options(self, item):
com = ''
subkey = list(item.keys())
if 'range' in subkey:
com = 'range ' + item['range']['start'] + \
' ' + item['range']['end'] + ' '
else:
com = subkey[0] + ' ' + item[subkey[0]] + ' '
return com

@ -1,264 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
nxos_bfd_interfaces class
This class creates a command set to bring the current device configuration
to a desired end-state. The command set is based on a comparison of the
current configuration (as dict) and the provided configuration (as dict).
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import dict_diff, to_list
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list
class Bfd_interfaces(ConfigBase):
"""
The nxos_bfd_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'bfd_interfaces',
]
# exclude_params = []
def __init__(self, module):
super(Bfd_interfaces, self).__init__(module)
def get_bfd_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:returns: A list of interface configs and a platform string
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
bfd_interfaces_facts = facts['ansible_network_resources'].get('bfd_interfaces', [])
platform = facts.get('ansible_net_platform', '')
return bfd_interfaces_facts, platform
def edit_config(self, commands):
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
cmds = list()
existing_bfd_interfaces_facts, platform = self.get_bfd_interfaces_facts()
cmds.extend(self.set_config(existing_bfd_interfaces_facts, platform))
if cmds:
if not self._module.check_mode:
self.edit_config(cmds)
result['changed'] = True
result['commands'] = cmds
changed_bfd_interfaces_facts, platform = self.get_bfd_interfaces_facts()
result['before'] = existing_bfd_interfaces_facts
if result['changed']:
result['after'] = changed_bfd_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_bfd_interfaces_facts, platform):
""" 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
"""
if re.search('N[56]K', platform):
# Some platforms do not support the 'bfd' interface keyword;
# remove the 'bfd' key from each want/have interface.
orig_want = self._module.params['config']
want = []
for w in orig_want:
del w['bfd']
want.append(w)
orig_have = existing_bfd_interfaces_facts
have = []
for h in orig_have:
del h['bfd']
have.append(h)
else:
want = self._module.params['config']
have = existing_bfd_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']
if state in ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
cmds = list()
if state == 'overridden':
cmds.extend(self._state_overridden(want, have))
elif state == 'deleted':
cmds.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
cmds.extend(self._state_merged(flatten_dict(w), have))
elif state == 'replaced':
cmds.extend(self._state_replaced(flatten_dict(w), have))
return cmds
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
"""
cmds = []
obj_in_have = search_obj_in_list(want['name'], have, 'name')
if obj_in_have:
diff = dict_diff(want, obj_in_have)
else:
diff = want
merged_cmds = self.set_commands(want, have)
if 'name' not in diff:
diff['name'] = want['name']
replaced_cmds = []
if obj_in_have:
replaced_cmds = self.del_attribs(diff)
if replaced_cmds or merged_cmds:
for cmd in set(replaced_cmds).intersection(set(merged_cmds)):
merged_cmds.remove(cmd)
cmds.extend(replaced_cmds)
cmds.extend(merged_cmds)
return cmds
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
"""
cmds = []
for h in have:
# Clean up bfd attrs for any interfaces not listed in the play
h = flatten_dict(h)
obj_in_want = flatten_dict(search_obj_in_list(h['name'], want, 'name'))
if obj_in_want:
# Let the 'want' loop handle all vals for this interface
continue
cmds.extend(self.del_attribs(h))
for w in want:
# Update any want attrs if needed. The overridden state considers
# the play as the source of truth for the entire device, therefore
# set any unspecified attrs to their default state.
w = self.set_none_vals_to_defaults(flatten_dict(w))
cmds.extend(self.set_commands(w, have))
return cmds
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
"""
return self.set_commands(want, have)
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
"""
if not (want or have):
return []
cmds = []
if want:
for w in want:
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
cmds.extend(self.del_attribs(obj_in_have))
else:
for h in have:
cmds.extend(self.del_attribs(flatten_dict(h)))
return cmds
def del_attribs(self, obj):
if not obj or len(obj.keys()) == 1:
return []
cmds = []
# 'bfd' and 'bfd echo' are enabled by default so the handling is
# counter-intuitive; we are enabling them to remove them. The end result
# is that they are removed from the interface config on the device.
if 'bfd' in obj and 'disable' in obj['bfd']:
cmds.append('bfd')
if 'echo' in obj and 'disable' in obj['echo']:
cmds.append('bfd echo')
if cmds:
cmds.insert(0, 'interface ' + obj['name'])
return cmds
def set_none_vals_to_defaults(self, want):
# Set dict None values to default states
if 'bfd' in want and want['bfd'] is None:
want['bfd'] = 'enable'
if 'echo' in want and want['echo'] is None:
want['echo'] = 'enable'
return want
def diff_of_dicts(self, want, obj_in_have):
diff = set(want.items()) - set(obj_in_have.items())
diff = dict(diff)
if diff and want['name'] == obj_in_have['name']:
diff.update({'name': want['name']})
return diff
def add_commands(self, want):
if not want:
return []
cmds = []
if 'bfd' in want and want['bfd'] is not None:
cmd = 'bfd' if want['bfd'] == 'enable' else 'no bfd'
cmds.append(cmd)
if 'echo' in want and want['echo'] is not None:
cmd = 'bfd echo' if want['echo'] == 'enable' else 'no bfd echo'
cmds.append(cmd)
if cmds:
cmds.insert(0, 'interface ' + want['name'])
return cmds
def set_commands(self, want, have):
cmds = []
obj_in_have = flatten_dict(search_obj_in_list(want['name'], have, 'name'))
if not obj_in_have:
cmds = self.add_commands(want)
else:
diff = self.diff_of_dicts(want, obj_in_have)
cmds = self.add_commands(diff)
return cmds

@ -1,248 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The nxos hsrp_interfaces class
This class creates a command set to bring the current device configuration
to a desired end-state. The command set is based on a comparison of the
current configuration (as dict) and the provided configuration (as dict).
"""
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 dict_diff, to_list, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, get_interface_type, normalize_interface, search_obj_in_list, vlan_range_to_list
class Hsrp_interfaces(ConfigBase):
"""
The nxos_hsrp_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'hsrp_interfaces',
]
def __init__(self, module):
super(Hsrp_interfaces, self).__init__(module)
def get_hsrp_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)
hsrp_interfaces_facts = facts['ansible_network_resources'].get('hsrp_interfaces', [])
return hsrp_interfaces_facts
def edit_config(self, commands):
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
cmds = list()
existing_hsrp_interfaces_facts = self.get_hsrp_interfaces_facts()
cmds.extend(self.set_config(existing_hsrp_interfaces_facts))
if cmds:
if not self._module.check_mode:
self.edit_config(cmds)
result['changed'] = True
result['commands'] = cmds
changed_hsrp_interfaces_facts = self.get_hsrp_interfaces_facts()
result['before'] = existing_hsrp_interfaces_facts
if result['changed']:
result['after'] = changed_hsrp_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_hsrp_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
"""
config = self._module.params['config']
want = []
if config:
for w in config:
w.update({'name': normalize_interface(w['name'])})
want.append(w)
have = existing_hsrp_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']
# check for 'config' keyword in play
if state in ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
cmds = list()
if state == 'overridden':
cmds.extend(self._state_overridden(want, have))
elif state == 'deleted':
cmds.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
cmds.extend(self._state_merged(flatten_dict(w), have))
elif state == 'replaced':
cmds.extend(self._state_replaced(flatten_dict(w), have))
return cmds
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
"""
cmds = []
obj_in_have = search_obj_in_list(want['name'], have, 'name')
if obj_in_have:
diff = dict_diff(want, obj_in_have)
else:
diff = want
merged_cmds = self.set_commands(want, have)
if 'name' not in diff:
diff['name'] = want['name']
replaced_cmds = []
if obj_in_have:
replaced_cmds = self.del_attribs(diff)
if replaced_cmds or merged_cmds:
for cmd in set(replaced_cmds).intersection(set(merged_cmds)):
merged_cmds.remove(cmd)
cmds.extend(replaced_cmds)
cmds.extend(merged_cmds)
return cmds
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
"""
cmds = []
for h in have:
# Check existing states, set to default if not in want or different than want
h = flatten_dict(h)
obj_in_want = search_obj_in_list(h['name'], want, 'name')
if obj_in_want:
# Let the 'want' loop handle all vals for this interface
continue
cmds.extend(self.del_attribs(h))
for w in want:
# Update any want attrs if needed. The overridden state considers
# the play as the source of truth for the entire device, therefore
# set any unspecified attrs to their default state.
w = self.set_none_vals_to_defaults(flatten_dict(w))
cmds.extend(self.set_commands(w, have))
return cmds
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
"""
return self.set_commands(want, have)
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
"""
if not (want or have):
return []
cmds = []
if want:
for w in want:
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
cmds.extend(self.del_attribs(obj_in_have))
else:
for h in have:
cmds.extend(self.del_attribs(flatten_dict(h)))
return cmds
def del_attribs(self, obj):
if not obj or len(obj.keys()) == 1:
return []
cmds = []
if 'bfd' in obj:
cmds.append('no hsrp bfd')
if cmds:
cmds.insert(0, 'interface ' + obj['name'])
return cmds
def set_none_vals_to_defaults(self, want):
# Set dict None values to default states
if 'bfd' in want and want['bfd'] is None:
want['bfd'] = 'disable'
return want
def diff_of_dicts(self, want, obj_in_have):
diff = set(want.items()) - set(obj_in_have.items())
diff = dict(diff)
if diff and want['name'] == obj_in_have['name']:
diff.update({'name': want['name']})
return diff
def add_commands(self, want, obj_in_have):
if not want:
return []
cmds = []
if 'bfd' in want and want['bfd'] is not None:
if want['bfd'] == 'enable':
cmd = 'hsrp bfd'
cmds.append(cmd)
elif want['bfd'] == 'disable' and obj_in_have and obj_in_have.get('bfd') == 'enable':
cmd = 'no hsrp bfd'
cmds.append(cmd)
if cmds:
cmds.insert(0, 'interface ' + want['name'])
return cmds
def set_commands(self, want, have):
cmds = []
obj_in_have = search_obj_in_list(want['name'], have, 'name')
if not obj_in_have:
cmds = self.add_commands(want, obj_in_have)
else:
diff = self.diff_of_dicts(want, obj_in_have)
cmds = self.add_commands(diff, obj_in_have)
return cmds

@ -1,379 +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 nxos_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
import re
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import dict_diff, to_list, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import normalize_interface, search_obj_in_list
from ansible.module_utils.network.nxos.utils.utils import remove_rsvd_interfaces
from ansible.module_utils.network.nxos.nxos import default_intf_enabled
class Interfaces(ConfigBase):
"""
The nxos_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'interfaces',
]
exclude_params = [
'description',
'mtu',
'speed',
'duplex',
]
def __init__(self, module):
super(Interfaces, self).__init__(module)
def get_interfaces_facts(self, get_default_interfaces=False):
""" Get the 'facts' (the current configuration)
:get_default_interfaces: boolean - when True include a list of existing-but-default interface names in the facts dict.
- The defaults list is primarily used to detect non-existent virtual interfaces.
: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')
interfaces_facts = remove_rsvd_interfaces(interfaces_facts)
if get_default_interfaces:
default_interfaces = facts['ansible_network_resources'].get('default_interfaces', [])
interfaces_facts.append(default_interfaces)
self.intf_defs = facts.get('intf_defs', {})
return interfaces_facts
def edit_config(self, commands):
"""Wrapper method for `_connection.edit_config()`
This method exists solely to allow the unit test framework to mock device connection calls.
"""
return self._connection.edit_config(commands)
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(get_default_interfaces=True)
default_intf_list = existing_interfaces_facts.pop()
commands.extend(self.set_config(existing_interfaces_facts, default_intf_list))
if commands:
if not self._module.check_mode:
self.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, default_intf_list):
""" 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
"""
config = self._module.params.get('config')
want = []
if config:
for w in config:
w.update({'name': normalize_interface(w['name'])})
want.append(remove_empties(w))
have = deepcopy(existing_interfaces_facts)
for i in want:
# 'have' does not include objects from the default_interfaces list.
# Add any 'want' names from default_interfaces to the 'have' list.
if i['name'] in default_intf_list:
have.append({'name': i['name']})
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 ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
commands = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(w, have))
elif state == 'replaced':
commands.extend(self._state_replaced(w, have))
return commands
def _state_replaced(self, w, 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 = []
name = w['name']
obj_in_have = search_obj_in_list(name, have, 'name')
if obj_in_have:
# If 'w' does not specify mode then intf may need to change to its
# default mode, however default mode may depend on sysdef.
if not w.get('mode') and re.search('Ethernet|port-channel', name):
sysdefs = self.intf_defs['sysdefs']
sysdef_mode = sysdefs['mode']
if obj_in_have.get('mode') != sysdef_mode:
w['mode'] = sysdef_mode
diff = dict_diff(w, obj_in_have)
else:
diff = w
merged_commands = self.set_commands(w, have)
if merged_commands:
# merged_commands:
# - These commands are changes specified by the playbook.
# - merged_commands apply to both existing and new objects
# replaced_commands:
# - These are the unspecified commands, used to reset any params
# that are not already set to default states
# - replaced_commands should only be used on 'have' objects
# (interfaces that already exist)
if obj_in_have:
if 'name' not in diff:
diff['name'] = name
wkeys = w.keys()
dkeys = diff.keys()
for k in wkeys:
if k in self.exclude_params and k in dkeys:
del diff[k]
replaced_commands = self.del_attribs(diff)
cmds = set(replaced_commands).intersection(set(merged_commands))
for cmd in cmds:
merged_commands.remove(cmd)
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
"""
# overridden is the same as replaced behavior except for the scope.
cmds = []
existing_interfaces = []
for h in have:
existing_interfaces.append(h['name'])
obj_in_want = search_obj_in_list(h['name'], want, 'name')
if obj_in_want:
if h != obj_in_want:
replaced_cmds = self._state_replaced(obj_in_want, [h])
if replaced_cmds:
cmds.extend(replaced_cmds)
else:
cmds.extend(self.del_attribs(h))
for w in want:
if w['name'] not in existing_interfaces:
# This is an object that was excluded from the 'have' list
# because all of its params are currently set to default states
# -OR- it's a new object that does not exist on the device yet.
cmds.extend(self.add_commands(w))
return cmds
def _state_merged(self, w, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
return self.set_commands(w, have)
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 w in want:
obj_in_have = search_obj_in_list(w['name'], have, 'name')
commands.extend(self.del_attribs(obj_in_have))
else:
if not have:
return commands
for h in have:
commands.extend(self.del_attribs(h))
return commands
def default_enabled(self, want=None, have=None, action=''):
# 'enabled' default state depends on the interface type and L2 state.
# Note that the current default could change when changing L2/L3 modes.
if want is None:
want = {}
if have is None:
have = {}
name = have.get('name')
if name is None:
return None
sysdefs = self.intf_defs['sysdefs']
sysdef_mode = sysdefs['mode']
# Get the default enabled state for this interface. This was collected
# during Facts gathering.
intf_def_enabled = self.intf_defs.get(name)
have_mode = have.get('mode', sysdef_mode)
if action == 'delete' and not want:
want_mode = sysdef_mode
else:
want_mode = want.get('mode', have_mode)
if (want_mode and have_mode) is None or (want_mode != have_mode) or intf_def_enabled is None:
# L2-L3 is changing or this is a new virtual intf. Get new default.
intf_def_enabled = default_intf_enabled(name=name, sysdefs=sysdefs, mode=want_mode)
return intf_def_enabled
def del_attribs(self, obj):
commands = []
if not obj or len(obj.keys()) == 1:
return commands
# mode/switchport changes should occur before other changes
sysdef_mode = self.intf_defs['sysdefs']['mode']
if 'mode' in obj and obj['mode'] != sysdef_mode:
no_cmd = 'no ' if sysdef_mode == 'layer3' else ''
commands.append(no_cmd + 'switchport')
if 'description' in obj:
commands.append('no description')
if 'speed' in obj:
commands.append('no speed')
if 'duplex' in obj:
commands.append('no duplex')
if 'enabled' in obj:
sysdef_enabled = self.default_enabled(have=obj, action='delete')
if obj['enabled'] is False and sysdef_enabled is True:
commands.append('no shutdown')
elif obj['enabled'] is True and sysdef_enabled is False:
commands.append('shutdown')
if 'mtu' in obj:
commands.append('no mtu')
if 'ip_forward' in obj and obj['ip_forward'] is True:
commands.append('no ip forward')
if 'fabric_forwarding_anycast_gateway' in obj and obj['fabric_forwarding_anycast_gateway'] is True:
commands.append('no fabric forwarding mode anycast-gateway')
if commands:
commands.insert(0, 'interface ' + obj['name'])
return commands
def diff_of_dicts(self, w, obj):
diff = set(w.items()) - set(obj.items())
diff = dict(diff)
if diff and w['name'] == obj['name']:
diff.update({'name': w['name']})
return diff
def add_commands(self, d, obj_in_have=None):
commands = []
if not d:
return commands
if obj_in_have is None:
obj_in_have = {}
# mode/switchport changes should occur before other changes
if 'mode' in d:
sysdef_mode = self.intf_defs['sysdefs']['mode']
have_mode = obj_in_have.get('mode', sysdef_mode)
want_mode = d['mode']
if have_mode == 'layer2':
if want_mode == 'layer3':
commands.append('no switchport')
elif want_mode == 'layer2':
commands.append('switchport')
if 'description' in d:
commands.append('description ' + d['description'])
if 'speed' in d:
commands.append('speed ' + str(d['speed']))
if 'duplex' in d:
commands.append('duplex ' + d['duplex'])
if 'enabled' in d:
have_enabled = obj_in_have.get('enabled', self.default_enabled(d, obj_in_have))
if d['enabled'] is False and have_enabled is True:
commands.append('shutdown')
elif d['enabled'] is True and have_enabled is False:
commands.append('no shutdown')
if 'mtu' in d:
commands.append('mtu ' + str(d['mtu']))
if 'ip_forward' in d:
if d['ip_forward'] is True:
commands.append('ip forward')
else:
commands.append('no ip forward')
if 'fabric_forwarding_anycast_gateway' in d:
if d['fabric_forwarding_anycast_gateway'] is True:
commands.append('fabric forwarding mode anycast-gateway')
else:
commands.append('no fabric forwarding mode anycast-gateway')
if commands or not obj_in_have:
commands.insert(0, 'interface' + ' ' + d['name'])
return commands
def set_commands(self, w, have):
commands = []
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if not obj_in_have:
commands = self.add_commands(w)
else:
diff = self.diff_of_dicts(w, obj_in_have)
commands = self.add_commands(diff, obj_in_have)
return commands

@ -1,301 +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 nxos_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 dict_diff, to_list, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, normalize_interface, search_obj_in_list, vlan_range_to_list
class L2_interfaces(ConfigBase):
"""
The nxos_l2_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l2_interfaces',
]
exclude_params = [
'vlan',
'allowed_vlans',
'native_vlans',
]
def __init__(self, module):
super(L2_interfaces, self).__init__(module)
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
"""
config = self._module.params.get('config')
want = []
if config:
for w in config:
w.update({'name': normalize_interface(w['name'])})
self.expand_trunk_allowed_vlans(w)
want.append(remove_empties(w))
have = existing_l2_interfaces_facts
for h in have:
self.expand_trunk_allowed_vlans(h)
resp = self.set_state(want, have)
return to_list(resp)
def expand_trunk_allowed_vlans(self, d):
if not d:
return None
if 'trunk' in d and d['trunk']:
if 'allowed_vlans' in d['trunk']:
allowed_vlans = vlan_range_to_list(d['trunk']['allowed_vlans'])
vlans_list = [str(l) for l in sorted(allowed_vlans)]
d['trunk']['allowed_vlans'] = ",".join(vlans_list)
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 ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
commands = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(flatten_dict(w), have))
elif state == 'replaced':
commands.extend(self._state_replaced(flatten_dict(w), have))
return commands
def _state_replaced(self, w, 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 = []
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
if obj_in_have:
diff = dict_diff(w, obj_in_have)
else:
diff = w
merged_commands = self.set_commands(w, have, True)
if 'name' not in diff:
diff['name'] = w['name']
dkeys = diff.keys()
for k in w.copy():
if k in self.exclude_params and k in dkeys:
del diff[k]
replaced_commands = self.del_attribs(diff)
if merged_commands:
cmds = set(replaced_commands).intersection(set(merged_commands))
for cmd in cmds:
merged_commands.remove(cmd)
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 h in have:
h = flatten_dict(h)
obj_in_want = flatten_dict(search_obj_in_list(h['name'], want, 'name'))
if h == obj_in_want:
continue
for w in want:
w = flatten_dict(w)
if h['name'] == w['name']:
wkeys = w.keys()
hkeys = h.keys()
for k in wkeys:
if k in self.exclude_params and k in hkeys:
del h[k]
commands.extend(self.del_attribs(h))
for w in want:
commands.extend(self.set_commands(flatten_dict(w), have, True))
return commands
def _state_merged(self, w, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
return self.set_commands(w, have)
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 w in want:
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
commands.extend(self.del_attribs(obj_in_have))
else:
if not have:
return commands
for h in have:
commands.extend(self.del_attribs(flatten_dict(h)))
return commands
def del_attribs(self, obj):
commands = []
if not obj or len(obj.keys()) == 1:
return commands
cmd = 'no switchport '
if 'vlan' in obj:
commands.append(cmd + 'access vlan')
if 'mode' in obj:
commands.append(cmd + 'mode')
if 'allowed_vlans' in obj:
commands.append(cmd + 'trunk allowed vlan')
if 'native_vlan' in obj:
commands.append(cmd + 'trunk native vlan')
if commands:
commands.insert(0, 'interface ' + obj['name'])
return commands
def diff_of_dicts(self, w, obj):
diff = set(w.items()) - set(obj.items())
diff = dict(diff)
if diff and w['name'] == obj['name']:
diff.update({'name': w['name']})
return diff
def add_commands(self, d, vlan_exists=False):
commands = []
if not d:
return commands
cmd = 'switchport '
if 'mode' in d:
commands.append(cmd + 'mode {0}'.format(d['mode']))
if 'vlan' in d:
commands.append(cmd + 'access vlan ' + str(d['vlan']))
if 'allowed_vlans' in d:
if vlan_exists:
commands.append(cmd + 'trunk allowed vlan add ' + str(d['allowed_vlans']))
else:
commands.append(cmd + 'trunk allowed vlan ' + str(d['allowed_vlans']))
if 'native_vlan' in d:
commands.append(cmd + 'trunk native vlan ' + str(d['native_vlan']))
if commands:
commands.insert(0, 'interface ' + d['name'])
return commands
def set_commands(self, w, have, replace=False):
commands = []
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
if not obj_in_have:
commands = self.add_commands(w)
else:
diff = self.diff_of_dicts(w, obj_in_have)
if diff and not replace:
if 'mode' in diff.keys() and diff['mode']:
commands = self.add_commands(diff)
if "allowed_vlans" in diff.keys() and diff["allowed_vlans"]:
vlan_tobe_added = diff["allowed_vlans"].split(',')
vlan_list = vlan_tobe_added[:]
if obj_in_have.get("allowed_vlans"):
have_vlans = obj_in_have["allowed_vlans"].split(',')
else:
have_vlans = []
for w_vlans in vlan_list:
if w_vlans in have_vlans:
vlan_tobe_added.pop(vlan_tobe_added.index(w_vlans))
if vlan_tobe_added:
diff.update({"allowed_vlans": ','.join(vlan_tobe_added)})
if have_vlans:
commands = self.add_commands(diff, True)
else:
commands = self.add_commands(diff)
return commands
commands = self.add_commands(diff)
return commands

@ -1,488 +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 nxos_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
import re
from copy import deepcopy
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import normalize_interface, search_obj_in_list
from ansible.module_utils.network.nxos.utils.utils import remove_rsvd_interfaces, get_interface_type
class L3_interfaces(ConfigBase):
"""
The nxos_l3_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l3_interfaces',
]
exclude_params = [
]
def __init__(self, module):
super(L3_interfaces, self).__init__(module)
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 []
self.platform = self.get_platform_type()
return remove_rsvd_interfaces(l3_interfaces_facts)
def get_platform_type(self):
default, _warnings = Facts(self._module).get_facts(legacy_facts_type=['default'])
return default.get('ansible_net_platform', '')
def edit_config(self, commands):
return self._connection.edit_config(commands)
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.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
"""
config = self._module.params.get('config')
want = []
if config:
for w in config:
w.update({'name': normalize_interface(w['name'])})
if get_interface_type(w['name']) == 'management':
self._module.fail_json(msg="The 'management' interface is not allowed to be managed by this module")
want.append(remove_empties(w))
have = deepcopy(existing_l3_interfaces_facts)
self.init_check_existing(have)
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 ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
commands = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(w, have))
elif state == 'replaced':
commands.extend(self._state_replaced(w, have))
return commands
def _state_replaced(self, want, have):
""" The command generator when state is replaced
Scope is limited to interface objects defined in the playbook.
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
cmds = []
name = want['name']
obj_in_have = search_obj_in_list(want['name'], have, 'name')
have_v4 = obj_in_have.pop('ipv4', []) if obj_in_have else []
have_v6 = obj_in_have.pop('ipv6', []) if obj_in_have else []
# Process lists of dicts separately
v4_cmds = self._v4_cmds(want.pop('ipv4', []), have_v4, state='replaced')
v6_cmds = self._v6_cmds(want.pop('ipv6', []), have_v6, state='replaced')
# Process remaining attrs
if obj_in_have:
# Find 'want' changes first
diff = self.diff_of_dicts(want, obj_in_have)
rmv = {'name': name}
haves_not_in_want = set(obj_in_have.keys()) - set(want.keys()) - set(diff.keys())
for i in haves_not_in_want:
rmv[i] = obj_in_have[i]
cmds.extend(self.generate_delete_commands(rmv))
else:
diff = want
cmds.extend(self.add_commands(diff, name=name))
cmds.extend(v4_cmds)
cmds.extend(v6_cmds)
self.cmd_order_fixup(cmds, name)
return cmds
def _state_overridden(self, want, have):
""" The command generator when state is overridden
Scope includes all interface objects on the device.
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
# overridden behavior is the same as replaced except for scope.
cmds = []
existing_vlans = []
for i in have:
obj_in_want = search_obj_in_list(i['name'], want, 'name')
if obj_in_want:
if i != obj_in_want:
v4_cmds = self._v4_cmds(obj_in_want.pop('ipv4', []), i.pop('ipv4', []), state='overridden')
replaced_cmds = self._state_replaced(obj_in_want, [i])
replaced_cmds.extend(v4_cmds)
self.cmd_order_fixup(replaced_cmds, obj_in_want['name'])
cmds.extend(replaced_cmds)
else:
deleted_cmds = self.generate_delete_commands(i)
self.cmd_order_fixup(deleted_cmds, i['name'])
cmds.extend(deleted_cmds)
for i in want:
if [item for item in have if i['name'] == item['name']]:
continue
cmds.extend(self.add_commands(i, name=i['name']))
return cmds
def _state_merged(self, w, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
return self.set_commands(w, have)
def _v4_cmds(self, want, have, state=None):
"""Helper method for processing ipv4 changes.
This is needed to handle primary/secondary address changes, which require a specific sequence when changing.
"""
# The ip address cli does not allow removing primary addresses while
# secondaries are present, but it does allow changing a primary to a
# new address as long as the address is not a current secondary.
# Be aware of scenarios where a secondary is taking over
# the role of the primary, which must be changed in sequence.
# In general, primaries/secondaries should change in this order:
# Step 1. Remove secondaries that are being changed or removed
# Step 2. Change the primary if needed
# Step 3. Merge secondaries
# Normalize inputs (add tag key if not present)
for i in want:
i['tag'] = i.get('tag')
for i in have:
i['tag'] = i.get('tag')
merged = True if state == 'merged' else False
replaced = True if state == 'replaced' else False
overridden = True if state == 'overridden' else False
# Create secondary and primary wants/haves
sec_w = [i for i in want if i.get('secondary')]
sec_h = [i for i in have if i.get('secondary')]
pri_w = [i for i in want if not i.get('secondary')]
pri_h = [i for i in have if not i.get('secondary')]
pri_w = pri_w[0] if pri_w else {}
pri_h = pri_h[0] if pri_h else {}
cmds = []
# Remove all addrs when no primary is specified in want (pri_w)
if pri_h and not pri_w and (replaced or overridden):
cmds.append('no ip address')
return cmds
# 1. Determine which secondaries are changing and remove them. Need a have/want
# diff instead of want/have because a have sec addr may be changing to a pri.
sec_to_rmv = []
sec_diff = self.diff_list_of_dicts(sec_h, sec_w)
for i in sec_diff:
if overridden or [w for w in sec_w if w['address'] == i['address']]:
sec_to_rmv.append(i['address'])
# Check if new primary is currently a secondary
if pri_w and [h for h in sec_h if h['address'] == pri_w['address']]:
if not overridden:
sec_to_rmv.append(pri_w['address'])
# Remove the changing secondaries
cmds.extend(['no ip address %s secondary' % i for i in sec_to_rmv])
# 2. change primary
if pri_w:
diff = dict(set(pri_w.items()) - set(pri_h.items()))
if diff:
cmd = 'ip address %s' % diff['address']
tag = diff.get('tag')
cmd += ' tag %s' % tag if tag else ''
cmds.append(cmd)
# 3. process remaining secondaries last
sec_w_to_chg = self.diff_list_of_dicts(sec_w, sec_h)
for i in sec_w_to_chg:
cmd = 'ip address %s secondary' % i['address']
cmd += ' tag %s' % i['tag'] if i['tag'] else ''
cmds.append(cmd)
return cmds
def _v6_cmds(self, want, have, state=''):
"""Helper method for processing ipv6 changes.
This is needed to avoid unnecessary churn on the device when removing or changing multiple addresses.
"""
# Normalize inputs (add tag key if not present)
for i in want:
i['tag'] = i.get('tag')
for i in have:
i['tag'] = i.get('tag')
cmds = []
# items to remove (items in 'have' only)
if state == 'replaced':
for i in self.diff_list_of_dicts(have, want):
want_addr = [w for w in want if w['address'] == i['address']]
if not want_addr:
cmds.append('no ipv6 address %s' % i['address'])
elif i['tag'] and not want_addr[0]['tag']:
# Must remove entire cli when removing tag
cmds.append('no ipv6 address %s' % i['address'])
# items to merge/add
for i in self.diff_list_of_dicts(want, have):
addr = i['address']
tag = i['tag']
if not tag and state == 'merged':
# When want is IP-no-tag and have is IP+tag it will show up in diff,
# but for merged nothing has changed, so ignore it for idempotence.
have_addr = [h for h in have if h['address'] == addr]
if have_addr and have_addr[0].get('tag'):
continue
cmd = 'ipv6 address %s' % i['address']
cmd += ' tag %s' % tag if tag else ''
cmds.append(cmd)
return cmds
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 w in want:
obj_in_have = search_obj_in_list(w['name'], have, 'name')
commands.extend(self.del_all_attribs(obj_in_have))
else:
if not have:
return commands
for h in have:
commands.extend(self.del_all_attribs(h))
return commands
def del_all_attribs(self, obj):
commands = []
if not obj or len(obj.keys()) == 1:
return commands
commands = self.generate_delete_commands(obj)
self.cmd_order_fixup(commands, obj['name'])
return commands
def generate_delete_commands(self, obj):
"""Generate CLI commands to remove non-default settings.
obj: dict of attrs to remove
"""
commands = []
name = obj.get('name')
if 'dot1q' in obj:
commands.append('no encapsulation dot1q')
if 'redirects' in obj:
if not self.check_existing(name, 'has_secondary') or re.match('N[3567]', self.platform):
# device auto-enables redirects when secondaries are removed;
# auto-enable may fail on legacy platforms so always do explicit enable
commands.append('ip redirects')
if 'unreachables' in obj:
commands.append('no ip unreachables')
if 'ipv4' in obj:
commands.append('no ip address')
if 'ipv6' in obj:
commands.append('no ipv6 address')
return commands
def init_check_existing(self, have):
"""Creates a class var dict for easier access to existing states
"""
self.existing_facts = dict()
have_copy = deepcopy(have)
for intf in have_copy:
name = intf['name']
self.existing_facts[name] = intf
# Check for presence of secondaries; used for ip redirects logic
if [i for i in intf.get('ipv4', []) if i.get('secondary')]:
self.existing_facts[name]['has_secondary'] = True
def check_existing(self, name, query):
"""Helper method to lookup existing states on an interface.
This is needed for attribute changes that have additional dependencies;
e.g. 'ip redirects' may auto-enable when all secondary ip addrs are removed.
"""
if name:
have = self.existing_facts.get(name, {})
if 'has_secondary' in query:
return have.get('has_secondary', False)
if 'redirects' in query:
return have.get('redirects', True)
if 'unreachables' in query:
return have.get('unreachables', False)
return None
def diff_of_dicts(self, w, obj):
diff = set(w.items()) - set(obj.items())
diff = dict(diff)
if diff and w['name'] == obj['name']:
diff.update({'name': w['name']})
return diff
def diff_list_of_dicts(self, w, h):
diff = []
set_w = set(tuple(sorted(d.items())) for d in w) if w else set()
set_h = set(tuple(sorted(d.items())) for d in h) if h else set()
difference = set_w.difference(set_h)
for element in difference:
diff.append(dict((x, y) for x, y in element))
return diff
def add_commands(self, diff, name=''):
commands = []
if not diff:
return commands
if 'dot1q' in diff:
commands.append('encapsulation dot1q ' + str(diff['dot1q']))
if 'redirects' in diff:
# Note: device will auto-disable redirects when secondaries are present
if diff['redirects'] != self.check_existing(name, 'redirects'):
no_cmd = 'no ' if diff['redirects'] is False else ''
commands.append(no_cmd + 'ip redirects')
self.cmd_order_fixup(commands, name)
if 'unreachables' in diff:
if diff['unreachables'] != self.check_existing(name, 'unreachables'):
no_cmd = 'no ' if diff['unreachables'] is False else ''
commands.append(no_cmd + 'ip unreachables')
if 'ipv4' in diff:
commands.extend(self.generate_afi_commands(diff['ipv4']))
if 'ipv6' in diff:
commands.extend(self.generate_afi_commands(diff['ipv6']))
self.cmd_order_fixup(commands, name)
return commands
def generate_afi_commands(self, diff):
cmds = []
for i in diff:
cmd = 'ipv6 address ' if re.search('::', i['address']) else 'ip address '
cmd += i['address']
if i.get('secondary'):
cmd += ' secondary'
if i.get('tag'):
cmd += ' tag ' + str(i['tag'])
cmds.append(cmd)
return cmds
def set_commands(self, w, have):
commands = []
name = w['name']
obj_in_have = search_obj_in_list(name, have, 'name')
if not obj_in_have:
commands = self.add_commands(w, name=name)
else:
# lists of dicts must be processed separately from non-list attrs
v4_cmds = self._v4_cmds(w.pop('ipv4', []), obj_in_have.pop('ipv4', []), state='merged')
v6_cmds = self._v6_cmds(w.pop('ipv6', []), obj_in_have.pop('ipv6', []), state='merged')
# diff remaining attrs
diff = self.diff_of_dicts(w, obj_in_have)
commands = self.add_commands(diff, name=name)
commands.extend(v4_cmds)
commands.extend(v6_cmds)
self.cmd_order_fixup(commands, name)
return commands
def cmd_order_fixup(self, cmds, name):
"""Inserts 'interface <name>' config at the beginning of populated command list; reorders dependent commands that must process after others.
"""
if cmds:
if name and not [item for item in cmds if item.startswith('interface')]:
cmds.insert(0, 'interface ' + name)
redirects = [item for item in cmds if re.match('(no )*ip redirects', item)]
if redirects:
# redirects should occur after ipv4 commands, just move to end of list
redirects = redirects.pop()
cmds.remove(redirects)
cmds.append(redirects)

@ -1,203 +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 nxos_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 dict_diff, to_list, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
class Lacp(ConfigBase):
"""
The nxos_lacp class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lacp',
]
exclude_params = [
'priority',
'mac',
]
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 = remove_empties(self._module.params['config'])
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']
commands = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
elif state == 'merged':
commands.extend(self._state_merged(want, have))
elif state == 'replaced':
commands.extend(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 = []
diff = dict_diff(want, have)
wkeys = want.keys()
dkeys = diff.keys()
for k in wkeys:
if k in self.exclude_params and k in dkeys:
del diff[k]
deleted_commands = self.del_all(diff)
merged_commands = self._state_merged(want, have)
commands.extend(deleted_commands)
if merged_commands:
commands.extend(merged_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
"""
return self.set_commands(want, have)
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 have:
return commands
commands.extend(self.del_all(have))
return commands
def get_diff(self, comparable, base):
diff = {}
if not base:
diff = comparable
else:
diff = dict_diff(base, comparable)
return diff
def del_all(self, diff):
commands = []
base = 'no lacp system-'
diff = diff.get('system')
if diff:
if 'priority' in diff:
commands.append(base + 'priority')
if 'mac' in diff:
commands.append(base + 'mac')
return commands
def add_commands(self, diff):
commands = []
base = 'lacp system-'
diff = diff.get('system')
if diff and 'priority' in diff:
cmd = base + 'priority' + ' ' + str(diff['priority'])
commands.append(cmd)
if diff and 'mac' in diff:
cmd = ''
if 'address' in diff['mac']:
cmd += base + 'mac' + ' ' + diff['mac']['address']
if 'role' in diff['mac']:
cmd += ' ' + 'role' + ' ' + diff['mac']['role']
if cmd:
commands.append(cmd)
return commands
def set_commands(self, want, have):
if not want:
return []
diff = self.get_diff(want, have)
return self.add_commands(diff)

@ -1,284 +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 nxos_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, dict_diff, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list, get_interface_type, normalize_interface
class Lacp_interfaces(ConfigBase):
"""
The nxos_lacp_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lacp_interfaces',
]
exclude_params = [
'port_priority',
'rate',
'min',
'max',
]
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
"""
config = self._module.params.get('config')
want = []
if config:
for w in config:
if get_interface_type(w['name']) not in ('portchannel', 'ethernet'):
self._module.fail_json(msg='This module works with either portchannel or ethernet')
w.update({'name': normalize_interface(w['name'])})
want.append(remove_empties(w))
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
"""
state = self._module.params['state']
if state in ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
commands = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(flatten_dict(w), have))
elif state == 'replaced':
commands.extend(self._state_replaced(flatten_dict(w), have))
return commands
def _state_replaced(self, w, 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 = []
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
diff = dict_diff(w, obj_in_have)
merged_commands = self.set_commands(w, have)
if 'name' not in diff:
diff['name'] = w['name']
wkeys = w.keys()
dkeys = diff.keys()
for k in wkeys:
if k in self.exclude_params and k in dkeys:
del diff[k]
replaced_commands = self.del_attribs(diff)
if merged_commands:
cmds = set(replaced_commands).intersection(set(merged_commands))
for cmd in cmds:
merged_commands.remove(cmd)
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 h in have:
h = flatten_dict(h)
obj_in_want = flatten_dict(search_obj_in_list(h['name'], want, 'name'))
if h == obj_in_want:
continue
for w in want:
w = flatten_dict(w)
if h['name'] == w['name']:
wkeys = w.keys()
hkeys = h.keys()
for k in wkeys:
if k in self.exclude_params and k in hkeys:
del h[k]
commands.extend(self.del_attribs(h))
for w in want:
commands.extend(self.set_commands(flatten_dict(w), have))
return commands
def _state_merged(self, w, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
return self.set_commands(w, have)
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 w in want:
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
commands.extend(self.del_attribs(obj_in_have))
else:
if not have:
return commands
for h in have:
commands.extend(self.del_attribs(flatten_dict(h)))
return commands
def del_attribs(self, obj):
commands = []
if not obj or len(obj.keys()) == 1:
return commands
commands.append('interface ' + obj['name'])
if 'graceful' in obj:
commands.append('lacp graceful-convergence')
if 'vpc' in obj:
commands.append('no lacp vpn-convergence')
if 'suspend_individual' in obj:
commands.append('lacp suspend_individual')
if 'mode' in obj:
commands.append('no lacp mode ' + obj['mode'])
if 'max' in obj:
commands.append('no lacp max-bundle')
if 'min' in obj:
commands.append('no lacp min-links')
if 'port_priority' in obj:
commands.append('no lacp port-priority')
if 'rate' in obj:
commands.append('no lacp rate')
return commands
def diff_of_dicts(self, w, obj):
diff = set(w.items()) - set(obj.items())
diff = dict(diff)
if diff and w['name'] == obj['name']:
diff.update({'name': w['name']})
return diff
def add_commands(self, d):
commands = []
if not d:
return commands
commands.append('interface' + ' ' + d['name'])
if 'port_priority' in d:
commands.append('lacp port-priority ' + str(d['port_priority']))
if 'rate' in d:
commands.append('lacp rate ' + str(d['rate']))
if 'min' in d:
commands.append('lacp min-links ' + str(d['min']))
if 'max' in d:
commands.append('lacp max-bundle ' + str(d['max']))
if 'mode' in d:
commands.append('lacp mode ' + d['mode'])
if 'suspend_individual' in d:
if d['suspend_individual'] is True:
commands.append('lacp suspend-individual')
else:
commands.append('no lacp suspend-individual')
if 'graceful' in d:
if d['graceful'] is True:
commands.append('lacp graceful-convergence')
else:
commands.append('no lacp graceful-convergence')
if 'vpc' in d:
if d['vpc'] is True:
commands.append('lacp vpc-convergence')
else:
commands.append('no lacp vpc-convergence')
return commands
def set_commands(self, w, have):
commands = []
obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name'))
if not obj_in_have:
commands = self.add_commands(w)
else:
diff = self.diff_of_dicts(w, obj_in_have)
commands = self.add_commands(diff)
return commands

@ -1,273 +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 junos_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 ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, remove_empties, dict_diff, search_obj_in_list
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import normalize_interface
class Lag_interfaces(ConfigBase):
"""
The nxos_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}
commands = list()
warnings = 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:
resp = self._connection.edit_config(commands)
if 'response' in resp:
for item in resp['response']:
if item:
err_str = item
if err_str.lower().startswith('cannot add'):
self._module.fail_json(msg=err_str)
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.get('config')
if want:
for w in want:
w.update(remove_empties(w))
if 'members' in w and w['members']:
for item in w['members']:
item.update({'member': normalize_interface(item['member'])})
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 = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(w, have))
if state == 'replaced':
commands.extend(self._state_replaced(w, have))
return commands
def _state_replaced(self, w, 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 = []
merged_commands = self.set_commands(w, have)
replaced_commands = self.del_intf_commands(w, have)
if merged_commands:
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 h in have:
obj_in_want = search_obj_in_list(h['name'], want, 'name')
if obj_in_want:
diff = self.diff_list_of_dicts(h['members'], obj_in_want['members'])
if not diff:
continue
commands.extend(self.del_all_commands(h))
for w in want:
commands.extend(self.set_commands(w, have))
return commands
def _state_merged(self, w, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
return self.set_commands(w, have)
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 w in want:
obj_in_have = search_obj_in_list(w['name'], have, 'name')
commands.extend(self.del_all_commands(obj_in_have))
else:
if not have:
return commands
for h in have:
commands.extend(self.del_all_commands(h))
return commands
def diff_list_of_dicts(self, want, have):
if not want:
want = []
if not have:
have = []
diff = []
for w_item in want:
h_item = search_obj_in_list(w_item['member'], have, key='member') or {}
delta = dict_diff(h_item, w_item)
if delta:
if h_item:
if 'mode' in delta.keys() and delta['mode'] == 'on' and 'mode' not in h_item.keys():
# mode = on will not be displayed in running-config
continue
if 'member' not in delta.keys():
delta['member'] = w_item['member']
diff.append(delta)
return diff
def intersect_list_of_dicts(self, w, h):
intersect = []
wmem = []
hmem = []
for d in w:
wmem.append({'member': d['member']})
for d in h:
hmem.append({'member': d['member']})
set_w = set(tuple(sorted(d.items())) for d in wmem)
set_h = set(tuple(sorted(d.items())) for d in hmem)
intersection = set_w.intersection(set_h)
for element in intersection:
intersect.append(dict((x, y) for x, y in element))
return intersect
def add_commands(self, diff, name):
commands = []
name = name.strip('port-channel')
for d in diff:
commands.append('interface' + ' ' + d['member'])
cmd = ''
group_cmd = 'channel-group {0}'.format(name)
if 'force' in d:
cmd = group_cmd + ' force ' + d['force']
if 'mode' in d:
if cmd:
cmd = cmd + ' mode ' + d['mode']
else:
cmd = group_cmd + ' mode ' + d['mode']
if not cmd:
cmd = group_cmd
commands.append(cmd)
return commands
def set_commands(self, w, have):
commands = []
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if not obj_in_have:
commands = self.add_commands(w['members'], w['name'])
else:
diff = self.diff_list_of_dicts(w['members'], obj_in_have['members'])
commands = self.add_commands(diff, w['name'])
return commands
def del_all_commands(self, obj_in_have):
commands = []
if not obj_in_have:
return commands
for m in obj_in_have['members']:
commands.append('interface' + ' ' + m['member'])
commands.append('no channel-group')
return commands
def del_intf_commands(self, w, have):
commands = []
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if obj_in_have:
lst_to_del = self.intersect_list_of_dicts(w['members'], obj_in_have['members'])
if lst_to_del:
for item in lst_to_del:
commands.append('interface' + ' ' + item['member'])
commands.append('no channel-group')
return commands

@ -1,240 +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 nxos_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 remove_empties, dict_diff
from ansible.module_utils.network.nxos.facts.facts import Facts
class Lldp_global(ConfigBase):
"""
The nxos_lldp_global 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_global_facts = facts['ansible_network_resources'].get(
'lldp_global')
if not lldp_global_facts:
return {}
return lldp_global_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_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'] = dict(existing_lldp_global_facts)
if result['changed']:
result['after'] = dict(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']
have = existing_lldp_global_facts
resp = self.set_state(remove_empties(want), have)
return 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 == 'deleted':
commands = self._state_deleted(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 = []
merge_dict = dict_diff(have, want)
# merge_dict will contain new and unique values in want
delete_dict = self.find_delete_params(have, want)
self._module.params['state'] = 'deleted'
commands.extend(self._state_deleted(delete_dict)) # delete
self._module.params['state'] = 'merged'
commands.extend(self.set_commands(merge_dict)) # merge
self._module.params['state'] = 'replaced'
return commands
def delete_nested_dict(self, have, want):
"""
Returns tlv_select nested dict that needs to be defaulted
"""
outer_dict = {}
for key, val in have.items():
inner_dict = {}
if not isinstance(val, dict):
if key not in want.keys():
inner_dict.update({key: val})
return inner_dict
else:
if key in want.keys():
outer_dict.update(
{key: self.delete_nested_dict(val, want[key])})
else:
outer_dict.update({key: val})
return outer_dict
def find_delete_params(self, have, want):
"""
Returns parameters that are present in have and not in want, that need to be defaulted
"""
delete_dict = {}
for key, val in have.items():
if key not in want.keys():
delete_dict.update({key: val})
else:
if key == 'tlv_select':
delete_dict.update({key: self.delete_nested_dict(
have['tlv_select'], want['tlv_select'])})
return delete_dict
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 = []
diff = dict_diff(have, want)
commands.extend(self.set_commands(diff))
return commands
def _state_deleted(self, 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 have:
for key, val in have.items():
if 'tlv_select' in key:
commands.extend(self.process_nested_dict(val))
else:
if key == 'port_id':
key = 'portid-subtype'
commands.append('no lldp ' + key + ' ' + str(val))
return commands
def set_commands(self, diff):
commands = []
for key, val in diff.items():
commands.extend(self.add_commands(key, val))
return commands
def add_commands(self, key, val):
command = []
if 'port_id' in key:
command.append('lldp portid-subtype ' + str(val))
elif 'tlv_select' in key:
command.extend(self.process_nested_dict(val))
else:
if val:
command.append('lldp ' + key + ' ' + str(val))
return command
def process_nested_dict(self, val):
nested_commands = []
for k, v in val.items():
if isinstance(v, dict):
for k1, v1 in v.items():
com1 = 'lldp tlv-select '
com2 = ''
if 'system' in k:
com2 = 'system-' + k1
elif 'management_address' in k:
com2 = 'management-address ' + k1
elif 'port' in k:
com2 = 'port-' + k1
com1 += com2
com1 = self.negate_command(com1, v1)
nested_commands.append(com1)
else:
com1 = 'lldp tlv-select '
if 'power_management' in k:
com1 += 'power-management'
else:
com1 += k
com1 = self.negate_command(com1, v)
nested_commands.append(com1)
return nested_commands
def negate_command(self, command, val):
# for merged, replaced vals need to be checked to add 'no'
if self._module.params['state'] == 'merged':
if not val:
command = 'no ' + command
return command

@ -1,304 +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 nxos_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, remove_empties, dict_diff
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list, get_interface_type, normalize_interface
class Lldp_interfaces(ConfigBase):
"""
The nxos_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, 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)
lldp_interfaces_facts = facts['ansible_network_resources'].get(
'lldp_interfaces')
if not lldp_interfaces_facts:
return []
return lldp_interfaces_facts
def edit_config(self, commands):
"""Wrapper method for `_connection.edit_config()`
This exists solely to allow the unit test framework to mock device connection calls.
"""
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
state = self._module.params['state']
action_states = ['merged', 'replaced', 'deleted', 'overridden']
if state == 'gathered':
result['gathered'] = self.get_lldp_interfaces_facts()
elif state == 'rendered':
result['rendered'] = self.set_config({})
elif state == 'parsed':
result['parsed'] = self.set_config({})
else:
existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
commands.extend(self.set_config(existing_lldp_interfaces_facts))
if commands and state in action_states:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['before'] = existing_lldp_interfaces_facts
result['commands'] = commands
result['commands'] = commands
changed_lldp_interfaces_facts = self.get_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
"""
config = self._module.params['config']
want = []
if config:
for w in config:
if get_interface_type(w['name']) not in ('management',
'ethernet'):
self._module.fail_json(
msg='This module works with either management or ethernet')
w.update({'name': normalize_interface(w['name'])})
want.append(remove_empties(w))
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
"""
commands = []
state = self._module.params['state']
if state == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'rendered':
commands = self._state_rendered(want)
elif state == 'parsed':
want = self._module.params['running_config']
commands = self._state_parsed(want)
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(flatten_dict(w), have))
elif state == 'replaced':
commands.extend(self._state_replaced(
flatten_dict(w), have))
return commands
def _state_parsed(self, want):
return self.get_lldp_interfaces_facts(want)
def _state_rendered(self, want):
commands = []
for w in want:
commands.extend(self.set_commands(w, {}))
return commands
def _state_gathered(self, have):
""" The command generator when state is gathered
:rtype: A list
:returns: the commands necessary to reproduce the current configuration
"""
commands = []
want = {}
commands.append(self.set_commands(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 = []
del_commands = []
delete_dict = {}
obj_in_have = flatten_dict(
search_obj_in_list(want['name'], have, 'name'))
for k1 in obj_in_have.keys():
if k1 not in want.keys():
delete_dict.update({k1: obj_in_have[k1]})
if delete_dict:
delete_dict.update({'name': want['name']})
del_commands = self.del_commands(delete_dict)
merged_commands = self.set_commands(want, have)
if merged_commands:
cmds = set(del_commands).intersection(set(merged_commands))
for cmd in cmds:
merged_commands.remove(cmd)
commands.extend(del_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 = []
want_intfs = [w['name'] for w in want]
for h in have:
h = flatten_dict(h)
delete_dict = {}
if h['name'] in want_intfs:
for w in want:
if w['name'] == h['name']:
delete_keys = list(set(h) - set(flatten_dict(w)))
for k in delete_keys:
delete_dict.update({k: h[k]})
delete_dict.update({'name': h['name']})
break
else:
delete_dict.update(h)
commands.extend(self.del_commands(delete_dict))
for w in want:
commands.extend(self.set_commands(flatten_dict(w), 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
"""
return self.set_commands(want, have)
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 w in want:
obj_in_have = flatten_dict(
search_obj_in_list(w['name'], have, 'name'))
commands.extend(self.del_commands(obj_in_have))
else:
if not have:
return commands
for h in have:
commands.extend(self.del_commands(flatten_dict(h)))
return commands
def set_commands(self, want, have):
commands = []
obj_in_have = flatten_dict(
search_obj_in_list(want['name'], have, 'name'))
if not obj_in_have:
commands = self.add_commands(flatten_dict(want))
else:
diff = dict_diff(obj_in_have, want)
if diff:
diff.update({'name': want['name']})
commands = self.add_commands(diff)
return commands
def add_commands(self, d):
commands = []
if not d:
return commands
commands.append('interface ' + d['name'])
if 'transmit' in d:
if (d['transmit']):
commands.append('lldp transmit')
else:
commands.append('no lldp transmit')
if 'receive' in d:
if (d['receive']):
commands.append('lldp receive')
else:
commands.append('no lldp receive')
if 'management_address' in d:
commands.append('lldp tlv-set management-address ' +
d['management_address'])
if 'vlan' in d:
commands.append('lldp tlv-set vlan ' + str(d['vlan']))
return commands
def del_commands(self, obj):
commands = []
if not obj or len(obj.keys()) == 1:
return commands
commands.append('interface ' + obj['name'])
if 'transmit' in obj:
commands.append('lldp transmit')
if 'receive' in obj:
commands.append('lldp receive')
if 'management_address' in obj:
commands.append('no lldp tlv-set management-address ' +
obj['management_address'])
if 'vlan' in obj:
commands.append('no lldp tlv-set vlan ' + str(obj['vlan']))
return commands

@ -1,503 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The nxos_telemetry 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.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.cmdref.telemetry.telemetry import TMS_GLOBAL, TMS_DESTGROUP, TMS_SENSORGROUP, TMS_SUBSCRIPTION
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import normalize_data, remove_duplicate_context
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import valiate_input, get_setval_path, massage_data
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import get_module_params_subsection, remove_duplicate_commands
from ansible.module_utils.network.nxos.utils.utils import normalize_interface
from ansible.module_utils.network.nxos.nxos import NxosCmdRef
class Telemetry(ConfigBase):
"""
The nxos_telemetry class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'telemetry',
]
def __init__(self, module):
super(Telemetry, self).__init__(module)
def get_telemetry_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)
telemetry_facts = facts['ansible_network_resources'].get('telemetry')
if not telemetry_facts:
return {}
return telemetry_facts
def edit_config(self, commands):
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
state = self._module.params['state']
if 'overridden' in state:
self._module.fail_json(msg='State <overridden> is invalid for this module.')
# When state is 'deleted', the module_params should not contain data
# under the 'config' key
if 'deleted' in state and self._module.params.get('config'):
self._module.fail_json(msg='Remove config key from playbook when state is <deleted>')
if self._module.params['config'] is None:
self._module.params['config'] = {}
# Normalize interface name.
int = self._module.params['config'].get('source_interface')
if int:
self._module.params['config']['source_interface'] = normalize_interface(int)
existing_telemetry_facts = self.get_telemetry_facts()
commands.extend(self.set_config(existing_telemetry_facts))
if commands:
if not self._module.check_mode:
self.edit_config(commands)
# TODO: edit_config is only available for network_cli. Once we
# add support for httpapi, we will need to switch to load_config
# or add support to httpapi for edit_config
#
# self._connection.load_config(commands)
result['changed'] = True
result['commands'] = commands
changed_telemetry_facts = self.get_telemetry_facts()
result['before'] = existing_telemetry_facts
if result['changed']:
result['after'] = changed_telemetry_facts
result['warnings'] = warnings
return result
def set_config(self, existing_tms_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
"""
config = self._module.params['config']
want = dict((k, v) for k, v in config.items() if v is not None)
have = existing_tms_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']
# The deleted case is very simple since we purge all telemetry config
# and does not require any processing using NxosCmdRef objects.
if state == 'deleted':
return self._state_deleted(want, have)
elif state == 'replaced':
if want == have:
return []
return self._state_replaced(want, have)
# Save off module params
ALL_MP = self._module.params['config']
cmd_ref = {}
cmd_ref['TMS_GLOBAL'] = {}
cmd_ref['TMS_DESTGROUP'] = {}
cmd_ref['TMS_SENSORGROUP'] = {}
cmd_ref['TMS_SUBSCRIPTION'] = {}
# Build Telemetry Global NxosCmdRef Object
cmd_ref['TMS_GLOBAL']['ref'] = []
self._module.params['config'] = get_module_params_subsection(ALL_MP, 'TMS_GLOBAL')
cmd_ref['TMS_GLOBAL']['ref'].append(NxosCmdRef(self._module, TMS_GLOBAL))
ref = cmd_ref['TMS_GLOBAL']['ref'][0]
ref.set_context()
ref.get_existing()
ref.get_playvals()
device_cache = ref.cache_existing
def build_cmdref_objects(td):
cmd_ref[td['type']]['ref'] = []
saved_ids = []
if want.get(td['name']):
for playvals in want[td['name']]:
valiate_input(playvals, td['name'], self._module)
if playvals['id'] in saved_ids:
continue
saved_ids.append(playvals['id'])
resource_key = td['cmd'].format(playvals['id'])
# Only build the NxosCmdRef object for the td['name'] module parameters.
self._module.params['config'] = get_module_params_subsection(ALL_MP, td['type'], playvals['id'])
cmd_ref[td['type']]['ref'].append(NxosCmdRef(self._module, td['obj']))
ref = cmd_ref[td['type']]['ref'][-1]
ref.set_context([resource_key])
if td['type'] == 'TMS_SENSORGROUP' and get_setval_path(self._module):
# Sensor group path setting can contain optional values.
# Call get_setval_path helper function to process any
# optional setval keys.
ref._ref['path']['setval'] = get_setval_path(self._module)
ref.get_existing(device_cache)
ref.get_playvals()
if td['type'] == 'TMS_DESTGROUP':
normalize_data(ref)
# Build Telemetry Destination Group NxosCmdRef Objects
td = {'name': 'destination_groups', 'type': 'TMS_DESTGROUP',
'obj': TMS_DESTGROUP, 'cmd': 'destination-group {0}'}
build_cmdref_objects(td)
# Build Telemetry Sensor Group NxosCmdRef Objects
td = {'name': 'sensor_groups', 'type': 'TMS_SENSORGROUP',
'obj': TMS_SENSORGROUP, 'cmd': 'sensor-group {0}'}
build_cmdref_objects(td)
# Build Telemetry Subscription NxosCmdRef Objects
td = {'name': 'subscriptions', 'type': 'TMS_SUBSCRIPTION',
'obj': TMS_SUBSCRIPTION, 'cmd': 'subscription {0}'}
build_cmdref_objects(td)
if state == 'merged':
if want == have:
return []
commands = self._state_merged(cmd_ref)
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 = []
massaged_have = massage_data(have)
massaged_want = massage_data(want)
ref = {}
ref['tms_global'] = NxosCmdRef([], TMS_GLOBAL, ref_only=True)
ref['tms_destgroup'] = NxosCmdRef([], TMS_DESTGROUP, ref_only=True)
ref['tms_sensorgroup'] = NxosCmdRef([], TMS_SENSORGROUP, ref_only=True)
ref['tms_subscription'] = NxosCmdRef([], TMS_SUBSCRIPTION, ref_only=True)
# Order matters for state replaced.
# First remove all subscriptions, followed by sensor-groups and destination-groups.
# Second add all destination-groups, followed by sensor-groups and subscriptions
add = {'TMS_GLOBAL': [], 'TMS_DESTGROUP': [], 'TMS_SENSORGROUP': [], 'TMS_SUBSCRIPTION': []}
delete = {'TMS_DESTGROUP': [], 'TMS_SENSORGROUP': [], 'TMS_SUBSCRIPTION': []}
# Process Telemetry Global Want and Have Values
# Possible states:
# - want and have are (set) (equal: no action, not equal: replace with want)
# - want (set) have (not set) (add want)
# - want (not set) have (set) (delete have)
# - want (not set) have (not set) (no action)
# global_ctx = ref['tms_global']._ref['_template']['context']
# property_ctx = ref['tms_global']._ref['certificate'].get('context')
# setval = ref['tms_global']._ref['certificate']['setval']
#
all_global_properties = ['certificate', 'compression', 'source_interface', 'vrf']
dest_profile_properties = ['compression', 'source_interface', 'vrf']
dest_profile_remote_commands = []
for property in all_global_properties:
cmd = None
global_ctx = ref['tms_global']._ref['_template']['context']
property_ctx = ref['tms_global']._ref[property].get('context')
setval = ref['tms_global']._ref[property]['setval']
kind = ref['tms_global']._ref[property]['kind']
if want.get(property) is not None:
if have.get(property) is not None:
if want.get(property) != have.get(property):
if kind == 'dict':
cmd = [setval.format(**want.get(property))]
else:
cmd = [setval.format(want.get(property))]
elif have.get(property) is None:
if kind == 'dict':
cmd = [setval.format(**want.get(property))]
else:
cmd = [setval.format(want.get(property))]
elif want.get(property) is None:
if have.get(property) is not None:
if kind == 'dict':
cmd = ['no ' + setval.format(**have.get(property))]
else:
cmd = ['no ' + setval.format(have.get(property))]
if property in dest_profile_properties:
dest_profile_remote_commands.extend(cmd)
if cmd is not None:
ctx = global_ctx
if property_ctx is not None:
ctx.extend(property_ctx)
add['TMS_GLOBAL'].extend(ctx)
add['TMS_GLOBAL'].extend(cmd)
add['TMS_GLOBAL'] = remove_duplicate_commands(add['TMS_GLOBAL'])
# If all destination profile commands are being removed then just
# remove the config context instead.
if len(dest_profile_remote_commands) == 3:
for item in dest_profile_remote_commands:
add['TMS_GLOBAL'].remove(item)
add['TMS_GLOBAL'].remove('destination-profile')
add['TMS_GLOBAL'].extend(['no destination-profile'])
# Process Telemetry destination_group, sensor_group and subscription Want and Have Values
# Possible states:
# - want (not set) have (set) (delete have)
# - want and have are (set) (equal: no action, not equal: replace with want)
# - want (set) have (not set) (add want)
# - want (not set) have (not set) (no action)
tms_resources = ['TMS_DESTGROUP', 'TMS_SENSORGROUP', 'TMS_SUBSCRIPTION']
for resource in tms_resources:
if resource == 'TMS_DESTGROUP':
name = 'destination-group'
cmd_property = 'destination'
global_ctx = ref['tms_destgroup']._ref['_template']['context']
setval = ref['tms_destgroup']._ref['destination']['setval']
want_resources = massaged_want.get('destination_groups')
have_resources = massaged_have.get('destination_groups')
if resource == 'TMS_SENSORGROUP':
name = 'sensor-group'
global_ctx = ref['tms_sensorgroup']._ref['_template']['context']
setval = {}
setval['data_source'] = ref['tms_sensorgroup']._ref['data_source']['setval']
setval['path'] = ref['tms_sensorgroup']._ref['path']['setval']
want_resources = massaged_want.get('sensor_groups')
have_resources = massaged_have.get('sensor_groups')
if resource == 'TMS_SUBSCRIPTION':
name = 'subscription'
global_ctx = ref['tms_subscription']._ref['_template']['context']
setval = {}
setval['destination_group'] = ref['tms_subscription']._ref['destination_group']['setval']
setval['sensor_group'] = ref['tms_subscription']._ref['sensor_group']['setval']
want_resources = massaged_want.get('subscriptions')
have_resources = massaged_have.get('subscriptions')
if not want_resources and have_resources:
# want not and have not set so delete have
for key in have_resources.keys():
remove_context = ['{0} {1} {2}'.format('no', name, key)]
delete[resource].extend(global_ctx)
if remove_context[0] not in delete[resource]:
delete[resource].extend(remove_context)
else:
# want and have are set.
# process wants:
for want_key in want_resources.keys():
if want_key not in have_resources.keys():
# Want resource key not in have resource key so add it
property_ctx = ['{0} {1}'.format(name, want_key)]
for item in want_resources[want_key]:
if resource == 'TMS_DESTGROUP':
cmd = [setval.format(**item[cmd_property])]
add[resource].extend(global_ctx)
if property_ctx[0] not in add[resource]:
add[resource].extend(property_ctx)
add[resource].extend(cmd)
if resource == 'TMS_SENSORGROUP':
cmd = {}
if item.get('data_source'):
cmd['data_source'] = [setval['data_source'].format(item['data_source'])]
if item.get('path'):
setval['path'] = get_setval_path(item.get('path'))
cmd['path'] = [setval['path'].format(**item['path'])]
add[resource].extend(global_ctx)
if property_ctx[0] not in add[resource]:
add[resource].extend(property_ctx)
if cmd.get('data_source'):
add[resource].extend(cmd['data_source'])
if cmd.get('path'):
add[resource].extend(cmd['path'])
if resource == 'TMS_SUBSCRIPTION':
cmd = {}
if item.get('destination_group'):
cmd['destination_group'] = [setval['destination_group'].format(item['destination_group'])]
if item.get('sensor_group'):
cmd['sensor_group'] = [setval['sensor_group'].format(**item['sensor_group'])]
add[resource].extend(global_ctx)
if property_ctx[0] not in add[resource]:
add[resource].extend(property_ctx)
if cmd.get('destination_group'):
add[resource].extend(cmd['destination_group'])
if cmd.get('sensor_group'):
add[resource].extend(cmd['sensor_group'])
elif want_key in have_resources.keys():
# Want resource key exists in have resource keys but we need to
# inspect the individual items under the resource key
# for differences
for item in want_resources[want_key]:
if item not in have_resources[want_key]:
if item is None:
continue
# item wanted but does not exist so add it
property_ctx = ['{0} {1}'.format(name, want_key)]
if resource == 'TMS_DESTGROUP':
cmd = [setval.format(**item[cmd_property])]
add[resource].extend(global_ctx)
if property_ctx[0] not in add[resource]:
add[resource].extend(property_ctx)
add[resource].extend(cmd)
if resource == 'TMS_SENSORGROUP':
cmd = {}
if item.get('data_source'):
cmd['data_source'] = [setval['data_source'].format(item['data_source'])]
if item.get('path'):
setval['path'] = get_setval_path(item.get('path'))
cmd['path'] = [setval['path'].format(**item['path'])]
add[resource].extend(global_ctx)
if property_ctx[0] not in add[resource]:
add[resource].extend(property_ctx)
if cmd.get('data_source'):
add[resource].extend(cmd['data_source'])
if cmd.get('path'):
add[resource].extend(cmd['path'])
if resource == 'TMS_SUBSCRIPTION':
cmd = {}
if item.get('destination_group'):
cmd['destination_group'] = [setval['destination_group'].format(item['destination_group'])]
if item.get('sensor_group'):
cmd['sensor_group'] = [setval['sensor_group'].format(**item['sensor_group'])]
add[resource].extend(global_ctx)
if property_ctx[0] not in add[resource]:
add[resource].extend(property_ctx)
if cmd.get('destination_group'):
add[resource].extend(cmd['destination_group'])
if cmd.get('sensor_group'):
add[resource].extend(cmd['sensor_group'])
# process haves:
for have_key in have_resources.keys():
if have_key not in want_resources.keys():
# Want resource key is not in have resource keys so remove it
cmd = ['no ' + '{0} {1}'.format(name, have_key)]
delete[resource].extend(global_ctx)
delete[resource].extend(cmd)
elif have_key in want_resources.keys():
# Have resource key exists in want resource keys but we need to
# inspect the individual items under the resource key
# for differences
for item in have_resources[have_key]:
if item not in want_resources[have_key]:
if item is None:
continue
# have item not wanted so remove it
property_ctx = ['{0} {1}'.format(name, have_key)]
if resource == 'TMS_DESTGROUP':
cmd = ['no ' + setval.format(**item[cmd_property])]
delete[resource].extend(global_ctx)
if property_ctx[0] not in delete[resource]:
delete[resource].extend(property_ctx)
delete[resource].extend(cmd)
if resource == 'TMS_SENSORGROUP':
cmd = {}
if item.get('data_source'):
cmd['data_source'] = ['no ' + setval['data_source'].format(item['data_source'])]
if item.get('path'):
setval['path'] = get_setval_path(item.get('path'))
cmd['path'] = ['no ' + setval['path'].format(**item['path'])]
delete[resource].extend(global_ctx)
if property_ctx[0] not in delete[resource]:
delete[resource].extend(property_ctx)
if cmd.get('data_source'):
delete[resource].extend(cmd['data_source'])
if cmd.get('path'):
delete[resource].extend(cmd['path'])
if resource == 'TMS_SUBSCRIPTION':
cmd = {}
if item.get('destination_group'):
cmd['destination_group'] = ['no ' + setval['destination_group'].format(item['destination_group'])]
if item.get('sensor_group'):
cmd['sensor_group'] = ['no ' + setval['sensor_group'].format(**item['sensor_group'])]
delete[resource].extend(global_ctx)
if property_ctx[0] not in delete[resource]:
delete[resource].extend(property_ctx)
if cmd.get('destination_group'):
delete[resource].extend(cmd['destination_group'])
if cmd.get('sensor_group'):
delete[resource].extend(cmd['sensor_group'])
add[resource] = remove_duplicate_context(add[resource])
delete[resource] = remove_duplicate_context(delete[resource])
commands.extend(delete['TMS_SUBSCRIPTION'])
commands.extend(delete['TMS_SENSORGROUP'])
commands.extend(delete['TMS_DESTGROUP'])
commands.extend(add['TMS_DESTGROUP'])
commands.extend(add['TMS_SENSORGROUP'])
commands.extend(add['TMS_SUBSCRIPTION'])
commands.extend(add['TMS_GLOBAL'])
commands = remove_duplicate_context(commands)
return commands
@staticmethod
def _state_merged(cmd_ref):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = cmd_ref['TMS_GLOBAL']['ref'][0].get_proposed()
if cmd_ref['TMS_DESTGROUP'].get('ref'):
for cr in cmd_ref['TMS_DESTGROUP']['ref']:
commands.extend(cr.get_proposed())
if cmd_ref['TMS_SENSORGROUP'].get('ref'):
for cr in cmd_ref['TMS_SENSORGROUP']['ref']:
commands.extend(cr.get_proposed())
if cmd_ref['TMS_SUBSCRIPTION'].get('ref'):
for cr in cmd_ref['TMS_SUBSCRIPTION']['ref']:
commands.extend(cr.get_proposed())
return remove_duplicate_context(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 = []
if want != have:
commands = ['no telemetry']
return commands

@ -1,290 +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 nxos_vlans 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 dict_diff, to_list, remove_empties
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.utils.utils import search_obj_in_list
class Vlans(ConfigBase):
"""
The nxos_vlans class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'vlans',
]
def __init__(self, module):
super(Vlans, self).__init__(module)
def get_vlans_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)
vlans_facts = facts['ansible_network_resources'].get('vlans')
if not vlans_facts:
return []
# Remove vlan 1 from facts list
vlans_facts = [i for i in vlans_facts if (int(i['vlan_id'])) != 1]
return vlans_facts
def edit_config(self, commands):
"""Wrapper method for `_connection.edit_config()`
This exists solely to allow the unit test framework to mock device connection calls.
"""
return self._connection.edit_config(commands)
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
existing_vlans_facts = self.get_vlans_facts()
commands.extend(self.set_config(existing_vlans_facts))
if commands:
if not self._module.check_mode:
self.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_vlans_facts = self.get_vlans_facts()
result['before'] = existing_vlans_facts
if result['changed']:
result['after'] = changed_vlans_facts
result['warnings'] = warnings
return result
def set_config(self, existing_vlans_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
"""
config = self._module.params.get('config')
want = []
if config:
for w in config:
if int(w['vlan_id']) == 1:
self._module.fail_json(msg="Vlan 1 is not allowed to be managed by this module")
want.append(remove_empties(w))
have = existing_vlans_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 ('overridden', 'merged', 'replaced') and not want:
self._module.fail_json(msg='config is required for state {0}'.format(state))
commands = list()
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
commands.extend(self._state_deleted(want, have))
else:
for w in want:
if state == 'merged':
commands.extend(self._state_merged(w, have))
elif state == 'replaced':
commands.extend(self._state_replaced(w, have))
return commands
def remove_default_states(self, obj):
"""Removes non-empty but default states from the obj.
"""
default_states = {
'enabled': True,
'state': 'active',
'mode': 'ce',
}
for k in default_states.keys():
if obj[k] == default_states[k]:
obj.pop(k, None)
return obj
def _state_replaced(self, want, have):
""" The command generator when state is replaced.
Scope is limited to vlan objects defined in the playbook.
:rtype: A list
:returns: The minimum command set required to migrate the current
configuration to the desired configuration.
"""
obj_in_have = search_obj_in_list(want['vlan_id'], have, 'vlan_id')
if obj_in_have:
# ignore states that are already reset, then diff what's left
obj_in_have = self.remove_default_states(obj_in_have)
diff = dict_diff(want, obj_in_have)
# Remove merge items from diff; what's left will be used to
# remove states not specified in the playbook
for k in dict(set(want.items()) - set(obj_in_have.items())).keys():
diff.pop(k, None)
else:
diff = want
# merged_cmds: 'want' cmds to update 'have' states that don't match
# replaced_cmds: remaining 'have' cmds that need to be reset to default
merged_cmds = self.set_commands(want, have)
replaced_cmds = []
if obj_in_have:
# Remaining diff items are used to reset states to default
replaced_cmds = self.del_attribs(diff)
cmds = []
if replaced_cmds or merged_cmds:
cmds += ['vlan %s' % str(want['vlan_id'])]
cmds += merged_cmds + replaced_cmds
return cmds
def _state_overridden(self, want, have):
""" The command generator when state is overridden.
Scope includes all vlan objects on the device.
:rtype: A list
:returns: the minimum command set required to migrate the current
configuration to the desired configuration.
"""
# overridden behavior is the same as replaced except for scope.
cmds = []
existing_vlans = []
for h in have:
existing_vlans.append(h['vlan_id'])
obj_in_want = search_obj_in_list(h['vlan_id'], want, 'vlan_id')
if obj_in_want:
if h != obj_in_want:
replaced_cmds = self._state_replaced(obj_in_want, [h])
if replaced_cmds:
cmds.extend(replaced_cmds)
else:
cmds.append('no vlan %s' % h['vlan_id'])
# Add wanted vlans that don't exist on the device yet
for w in want:
if w['vlan_id'] not in existing_vlans:
new_vlan = ['vlan %s' % w['vlan_id']]
cmds.extend(new_vlan + self.add_commands(w))
return cmds
def _state_merged(self, w, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
cmds = self.set_commands(w, have)
if cmds:
cmds.insert(0, 'vlan %s' % str(w['vlan_id']))
return(cmds)
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 w in want:
obj_in_have = search_obj_in_list(w['vlan_id'], have, 'vlan_id')
if obj_in_have:
commands.append('no vlan ' + str(obj_in_have['vlan_id']))
else:
if not have:
return commands
for h in have:
commands.append('no vlan ' + str(h['vlan_id']))
return commands
def del_attribs(self, obj):
"""Returns a list of commands to reset states to default
"""
commands = []
if not obj:
return commands
default_cmds = {
'name': 'no name',
'state': 'no state',
'enabled': 'no shutdown',
'mode': 'mode ce',
'mapped_vni': 'no vn-segment',
}
for k in obj:
commands.append(default_cmds[k])
return commands
def diff_of_dicts(self, w, obj):
diff = set(w.items()) - set(obj.items())
diff = dict(diff)
if diff and w['vlan_id'] == obj['vlan_id']:
diff.update({'vlan_id': w['vlan_id']})
return diff
def add_commands(self, d):
commands = []
if not d:
return commands
if 'name' in d:
commands.append('name ' + d['name'])
if 'state' in d:
commands.append('state ' + d['state'])
if 'enabled' in d:
if d['enabled'] is True:
commands.append('no shutdown')
else:
commands.append('shutdown')
if 'mode' in d:
commands.append('mode ' + d['mode'])
if 'mapped_vni' in d:
commands.append('vn-segment %s' % d['mapped_vni'])
return commands
def set_commands(self, w, have):
commands = []
obj_in_have = search_obj_in_list(w['vlan_id'], have, 'vlan_id')
if not obj_in_have:
commands = self.add_commands(w)
else:
diff = self.diff_of_dicts(w, obj_in_have)
commands = self.add_commands(diff)
return commands

@ -1,119 +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 nxos 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.network.common import utils
from ansible.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import normalize_interface
class Acl_interfacesFacts(object):
""" The nxos 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 get_device_data(self, connection):
return connection.get('show running-config | section interface')
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 = self.get_device_data(connection)
data = data.split('interface')
resources = []
for i in range(len(data)):
intf = data[i].split('\n')
for l in range(1, len(intf)):
if not re.search('ip(v6)?( port)? (access-group|traffic-filter)', intf[l]):
intf[l] = ''
intf = list(filter(None, intf))
resources.append(intf)
objs = []
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('acl_interfaces', None)
facts = {}
if objs:
params = utils.validate_config(
self.argument_spec, {'config': objs})
params = utils.remove_empties(params)
facts['acl_interfaces'] = 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)
name = conf[0].strip()
config['name'] = normalize_interface(name)
config['access_groups'] = []
v4 = {'afi': 'ipv4', 'acls': []}
v6 = {'afi': 'ipv6', 'acls': []}
for c in conf[1:]:
if c:
acl4 = re.search(r'ip( port)? access-group (\w*) (\w*)', c)
acl6 = re.search(r'ipv6( port)? traffic-filter (\w*) (\w*)', c)
if acl4:
acl = {'name': acl4.group(2).strip(
), 'direction': acl4.group(3).strip()}
if acl4.group(1):
acl.update({'port': True})
v4['acls'].append(acl)
elif acl6:
acl = {'name': acl6.group(2), 'direction': acl6.group(3)}
if acl6.group(1):
acl.update({'port': True})
v6['acls'].append(acl)
if len(v4['acls']) > 0:
config['access_groups'].append(v4)
if len(v6['acls']) > 0:
config['access_groups'].append(v6)
return utils.remove_empties(config)

@ -1,236 +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 nxos 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 ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.argspec.acls.acls import AclsArgs
class AclsFacts(object):
""" The nxos 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 running-config | section 'ip(v6)* access-list'")
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)
data = re.split('\nip', data)
v6 = []
v4 = []
for i in range(len(data)):
if str(data[i]):
if 'v6' in str(data[i]).split()[0]:
v6.append(data[i])
else:
v4.append(data[i])
resources = []
resources.append(v6)
resources.append(v4)
objs = []
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('acls', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec,
{'config': objs})
params = utils.remove_empties(params)
facts['acls'] = params['config']
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def get_endpoint(self, ace, pro):
ret_dict = {}
option = ace.split()[0]
if option == 'any':
ret_dict.update({'any': True})
else:
# it could be a.b.c.d or a.b.c.d/x or a.b.c.d/32
if '/' in option: # or 'host' in option:
ip = re.search(r'(.*)/(\d+)', option)
if int(ip.group(2)) < 32 or 32 < int(ip.group(2)) < 128:
ret_dict.update({'prefix': option})
else:
ret_dict.update({'host': ip.group(1)})
else:
ret_dict.update({'address': option})
wb = ace.split()[1]
ret_dict.update({'wildcard_bits': wb})
ace = re.sub('{0}'.format(wb), '', ace, 1)
ace = re.sub(option, '', ace, 1)
if pro in ['tcp', 'udp']:
keywords = ['eq', 'lt', 'gt', 'neq', 'range']
if len(ace.split()) and ace.split()[0] in keywords:
port_protocol = {}
port_pro = re.search(r'(eq|lt|gt|neq) (\w*)', ace)
if port_pro:
port_protocol.update(
{port_pro.group(1): port_pro.group(2)})
ace = re.sub(port_pro.group(1), '', ace, 1)
ace = re.sub(port_pro.group(2), '', ace, 1)
else:
limit = re.search(r'(range) (\w*) (\w*)', ace)
if limit:
port_protocol.update({
'range': {
'start': limit.group(2),
'end': limit.group(3)
}
})
ace = re.sub(limit.group(2), '', ace, 1)
ace = re.sub(limit.group(3), '', ace, 1)
if port_protocol:
ret_dict.update({'port_protocol': port_protocol})
return ace, ret_dict
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)
protocol_options = {
'tcp': ['fin', 'established', 'psh', 'rst', 'syn', 'urg', 'ack'],
'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'
],
'igmp': ['dvmrp', 'host_query', 'host_report'],
}
if conf:
if 'v6' in conf[0].split()[0]:
config['afi'] = 'ipv6'
else:
config['afi'] = 'ipv4'
config['acls'] = []
for acl in conf:
acls = {}
if 'match-local-traffic' in acl:
config['match_local_traffic'] = True
continue
acl = acl.split('\n')
acl = [a.strip() for a in acl]
acl = list(filter(None, acl))
acls['name'] = re.match(r'(ip)?(v6)?\s?access-list (.*)',
acl[0]).group(3)
acls['aces'] = []
for ace in list(filter(None, acl[1:])):
if re.search(r'ip(.*)access-list.*', ace):
break
entry = {}
ace = ace.strip()
seq = re.match(r'(\d*)', ace).group(0)
entry.update({'sequence': seq})
ace = re.sub(seq, '', ace, 1)
grant = ace.split()[0]
rem = ''
if grant != 'remark':
entry.update({'grant': grant})
else:
rem = re.match('.*remark (.*)', ace).group(1)
entry.update({'remark': rem})
if not rem:
ace = re.sub(grant, '', ace, 1)
pro = ace.split()[0]
entry.update({'protocol': pro})
ace = re.sub(pro, '', ace, 1)
ace, source = self.get_endpoint(ace, pro)
entry.update({'source': source})
ace, dest = self.get_endpoint(ace, pro)
entry.update({'destination': dest})
dscp = re.search(r'dscp (\w*)', ace)
if dscp:
entry.update({'dscp': dscp.group(1)})
frag = re.search(r'fragments', ace)
if frag:
entry.update({'fragments': True})
prec = re.search(r'precedence (\w*)', ace)
if prec:
entry.update({'precedence': prec.group(1)})
log = re.search('log', ace)
if log:
entry.update({'log': True})
if pro == 'tcp' or pro == 'icmp' or pro == 'igmp':
pro_options = {}
options = {}
for option in protocol_options[pro]:
option = re.sub('_', '-', option)
if option in ace:
option = re.sub('-', '_', option)
options.update({option: True})
if options:
pro_options.update({pro: options})
if pro_options:
entry.update({'protocol_options': pro_options})
acls['aces'].append(entry)
config['acls'].append(acls)
return utils.remove_empties(config)

@ -1,97 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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
"""
The nxos bfd_interfaces fact class
Populate the facts tree based on the current device configuration.
"""
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import Bfd_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
class Bfd_interfacesFacts(object):
""" The nxos_bfd_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Bfd_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 bfd_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 | section '^interface|^feature bfd'")
# Some of the bfd attributes
if 'feature bfd' in data.split('\n'):
resources = data.split('interface ')
resources.pop(0)
else:
resources = []
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj and len(obj.keys()) > 1:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('bfd_interfaces', None)
facts = {}
if objs:
facts['bfd_interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['bfd_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 get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
# 'bfd'/'bfd echo' do not nvgen when enabled thus set to 'enable' when None.
# 'bfd' is not supported on some platforms
config['bfd'] = utils.parse_conf_cmd_arg(conf, 'bfd', 'enable', 'disable') or 'enable'
config['echo'] = utils.parse_conf_cmd_arg(conf, 'bfd echo', 'enable', 'disable') or 'enable'
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 facts class for nxos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.nxos.facts.legacy.base import Default, Legacy, Hardware, Config, Interfaces, Features
from ansible.module_utils.network.nxos.facts.bfd_interfaces.bfd_interfaces import Bfd_interfacesFacts
from ansible.module_utils.network.nxos.facts.hsrp_interfaces.hsrp_interfaces import Hsrp_interfacesFacts
from ansible.module_utils.network.nxos.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.nxos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.nxos.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.nxos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
from ansible.module_utils.network.nxos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.nxos.facts.telemetry.telemetry import TelemetryFacts
from ansible.module_utils.network.nxos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.nxos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts
from ansible.module_utils.network.nxos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.nxos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.nxos.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
from ansible.module_utils.network.nxos.facts.acls.acls import AclsFacts
FACT_LEGACY_SUBSETS = dict(
default=Default,
legacy=Legacy,
hardware=Hardware,
interfaces=Interfaces,
config=Config,
features=Features,
)
FACT_RESOURCE_SUBSETS = dict(
bfd_interfaces=Bfd_interfacesFacts,
hsrp_interfaces=Hsrp_interfacesFacts,
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
telemetry=TelemetryFacts,
vlans=VlansFacts,
lacp=LacpFacts,
lacp_interfaces=Lacp_interfacesFacts,
interfaces=InterfacesFacts,
l3_interfaces=L3_interfacesFacts,
l2_interfaces=L2_interfacesFacts,
lldp_interfaces=Lldp_interfacesFacts,
acl_interfaces=Acl_interfacesFacts,
acls=AclsFacts,
)
class Facts(FactsBase):
""" The fact class for nxos
"""
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 nxos
: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,89 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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
"""
The nxos hsrp_interfaces fact class
Populate the facts tree based on the current device configuration.
"""
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import Hsrp_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
class Hsrp_interfacesFacts(object):
""" The nxos hsrp_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Hsrp_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 hsrp_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 | section ^interface')
resources = data.split('interface ')
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj and len(obj.keys()) > 1:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('hsrp_interfaces', None)
facts = {}
if objs:
facts['hsrp_interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['hsrp_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 get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
config['bfd'] = utils.parse_conf_cmd_arg(conf, 'hsrp bfd', 'enable', 'disable')
return utils.remove_empties(config)

@ -1,151 +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)#!/usr/bin/python
"""
The nxos 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.nxos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
from ansible.module_utils.network.nxos.nxos import default_intf_enabled
class InterfacesFacts(object):
""" The nxos 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 connection: the device connection
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = connection.get("show running-config all | incl 'system default switchport'")
data += connection.get('show running-config | section ^interface')
# Collect device defaults & per-intf defaults
self.render_system_defaults(data)
intf_defs = {'sysdefs': self.sysdefs}
config = data.split('interface ')
default_interfaces = []
for conf in config:
conf = conf.strip()
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj:
intf_defs[obj['name']] = obj.pop('enabled_def', None)
if len(obj.keys()) > 1:
objs.append(obj)
elif len(obj.keys()) == 1:
# Existing-but-default interfaces are not included in the
# objs list; however a list of default interfaces is
# necessary to prevent idempotence issues and to help
# with virtual interfaces that haven't been created yet.
default_interfaces.append(obj['name'])
ansible_facts['ansible_network_resources'].pop('interfaces', None)
facts = {}
facts['interfaces'] = []
if objs:
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)
ansible_facts['ansible_network_resources']['default_interfaces'] = default_interfaces
ansible_facts['intf_defs'] = intf_defs
return ansible_facts
def _device_info(self):
return self._module._capabilities.get('device_info', {})
def render_system_defaults(self, config):
"""Collect user-defined-default states for 'system default switchport' configurations.
These configurations determine default L2/L3 modes and enabled/shutdown
states. The default values for user-defined-default configurations may
be different for legacy platforms.
Notes:
- L3 enabled default state is False on N9K,N7K but True for N3K,N6K
- Changing L2-L3 modes may change the default enabled value.
- '(no) system default switchport shutdown' only applies to L2 interfaces.
"""
platform = self._device_info().get('network_os_platform', '')
L3_enabled = True if re.search('N[356]K', platform) else False
sysdefs = {
'mode': None,
'L2_enabled': None,
'L3_enabled': L3_enabled
}
pat = '(no )*system default switchport$'
m = re.search(pat, config, re.MULTILINE)
if m:
sysdefs['mode'] = 'layer3' if 'no ' in m.groups() else 'layer2'
pat = '(no )*system default switchport shutdown$'
m = re.search(pat, config, re.MULTILINE)
if m:
sysdefs['L2_enabled'] = True if 'no ' in m.groups() else False
self.sysdefs = sysdefs
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 get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
config['description'] = utils.parse_conf_arg(conf, 'description')
config['speed'] = utils.parse_conf_arg(conf, 'speed')
config['mtu'] = utils.parse_conf_arg(conf, 'mtu')
config['duplex'] = utils.parse_conf_arg(conf, 'duplex')
config['mode'] = utils.parse_conf_cmd_arg(conf, 'switchport', 'layer2', 'layer3')
config['enabled'] = utils.parse_conf_cmd_arg(conf, 'shutdown', False, True)
# Capture the default 'enabled' state, which may be interface-specific
config['enabled_def'] = default_intf_enabled(name=intf, sysdefs=self.sysdefs, mode=config['mode'])
config['fabric_forwarding_anycast_gateway'] = utils.parse_conf_cmd_arg(conf, 'fabric forwarding mode anycast-gateway', True)
config['ip_forward'] = utils.parse_conf_cmd_arg(conf, 'ip forward', True)
interfaces_cfg = utils.remove_empties(config)
return interfaces_cfg

@ -1,94 +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)#!/usr/bin/python
"""
The nxos 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
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
class L2_interfacesFacts(object):
"""The nxos 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 connection: the device connection
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = connection.get('show running-config | section ^interface')
config = data.split('interface ')
for conf in config:
conf = conf.strip()
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj and len(obj.keys()) > 1:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('l2_interfaces', None)
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 get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
config['mode'] = utils.parse_conf_arg(conf, 'switchport mode')
config['ip_forward'] = utils.parse_conf_arg(conf, 'ip forward')
config['access']['vlan'] = utils.parse_conf_arg(conf, 'switchport access vlan')
config['trunk']['allowed_vlans'] = utils.parse_conf_arg(conf, 'switchport trunk allowed vlan')
config['trunk']['native_vlan'] = utils.parse_conf_arg(conf, 'switchport trunk native vlan')
return utils.remove_empties(config)

@ -1,126 +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)#!/usr/bin/python
"""
The nxos 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
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
class L3_interfacesFacts(object):
""" The nxos 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 l3_interfaces
:param connection: the device connection
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = connection.get('show running-config | section ^interface')
config = data.split('interface ')
for conf in config:
conf = conf.strip()
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj and len(obj.keys()) > 1:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('l3_interfaces', None)
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 get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
config['dot1q'] = utils.parse_conf_arg(conf, 'encapsulation dot1[qQ]')
config['redirects'] = utils.parse_conf_cmd_arg(conf, 'no ip redirects', False, True)
config['unreachables'] = utils.parse_conf_cmd_arg(conf, 'ip unreachables', True, False)
ipv4_match = re.compile(r'\n ip address (.*)')
matches = ipv4_match.findall(conf)
if matches:
if matches[0]:
config['ipv4'] = []
for m in matches:
ipv4_conf = m.split()
addr = ipv4_conf[0]
if addr:
config_dict = {'address': addr}
if len(ipv4_conf) > 1:
d = ipv4_conf[1]
if d == 'secondary':
config_dict.update({'secondary': True})
if len(ipv4_conf) == 4:
if ipv4_conf[2] == 'tag':
config_dict.update({'tag': int(ipv4_conf[-1])})
elif d == 'tag':
config_dict.update({'tag': int(ipv4_conf[-1])})
config['ipv4'].append(config_dict)
ipv6_match = re.compile(r'\n ipv6 address (.*)')
matches = ipv6_match.findall(conf)
if matches:
if matches[0]:
config['ipv6'] = []
for m in matches:
ipv6_conf = m.split()
addr = ipv6_conf[0]
if addr:
config_dict = {'address': addr}
if len(ipv6_conf) > 1:
d = ipv6_conf[1]
if d == 'tag':
config_dict.update({'tag': int(ipv6_conf[-1])})
config['ipv6'].append(config_dict)
return utils.remove_empties(config)

@ -1,84 +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 nxos 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
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.nxos.argspec.lacp.lacp import LacpArgs
class LacpFacts(object):
""" The nxos 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("show running-config | include lacp")
resources = data.strip()
objs = self.render_config(self.generated_spec, resources)
ansible_facts['ansible_network_resources'].pop('lacp', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
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)
p_match = re.search(r'lacp system-priority (\d+)', conf, re.M)
if p_match:
config['system']['priority'] = p_match.group(1)
a_match = re.search(r'lacp system-mac (\S+)', conf, re.M)
if a_match:
address = a_match.group(1)
config['system']['mac']['address'] = address
r_match = re.search(r'lacp system-mac {0} role (\S+)'.format(address), conf, re.M)
if r_match:
config['system']['mac']['role'] = r_match.group(1)
return utils.remove_empties(config)

@ -1,108 +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 nxos 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.nxos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
class Lacp_interfacesFacts(object):
""" The nxos 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
"""
objs = []
if not data:
data = connection.get('show running-config | section ^interface')
resources = data.split('interface ')
for resource in resources:
if resource and re.search(r'lacp', resource):
obj = self.render_config(self.generated_spec, resource)
if obj and len(obj.keys()) > 1:
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'^(\S+)', conf)
intf = match.group(1)
if get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
config['port_priority'] = utils.parse_conf_arg(conf, 'lacp port-priority')
config['rate'] = utils.parse_conf_arg(conf, 'lacp rate')
config['mode'] = utils.parse_conf_arg(conf, 'mode')
suspend_individual = re.search(r'no lacp suspend-individual', conf)
if suspend_individual:
config['suspend_individual'] = False
max_links = utils.parse_conf_arg(conf, 'lacp max-bundle')
if max_links:
config['links']['max'] = max_links
min_links = utils.parse_conf_arg(conf, 'lacp min-links')
if min_links:
config['links']['min'] = min_links
graceful = re.search(r'no lacp graceful-convergence', conf)
if graceful:
config['convergence']['gracefule'] = False
vpc = re.search(r'lacp vpc-convergence', conf)
if vpc:
config['convergence']['vpc'] = True
return utils.remove_empties(config)

@ -1,124 +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)#!/usr/bin/python
"""
The nxos 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.nxos.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type, normalize_interface
class Lag_interfacesFacts(object):
""" The nxos 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 data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = connection.get('show running-config | include channel-group')
config = re.split('(\n |)channel-group ', data)
config = list(dict.fromkeys(config))
for conf in config:
if conf:
obj = self.render_config(self.generated_spec, conf, connection)
if obj and len(obj.keys()) > 1:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('lag_interfaces', None)
facts = {}
if objs:
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 get_members(self, id, connection):
"""
Returns members associated with a channel-group
:param name: The channel group
:rtype: list
:returns: Members
"""
members = []
data = connection.get('show port-channel summary')
match = re.search(r'{0} (.+)(|\n)'.format(id), data)
if match:
interfaces = re.search(r'Eth\d(.+)$', match.group())
if interfaces:
for i in interfaces.group().split():
if get_interface_type(i[:-3]) != 'unknown':
members.append(normalize_interface(i[:-3]))
return members
def render_config(self, spec, conf, connection):
"""
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'(\d+)( |)(force )?(mode \S+)?', conf, re.M)
if match:
matches = match.groups()
config['name'] = 'port-channel' + str(matches[0])
config['members'] = []
members = self.get_members(config['name'].strip('port-channel'), connection)
if members:
for m in members:
m_dict = {}
if matches[2]:
m_dict['force'] = matches[2]
if matches[3]:
m_dict['mode'] = matches[3][5:]
m_dict['member'] = m
config['members'].append(m_dict)
else:
config = {}
lag_intf_cfg = utils.remove_empties(config)
# if lag interfaces config is not present return empty dict
if len(lag_intf_cfg) == 1:
return {}
else:
return lag_intf_cfg

@ -1,756 +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)
import platform
import re
from ansible.module_utils.network.nxos.nxos import run_commands, get_config, get_capabilities
from ansible.module_utils.network.nxos.utils.utils import get_interface_type, normalize_interface
from ansible.module_utils.six import iteritems
g_config = None
class FactsBase(object):
def __init__(self, module):
self.module = module
self.warnings = list()
self.facts = dict()
self.capabilities = get_capabilities(self.module)
def populate(self):
pass
def run(self, command, output='text'):
command_string = command
command = {
'command': command,
'output': output
}
resp = run_commands(self.module, [command], check_rc='retry_json')
try:
return resp[0]
except IndexError:
self.warnings.append('command %s failed, facts for this command will not be populated' % command_string)
return None
def get_config(self):
global g_config
if not g_config:
g_config = get_config(self.module)
return g_config
def transform_dict(self, data, keymap):
transform = dict()
for key, fact in keymap:
if key in data:
transform[fact] = data[key]
return transform
def transform_iterable(self, iterable, keymap):
for item in iterable:
yield self.transform_dict(item, keymap)
class Default(FactsBase):
def populate(self):
data = None
data = self.run('show version')
if data:
self.facts['serialnum'] = self.parse_serialnum(data)
data = self.run('show license host-id')
if data:
self.facts['license_hostid'] = self.parse_license_hostid(data)
self.facts.update(self.platform_facts())
def parse_serialnum(self, data):
match = re.search(r'Processor Board ID\s*(\S+)', data, re.M)
if match:
return match.group(1)
def platform_facts(self):
platform_facts = {}
resp = self.capabilities
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
def parse_license_hostid(self, data):
match = re.search(r'License hostid: VDH=(.+)$', data, re.M)
if match:
return match.group(1)
class Config(FactsBase):
def populate(self):
super(Config, self).populate()
self.facts['config'] = self.get_config()
class Features(FactsBase):
def populate(self):
super(Features, self).populate()
data = self.get_config()
if data:
features = []
for line in data.splitlines():
if line.startswith('feature'):
features.append(line.replace('feature', '').strip())
self.facts['features_enabled'] = features
class Hardware(FactsBase):
def populate(self):
data = self.run('dir')
if data:
self.facts['filesystems'] = self.parse_filesystems(data)
data = None
data = self.run('show system resources', output='json')
if data:
if isinstance(data, dict):
self.facts['memtotal_mb'] = int(data['memory_usage_total']) / 1024
self.facts['memfree_mb'] = int(data['memory_usage_free']) / 1024
else:
self.facts['memtotal_mb'] = self.parse_memtotal_mb(data)
self.facts['memfree_mb'] = self.parse_memfree_mb(data)
def parse_filesystems(self, data):
return re.findall(r'^Usage for (\S+)//', data, re.M)
def parse_memtotal_mb(self, data):
match = re.search(r'(\S+)K(\s+|)total', data, re.M)
if match:
memtotal = match.group(1)
return int(memtotal) / 1024
def parse_memfree_mb(self, data):
match = re.search(r'(\S+)K(\s+|)free', data, re.M)
if match:
memfree = match.group(1)
return int(memfree) / 1024
class Interfaces(FactsBase):
INTERFACE_MAP = frozenset([
('state', 'state'),
('desc', 'description'),
('eth_bw', 'bandwidth'),
('eth_duplex', 'duplex'),
('eth_speed', 'speed'),
('eth_mode', 'mode'),
('eth_hw_addr', 'macaddress'),
('eth_mtu', 'mtu'),
('eth_hw_desc', 'type')
])
INTERFACE_SVI_MAP = frozenset([
('svi_line_proto', 'state'),
('svi_bw', 'bandwidth'),
('svi_mac', 'macaddress'),
('svi_mtu', 'mtu'),
('type', 'type')
])
INTERFACE_IPV4_MAP = frozenset([
('eth_ip_addr', 'address'),
('eth_ip_mask', 'masklen')
])
INTERFACE_SVI_IPV4_MAP = frozenset([
('svi_ip_addr', 'address'),
('svi_ip_mask', 'masklen')
])
INTERFACE_IPV6_MAP = frozenset([
('addr', 'address'),
('prefix', 'subnet')
])
def ipv6_structure_op_supported(self):
data = self.capabilities
if data:
nxos_os_version = data['device_info']['network_os_version']
unsupported_versions = ['I2', 'F1', 'A8']
for ver in unsupported_versions:
if ver in nxos_os_version:
return False
return True
def populate(self):
self.facts['all_ipv4_addresses'] = list()
self.facts['all_ipv6_addresses'] = list()
self.facts['neighbors'] = {}
data = None
data = self.run('show interface', output='json')
if data:
if isinstance(data, dict):
self.facts['interfaces'] = self.populate_structured_interfaces(data)
else:
interfaces = self.parse_interfaces(data)
self.facts['interfaces'] = self.populate_interfaces(interfaces)
if self.ipv6_structure_op_supported():
data = self.run('show ipv6 interface', output='json')
else:
data = None
if data:
if isinstance(data, dict):
self.populate_structured_ipv6_interfaces(data)
else:
interfaces = self.parse_interfaces(data)
self.populate_ipv6_interfaces(interfaces)
data = self.run('show lldp neighbors', output='json')
if data:
if isinstance(data, dict):
self.facts['neighbors'].update(self.populate_structured_neighbors_lldp(data))
else:
self.facts['neighbors'].update(self.populate_neighbors(data))
data = self.run('show cdp neighbors detail', output='json')
if data:
if isinstance(data, dict):
self.facts['neighbors'].update(self.populate_structured_neighbors_cdp(data))
else:
self.facts['neighbors'].update(self.populate_neighbors_cdp(data))
self.facts['neighbors'].pop(None, None) # Remove null key
def populate_structured_interfaces(self, data):
interfaces = dict()
for item in data['TABLE_interface']['ROW_interface']:
name = item['interface']
intf = dict()
if 'type' in item:
intf.update(self.transform_dict(item, self.INTERFACE_SVI_MAP))
else:
intf.update(self.transform_dict(item, self.INTERFACE_MAP))
if 'eth_ip_addr' in item:
intf['ipv4'] = self.transform_dict(item, self.INTERFACE_IPV4_MAP)
self.facts['all_ipv4_addresses'].append(item['eth_ip_addr'])
if 'svi_ip_addr' in item:
intf['ipv4'] = self.transform_dict(item, self.INTERFACE_SVI_IPV4_MAP)
self.facts['all_ipv4_addresses'].append(item['svi_ip_addr'])
interfaces[name] = intf
return interfaces
def populate_structured_ipv6_interfaces(self, data):
try:
data = data['TABLE_intf']
if data:
if isinstance(data, dict):
data = [data]
for item in data:
name = item['ROW_intf']['intf-name']
intf = self.facts['interfaces'][name]
intf['ipv6'] = self.transform_dict(item, self.INTERFACE_IPV6_MAP)
try:
addr = item['ROW_intf']['addr']
except KeyError:
addr = item['ROW_intf']['TABLE_addr']['ROW_addr']['addr']
self.facts['all_ipv6_addresses'].append(addr)
else:
return ""
except TypeError:
return ""
def populate_structured_neighbors_lldp(self, data):
objects = dict()
data = data['TABLE_nbor']['ROW_nbor']
if isinstance(data, dict):
data = [data]
for item in data:
local_intf = normalize_interface(item['l_port_id'])
objects[local_intf] = list()
nbor = dict()
nbor['port'] = item['port_id']
nbor['host'] = nbor['sysname'] = item['chassis_id']
objects[local_intf].append(nbor)
return objects
def populate_structured_neighbors_cdp(self, data):
objects = dict()
data = data['TABLE_cdp_neighbor_detail_info']['ROW_cdp_neighbor_detail_info']
if isinstance(data, dict):
data = [data]
for item in data:
local_intf = item['intf_id']
objects[local_intf] = list()
nbor = dict()
nbor['port'] = item['port_id']
nbor['host'] = nbor['sysname'] = item['device_id']
objects[local_intf].append(nbor)
return objects
def parse_interfaces(self, data):
parsed = dict()
key = ''
for line in data.split('\n'):
if len(line) == 0:
continue
elif line.startswith('admin') or line[0] == ' ':
parsed[key] += '\n%s' % line
else:
match = re.match(r'^(\S+)', line)
if match:
key = match.group(1)
if not key.startswith('admin') or not key.startswith('IPv6 Interface'):
parsed[key] = line
return parsed
def populate_interfaces(self, interfaces):
facts = dict()
for key, value in iteritems(interfaces):
intf = dict()
if get_interface_type(key) == 'svi':
intf['state'] = self.parse_state(key, value, intf_type='svi')
intf['macaddress'] = self.parse_macaddress(value, intf_type='svi')
intf['mtu'] = self.parse_mtu(value, intf_type='svi')
intf['bandwidth'] = self.parse_bandwidth(value, intf_type='svi')
intf['type'] = self.parse_type(value, intf_type='svi')
if 'Internet Address' in value:
intf['ipv4'] = self.parse_ipv4_address(value, intf_type='svi')
facts[key] = intf
else:
intf['state'] = self.parse_state(key, value)
intf['description'] = self.parse_description(value)
intf['macaddress'] = self.parse_macaddress(value)
intf['mode'] = self.parse_mode(value)
intf['mtu'] = self.parse_mtu(value)
intf['bandwidth'] = self.parse_bandwidth(value)
intf['duplex'] = self.parse_duplex(value)
intf['speed'] = self.parse_speed(value)
intf['type'] = self.parse_type(value)
if 'Internet Address' in value:
intf['ipv4'] = self.parse_ipv4_address(value)
facts[key] = intf
return facts
def parse_state(self, key, value, intf_type='ethernet'):
match = None
if intf_type == 'svi':
match = re.search(r'line protocol is\s*(\S+)', value, re.M)
else:
match = re.search(r'%s is\s*(\S+)' % key, value, re.M)
if match:
return match.group(1)
def parse_macaddress(self, value, intf_type='ethernet'):
match = None
if intf_type == 'svi':
match = re.search(r'address is\s*(\S+)', value, re.M)
else:
match = re.search(r'address:\s*(\S+)', value, re.M)
if match:
return match.group(1)
def parse_mtu(self, value, intf_type='ethernet'):
match = re.search(r'MTU\s*(\S+)', value, re.M)
if match:
return match.group(1)
def parse_bandwidth(self, value, intf_type='ethernet'):
match = re.search(r'BW\s*(\S+)', value, re.M)
if match:
return match.group(1)
def parse_type(self, value, intf_type='ethernet'):
match = None
if intf_type == 'svi':
match = re.search(r'Hardware is\s*(\S+)', value, re.M)
else:
match = re.search(r'Hardware:\s*(.+),', value, re.M)
if match:
return match.group(1)
def parse_description(self, value, intf_type='ethernet'):
match = re.search(r'Description: (.+)$', value, re.M)
if match:
return match.group(1)
def parse_mode(self, value, intf_type='ethernet'):
match = re.search(r'Port mode is (\S+)', value, re.M)
if match:
return match.group(1)
def parse_duplex(self, value, intf_type='ethernet'):
match = re.search(r'(\S+)-duplex', value, re.M)
if match:
return match.group(1)
def parse_speed(self, value, intf_type='ethernet'):
match = re.search(r'duplex, (.+)$', value, re.M)
if match:
return match.group(1)
def parse_ipv4_address(self, value, intf_type='ethernet'):
ipv4 = {}
match = re.search(r'Internet Address is (.+)$', value, re.M)
if match:
address = match.group(1)
addr = address.split('/')[0]
ipv4['address'] = address.split('/')[0]
ipv4['masklen'] = address.split('/')[1]
self.facts['all_ipv4_addresses'].append(addr)
return ipv4
def populate_neighbors(self, data):
objects = dict()
# if there are no neighbors the show command returns
# ERROR: No neighbour information
if data.startswith('ERROR'):
return dict()
regex = re.compile(r'(\S+)\s+(\S+)\s+\d+\s+\w+\s+(\S+)')
for item in data.split('\n')[4:-1]:
match = regex.match(item)
if match:
nbor = dict()
nbor['host'] = nbor['sysname'] = match.group(1)
nbor['port'] = match.group(3)
local_intf = normalize_interface(match.group(2))
if local_intf not in objects:
objects[local_intf] = []
objects[local_intf].append(nbor)
return objects
def populate_neighbors_cdp(self, data):
facts = dict()
for item in data.split('----------------------------------------'):
if item == '':
continue
local_intf = self.parse_lldp_intf(item)
if local_intf not in facts:
facts[local_intf] = list()
fact = dict()
fact['port'] = self.parse_lldp_port(item)
fact['sysname'] = self.parse_lldp_sysname(item)
facts[local_intf].append(fact)
return facts
def parse_lldp_intf(self, data):
match = re.search(r'Interface:\s*(\S+)', data, re.M)
if match:
return match.group(1).strip(',')
def parse_lldp_port(self, data):
match = re.search(r'Port ID \(outgoing port\):\s*(\S+)', data, re.M)
if match:
return match.group(1)
def parse_lldp_sysname(self, data):
match = re.search(r'Device ID:(.+)$', data, re.M)
if match:
return match.group(1)
def populate_ipv6_interfaces(self, interfaces):
facts = dict()
for key, value in iteritems(interfaces):
intf = dict()
intf['ipv6'] = self.parse_ipv6_address(value)
facts[key] = intf
def parse_ipv6_address(self, value):
ipv6 = {}
match_addr = re.search(r'IPv6 address:\s*(\S+)', value, re.M)
if match_addr:
addr = match_addr.group(1)
ipv6['address'] = addr
self.facts['all_ipv6_addresses'].append(addr)
match_subnet = re.search(r'IPv6 subnet:\s*(\S+)', value, re.M)
if match_subnet:
ipv6['subnet'] = match_subnet.group(1)
return ipv6
class Legacy(FactsBase):
# facts from nxos_facts 2.1
VERSION_MAP = frozenset([
('host_name', '_hostname'),
('kickstart_ver_str', '_os'),
('chassis_id', '_platform')
])
MODULE_MAP = frozenset([
('model', 'model'),
('modtype', 'type'),
('ports', 'ports'),
('status', 'status')
])
FAN_MAP = frozenset([
('fanname', 'name'),
('fanmodel', 'model'),
('fanhwver', 'hw_ver'),
('fandir', 'direction'),
('fanstatus', 'status')
])
POWERSUP_MAP = frozenset([
('psmodel', 'model'),
('psnum', 'number'),
('ps_status', 'status'),
('ps_status_3k', 'status'),
('actual_out', 'actual_output'),
('actual_in', 'actual_in'),
('total_capa', 'total_capacity'),
('input_type', 'input_type'),
('watts', 'watts'),
('amps', 'amps')
])
def populate(self):
data = None
data = self.run('show version', output='json')
if data:
if isinstance(data, dict):
self.facts.update(self.transform_dict(data, self.VERSION_MAP))
else:
self.facts['hostname'] = self.parse_hostname(data)
self.facts['os'] = self.parse_os(data)
self.facts['platform'] = self.parse_platform(data)
data = self.run('show interface', output='json')
if data:
if isinstance(data, dict):
self.facts['interfaces_list'] = self.parse_structured_interfaces(data)
else:
self.facts['interfaces_list'] = self.parse_interfaces(data)
data = self.run('show vlan brief', output='json')
if data:
if isinstance(data, dict):
self.facts['vlan_list'] = self.parse_structured_vlans(data)
else:
self.facts['vlan_list'] = self.parse_vlans(data)
data = self.run('show module', output='json')
if data:
if isinstance(data, dict):
self.facts['module'] = self.parse_structured_module(data)
else:
self.facts['module'] = self.parse_module(data)
data = self.run('show environment fan', output='json')
if data:
if isinstance(data, dict):
self.facts['fan_info'] = self.parse_structured_fan_info(data)
else:
self.facts['fan_info'] = self.parse_fan_info(data)
data = self.run('show environment power', output='json')
if data:
if isinstance(data, dict):
self.facts['power_supply_info'] = self.parse_structured_power_supply_info(data)
else:
self.facts['power_supply_info'] = self.parse_power_supply_info(data)
def parse_structured_interfaces(self, data):
objects = list()
for item in data['TABLE_interface']['ROW_interface']:
objects.append(item['interface'])
return objects
def parse_structured_vlans(self, data):
objects = list()
data = data['TABLE_vlanbriefxbrief']['ROW_vlanbriefxbrief']
if isinstance(data, dict):
objects.append(data['vlanshowbr-vlanid-utf'])
elif isinstance(data, list):
for item in data:
objects.append(item['vlanshowbr-vlanid-utf'])
return objects
def parse_structured_module(self, data):
data = data['TABLE_modinfo']['ROW_modinfo']
if isinstance(data, dict):
data = [data]
objects = list(self.transform_iterable(data, self.MODULE_MAP))
return objects
def parse_structured_fan_info(self, data):
objects = list()
for key in ("fandetails", "fandetails_3k"):
if data.get(key):
try:
data = data[key]['TABLE_faninfo']['ROW_faninfo']
except KeyError:
# Some virtual images don't actually report faninfo. In this case, move on and
# just return an empty list.
pass
else:
objects = list(self.transform_iterable(data, self.FAN_MAP))
break
return objects
def parse_structured_power_supply_info(self, data):
if data.get('powersup').get('TABLE_psinfo_n3k'):
fact = data['powersup']['TABLE_psinfo_n3k']['ROW_psinfo_n3k']
else:
if isinstance(data['powersup']['TABLE_psinfo'], list):
fact = []
for i in data['powersup']['TABLE_psinfo']:
fact.append(i['ROW_psinfo'])
else:
fact = data['powersup']['TABLE_psinfo']['ROW_psinfo']
objects = list(self.transform_iterable(fact, self.POWERSUP_MAP))
return objects
def parse_hostname(self, data):
match = re.search(r'\s+Device name:\s+(\S+)', data, re.M)
if match:
return match.group(1)
def parse_os(self, data):
match = re.search(r'\s+system:\s+version\s*(\S+)', data, re.M)
if match:
return match.group(1)
else:
match = re.search(r'\s+kickstart:\s+version\s*(\S+)', data, re.M)
if match:
return match.group(1)
def parse_platform(self, data):
match = re.search(r'Hardware\n\s+cisco\s+(\S+\s+\S+)', data, re.M)
if match:
return match.group(1)
def parse_interfaces(self, data):
objects = list()
for line in data.split('\n'):
if len(line) == 0:
continue
elif line.startswith('admin') or line[0] == ' ':
continue
else:
match = re.match(r'^(\S+)', line)
if match:
intf = match.group(1)
if get_interface_type(intf) != 'unknown':
objects.append(intf)
return objects
def parse_vlans(self, data):
objects = list()
for line in data.splitlines():
if line == '':
continue
if line[0].isdigit():
vlan = line.split()[0]
objects.append(vlan)
return objects
def parse_module(self, data):
objects = list()
for line in data.splitlines():
if line == '':
break
if line[0].isdigit():
obj = {}
match_port = re.search(r'\d\s*(\d*)', line, re.M)
if match_port:
obj['ports'] = match_port.group(1)
match = re.search(r'\d\s*\d*\s*(.+)$', line, re.M)
if match:
l = match.group(1).split(' ')
items = list()
for item in l:
if item == '':
continue
items.append(item.strip())
if items:
obj['type'] = items[0]
obj['model'] = items[1]
obj['status'] = items[2]
objects.append(obj)
return objects
def parse_fan_info(self, data):
objects = list()
for l in data.splitlines():
if '-----------------' in l or 'Status' in l:
continue
line = l.split()
if len(line) > 1:
obj = {}
obj['name'] = line[0]
obj['model'] = line[1]
obj['hw_ver'] = line[-2]
obj['status'] = line[-1]
objects.append(obj)
return objects
def parse_power_supply_info(self, data):
objects = list()
for l in data.splitlines():
if l == '':
break
if l[0].isdigit():
obj = {}
line = l.split()
obj['model'] = line[1]
obj['number'] = line[0]
obj['status'] = line[-1]
objects.append(obj)
return objects

@ -1,105 +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 nxos lldp_global 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.nxos.argspec.lldp_global.lldp_global import Lldp_globalArgs
class Lldp_globalFacts(object):
""" The nxos lldp_global 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_global
: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('show running-config | include lldp')
objs = {}
objs = self.render_config(self.generated_spec, data)
ansible_facts['ansible_network_resources'].pop('lldp_global', None)
facts = {}
if objs:
params = utils.validate_config(
self.argument_spec, {'config': objs})
facts['lldp_global'] = params['config']
facts = utils.remove_empties(facts)
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)
conf = re.split('\n', conf)
for command in conf:
param = re.search(
r'(.*)lldp (\w+(-?)\w+)',
command) # get the word after 'lldp'
if param:
# get the nested-dict/value for that param
key2 = re.search(r'%s(.*)' % param.group(2), command)
key2 = key2.group(1).strip()
key1 = param.group(2).replace('-', '_')
if key1 == 'portid_subtype':
key1 = 'port_id'
config[key1] = key2
elif key1 == 'tlv_select':
key2 = key2.split()
key2[0] = key2[0].replace('-', '_')
if len(key2) == 1:
if 'port' in key2[0] or 'system' in key2[0]: # nested dicts
key2 = key2[0].split('_')
# config[tlv_select][system][name]=False
config[key1][key2[0]][key2[1]] = False
else:
# config[tlv_select][dcbxp]=False
config[key1][key2[0]] = False
else:
# config[tlv_select][management_address][v6]=False
config[key1][key2[0]][key2[1]] = False
else:
config[key1] = key2 # config[reinit]=4
return utils.remove_empties(config)

@ -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)
"""
The nxos 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.nxos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs
from ansible.module_utils.network.nxos.utils.utils import get_interface_type
class Lldp_interfacesFacts(object):
""" The nxos 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 get_device_data(self, connection):
return connection.get('show running-config | section ^interface')
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 = self.get_device_data(connection)
objs = []
data = data.split('interface')
resources = []
for i in range(len(data)):
intf = data[i].split('\n')
for l in range(1, len(intf)):
if not re.search('lldp', intf[l]):
intf[l] = ''
intf = list(filter(None, intf))
intf = ''.join(i for i in intf)
resources.append(intf)
for resource in resources:
if resource: # and re.search(r'lldp', resource):
obj = self.render_config(self.generated_spec, resource)
if obj and len(obj.keys()) >= 1:
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'^ (\S+)', conf)
if match is None:
return {}
intf = match.group(1)
if get_interface_type(intf) not in ['management', 'ethernet']:
return {}
config['name'] = intf
if 'lldp receive' in conf: # for parsed state only
config['receive'] = True
if 'no lldp receive' in conf:
config['receive'] = False
if 'lldp transmit' in conf: # for parsed state only
config['transmit'] = True
if 'no lldp transmit' in conf:
config['transmit'] = False
if 'management-address' in conf:
config['tlv_set']['management_address'] = re.search(
r'management-address (\S*)', conf).group(1)
if 'vlan' in conf:
config['tlv_set']['vlan'] = re.search(
r'vlan (\S*)', conf).group(1)
return utils.remove_empties(config)

@ -1,163 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The nxos telemetry 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.nxos.argspec.telemetry.telemetry import TelemetryArgs
from ansible.module_utils.network.nxos.cmdref.telemetry.telemetry import TMS_GLOBAL, TMS_DESTGROUP, TMS_SENSORGROUP, TMS_SUBSCRIPTION
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import get_instance_data, cr_key_lookup
from ansible.module_utils.network.nxos.utils.telemetry.telemetry import normalize_data
from ansible.module_utils.network.nxos.nxos import NxosCmdRef
class TelemetryFacts(object):
""" The nxos telemetry fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = TelemetryArgs.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 telemetry
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if connection: # just for linting purposes, remove
pass
cmd_ref = {}
cmd_ref['TMS_GLOBAL'] = {}
cmd_ref['TMS_DESTGROUP'] = {}
cmd_ref['TMS_SENSORGROUP'] = {}
cmd_ref['TMS_SUBSCRIPTION'] = {}
# For fact gathering, module state should be 'present' when using
# NxosCmdRef to query state
if self._module.params.get('state'):
saved_module_state = self._module.params['state']
self._module.params['state'] = 'present'
# Get Telemetry Global Data
cmd_ref['TMS_GLOBAL']['ref'] = []
cmd_ref['TMS_GLOBAL']['ref'].append(NxosCmdRef(self._module, TMS_GLOBAL))
ref = cmd_ref['TMS_GLOBAL']['ref'][0]
ref.set_context()
ref.get_existing()
device_cache = ref.cache_existing
if device_cache is None:
device_cache_lines = []
else:
device_cache_lines = device_cache.split("\n")
# Get Telemetry Destination Group Data
cmd_ref['TMS_DESTGROUP']['ref'] = []
for line in device_cache_lines:
if re.search(r'destination-group', line):
resource_key = line.strip()
cmd_ref['TMS_DESTGROUP']['ref'].append(NxosCmdRef(self._module, TMS_DESTGROUP))
ref = cmd_ref['TMS_DESTGROUP']['ref'][-1]
ref.set_context([resource_key])
ref.get_existing(device_cache)
normalize_data(ref)
# Get Telemetry Sensorgroup Group Data
cmd_ref['TMS_SENSORGROUP']['ref'] = []
for line in device_cache_lines:
if re.search(r'sensor-group', line):
resource_key = line.strip()
cmd_ref['TMS_SENSORGROUP']['ref'].append(NxosCmdRef(self._module, TMS_SENSORGROUP))
ref = cmd_ref['TMS_SENSORGROUP']['ref'][-1]
ref.set_context([resource_key])
ref.get_existing(device_cache)
# Get Telemetry Subscription Data
cmd_ref['TMS_SUBSCRIPTION']['ref'] = []
for line in device_cache_lines:
if re.search(r'subscription', line):
resource_key = line.strip()
cmd_ref['TMS_SUBSCRIPTION']['ref'].append(NxosCmdRef(self._module, TMS_SUBSCRIPTION))
ref = cmd_ref['TMS_SUBSCRIPTION']['ref'][-1]
ref.set_context([resource_key])
ref.get_existing(device_cache)
objs = []
objs = self.render_config(self.generated_spec, cmd_ref)
facts = {'telemetry': {}}
if objs:
# params = utils.validate_config(self.argument_spec, {'config': objs})
facts['telemetry'] = objs
ansible_facts['ansible_network_resources'].update(facts)
if self._module.params.get('state'):
self._module.params['state'] = saved_module_state
return ansible_facts
def render_config(self, spec, cmd_ref):
"""
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['destination_groups'] = []
config['sensor_groups'] = []
config['subscriptions'] = []
managed_objects = ['TMS_GLOBAL', 'TMS_DESTGROUP', 'TMS_SENSORGROUP', 'TMS_SUBSCRIPTION']
# Walk the argspec and cmd_ref objects and build out config dict.
for key in config.keys():
for mo in managed_objects:
for cr in cmd_ref[mo]['ref']:
cr_keys = cr_key_lookup(key, mo)
for cr_key in cr_keys:
if cr._ref.get(cr_key) and cr._ref[cr_key].get('existing'):
if isinstance(config[key], dict):
for k in config[key].keys():
for existing_key in cr._ref[cr_key]['existing'].keys():
config[key][k] = cr._ref[cr_key]['existing'][existing_key][k]
continue
if isinstance(config[key], list):
for existing_key in cr._ref[cr_key]['existing'].keys():
data = get_instance_data(key, cr_key, cr, existing_key)
config[key].append(data)
continue
for existing_key in cr._ref[cr_key]['existing'].keys():
config[key] = cr._ref[cr_key]['existing'][existing_key]
elif cr._ref.get(cr_key):
data = get_instance_data(key, cr_key, cr, None)
if isinstance(config[key], list) and data not in config[key]:
config[key].append(data)
return utils.remove_empties(config)

@ -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)#!/usr/bin/python
"""
The nxos vlans 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
import ast
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.common.utils import parse_conf_arg, parse_conf_cmd_arg
from ansible.module_utils.network.nxos.argspec.vlans.vlans import VlansArgs
class VlansFacts(object):
""" The nxos vlans fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = VlansArgs.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, show_cmd):
"""Wrapper method for `connection.get()`
This exists solely to allow the unit test framework to mock device connection calls.
"""
return connection.get(show_cmd)
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for vlans
:param connection: the device connection
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
# **TBD**
# N7K EOL/legacy image 6.2 does not support show vlan | json output.
# If support is still required for this image then:
# - Wrapp the json calls below in a try/except
# - When excepted, use a helper method to parse the run_cfg_output,
# using the run_cfg_output data to generate compatible json data that
# can be read by normalize_table_data.
if not data:
# Use structured for most of the vlan parameter states.
# This data is consistent across the supported nxos platforms.
structured = self.get_device_data(connection, 'show vlan | json')
# Raw cli config is needed for mapped_vni, which is not included in structured.
run_cfg_output = self.get_device_data(connection, 'show running-config | section ^vlan')
# Create a single dictionary from all data sources
data = self.normalize_table_data(structured, run_cfg_output)
for vlan in data:
obj = self.render_config(self.generated_spec, vlan)
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('vlans', None)
facts = {}
if objs:
facts['vlans'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['vlans'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, spec, vlan):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param vlan: structured data vlan settings (dict) and raw cfg from device
:rtype: dictionary
:returns: The generated config
Sample inputs: test/units/modules/network/nxos/fixtures/nxos_vlans/show_vlan
"""
obj = deepcopy(spec)
obj['vlan_id'] = vlan['vlan_id']
# name: 'VLAN000x' (default name) or custom name
name = vlan['vlanshowbr-vlanname']
if name and re.match("VLAN%04d" % int(vlan['vlan_id']), name):
name = None
obj['name'] = name
# mode: 'ce-vlan' or 'fabricpath-vlan'
obj['mode'] = vlan['vlanshowinfo-vlanmode'].replace('-vlan', '')
# enabled: shutdown, noshutdown
obj['enabled'] = True if 'noshutdown' in vlan['vlanshowbr-shutstate'] else False
# state: active, suspend
obj['state'] = vlan['vlanshowbr-vlanstate']
# non-structured data
obj['mapped_vni'] = parse_conf_arg(vlan['run_cfg'], 'vn-segment')
return utils.remove_empties(obj)
def normalize_table_data(self, structured, run_cfg_output):
"""Normalize structured output and raw running-config output into
a single dict to simplify render_config usage.
This is needed because:
- The NXOS devices report most of the vlan settings within two
structured data keys: 'vlanbrief' and 'mtuinfo', but the output is
incomplete and therefore raw running-config data is also needed.
- running-config by itself is insufficient because of major differences
in the cli config syntax across platforms.
- Thus a helper method combines settings from the separate top-level keys,
and adds a 'run_cfg' key containing raw cli from the device.
"""
# device output may be string, convert to list
structured = ast.literal_eval(str(structured))
vlanbrief = []
mtuinfo = []
if 'TABLE_vlanbrief' in structured:
# SAMPLE: {"TABLE_vlanbriefid": {"ROW_vlanbriefid": {
# "vlanshowbr-vlanid": "4", "vlanshowbr-vlanid-utf": "4",
# "vlanshowbr-vlanname": "VLAN0004", "vlanshowbr-vlanstate": "active",
# "vlanshowbr-shutstate": "noshutdown"}},
vlanbrief = structured['TABLE_vlanbrief']['ROW_vlanbrief']
# SAMPLE: "TABLE_mtuinfoid": {"ROW_mtuinfoid": {
# "vlanshowinfo-vlanid": "4", "vlanshowinfo-media-type": "enet",
# "vlanshowinfo-vlanmode": "ce-vlan"}}
mtuinfo = structured['TABLE_mtuinfo']['ROW_mtuinfo']
if type(vlanbrief) is not list:
# vlanbrief is not a list when only one vlan is found.
vlanbrief = [vlanbrief]
mtuinfo = [mtuinfo]
# split out any per-vlan cli config
run_cfg_list = re.split(r'[\n^]vlan ', run_cfg_output)
# Create a list of vlan dicts where each dict contains vlanbrief,
# mtuinfo, and non-structured running-config data for one vlan.
vlans = []
for index, v in enumerate(vlanbrief):
v['vlan_id'] = v.get('vlanshowbr-vlanid-utf')
vlan = {}
vlan.update(v)
vlan.update(mtuinfo[index])
run_cfg = [i for i in run_cfg_list if "%s\n" % v['vlan_id'] in i] or ['']
vlan['run_cfg'] = run_cfg.pop()
vlans.append(vlan)
return vlans

File diff suppressed because it is too large Load Diff

@ -1,250 +0,0 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The nxos telemetry utility library
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
def get_module_params_subsection(module_params, tms_config, resource_key=None):
"""
Helper method to get a specific module_params subsection
"""
mp = {}
if tms_config == 'TMS_GLOBAL':
relevant_keys = ['certificate',
'compression',
'source_interface',
'vrf']
for key in relevant_keys:
mp[key] = module_params[key]
if tms_config == 'TMS_DESTGROUP':
mp['destination_groups'] = []
for destgrp in module_params['destination_groups']:
if destgrp['id'] == resource_key:
mp['destination_groups'].append(destgrp)
if tms_config == 'TMS_SENSORGROUP':
mp['sensor_groups'] = []
for sensor in module_params['sensor_groups']:
if sensor['id'] == resource_key:
mp['sensor_groups'].append(sensor)
if tms_config == 'TMS_SUBSCRIPTION':
mp['subscriptions'] = []
for sensor in module_params['subscriptions']:
if sensor['id'] == resource_key:
mp['subscriptions'].append(sensor)
return mp
def valiate_input(playvals, type, module):
"""
Helper method to validate playbook values for destination groups
"""
if type == 'destination_groups':
if not playvals.get('id'):
msg = "Invalid playbook value: {0}.".format(playvals)
msg += " Parameter <id> under <destination_groups> is required"
module.fail_json(msg=msg)
if playvals.get('destination') and not isinstance(playvals['destination'], dict):
msg = "Invalid playbook value: {0}.".format(playvals)
msg += " Parameter <destination> under <destination_groups> must be a dict"
module.fail_json(msg=msg)
if not playvals.get('destination') and len(playvals) > 1:
msg = "Invalid playbook value: {0}.".format(playvals)
msg += " Playbook entry contains unrecongnized parameters."
msg += " Make sure <destination> keys under <destination_groups> are specified as follows:"
msg += " destination: {ip: <ip>, port: <port>, protocol: <prot>, encoding: <enc>}}"
module.fail_json(msg=msg)
if type == 'sensor_groups':
if not playvals.get('id'):
msg = "Invalid playbook value: {0}.".format(playvals)
msg += " Parameter <id> under <sensor_groups> is required"
module.fail_json(msg=msg)
if playvals.get('path') and 'name' not in playvals['path'].keys():
msg = "Invalid playbook value: {0}.".format(playvals)
msg += " Parameter <path> under <sensor_groups> requires <name> key"
module.fail_json(msg=msg)
def get_instance_data(key, cr_key, cr, existing_key):
"""
Helper method to get instance data used to populate list structure in config
fact dictionary
"""
data = {}
if existing_key is None:
instance = None
else:
instance = cr._ref[cr_key]['existing'][existing_key]
patterns = {
'destination_groups': r"destination-group (\d+)",
'sensor_groups': r"sensor-group (\d+)",
'subscriptions': r"subscription (\d+)",
}
if key in patterns.keys():
m = re.search(patterns[key], cr._ref['_resource_key'])
instance_key = m.group(1)
data = {'id': instance_key, cr_key: instance}
# Remove None values
data = dict((k, v) for k, v in data.items() if v is not None)
return data
def cr_key_lookup(key, mo):
"""
Helper method to get instance key value for Managed Object (mo)
"""
cr_keys = [key]
if key == 'destination_groups' and mo == 'TMS_DESTGROUP':
cr_keys = ['destination']
elif key == 'sensor_groups' and mo == 'TMS_SENSORGROUP':
cr_keys = ['data_source', 'path']
elif key == 'subscriptions' and mo == 'TMS_SUBSCRIPTION':
cr_keys = ['destination_group', 'sensor_group']
return cr_keys
def normalize_data(cmd_ref):
''' Normalize playbook values and get_exisiting data '''
playval = cmd_ref._ref.get('destination').get('playval')
existing = cmd_ref._ref.get('destination').get('existing')
dest_props = ['protocol', 'encoding']
if playval:
for prop in dest_props:
for key in playval.keys():
playval[key][prop] = playval[key][prop].lower()
if existing:
for key in existing.keys():
for prop in dest_props:
existing[key][prop] = existing[key][prop].lower()
def remove_duplicate_context(cmds):
''' Helper method to remove duplicate telemetry context commands '''
if not cmds:
return cmds
feature_indices = [i for i, x in enumerate(cmds) if x == "feature telemetry"]
telemetry_indices = [i for i, x in enumerate(cmds) if x == "telemetry"]
if len(feature_indices) == 1 and len(telemetry_indices) == 1:
return cmds
if len(feature_indices) == 1 and not telemetry_indices:
return cmds
if len(telemetry_indices) == 1 and not feature_indices:
return cmds
if feature_indices and feature_indices[-1] > 1:
cmds.pop(feature_indices[-1])
return remove_duplicate_context(cmds)
if telemetry_indices and telemetry_indices[-1] > 1:
cmds.pop(telemetry_indices[-1])
return remove_duplicate_context(cmds)
def get_setval_path(module_or_path_data):
''' Build setval for path parameter based on playbook inputs
Full Command:
- path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition}
Required:
- path {name}
Optional:
- depth {depth}
- query-condition {query_condition},
- filter-condition {filter_condition}
'''
if isinstance(module_or_path_data, dict):
path = module_or_path_data
else:
path = module_or_path_data.params['config']['sensor_groups'][0].get('path')
if path is None:
return path
setval = 'path {name}'
if 'depth' in path.keys():
if path.get('depth') != 'None':
setval = setval + ' depth {depth}'
if 'query_condition' in path.keys():
if path.get('query_condition') != 'None':
setval = setval + ' query-condition {query_condition}'
if 'filter_condition' in path.keys():
if path.get('filter_condition') != 'None':
setval = setval + ' filter-condition {filter_condition}'
return setval
def remove_duplicate_commands(commands_list):
# Remove any duplicate commands.
# pylint: disable=unnecessary-lambda
return sorted(set(commands_list), key=lambda x: commands_list.index(x))
def massage_data(have_or_want):
# Massage non global into a data structure that is indexed by id and
# normalized for destination_groups, sensor_groups and subscriptions.
data = deepcopy(have_or_want)
massaged = {}
massaged['destination_groups'] = {}
massaged['sensor_groups'] = {}
massaged['subscriptions'] = {}
from pprint import pprint
for subgroup in ['destination_groups', 'sensor_groups', 'subscriptions']:
for item in data.get(subgroup, []):
id = str(item.get('id'))
if id not in massaged[subgroup].keys():
massaged[subgroup][id] = []
item.pop('id')
if not item:
item = None
else:
if item.get('destination'):
if item.get('destination').get('port'):
item['destination']['port'] = str(item['destination']['port'])
if item.get('destination').get('protocol'):
item['destination']['protocol'] = item['destination']['protocol'].lower()
if item.get('destination').get('encoding'):
item['destination']['encoding'] = item['destination']['encoding'].lower()
if item.get('path'):
for key in ['filter_condition', 'query_condition', 'depth']:
if item.get('path').get(key) == 'None':
del item['path'][key]
if item.get('path').get('depth') is not None:
item['path']['depth'] = str(item['path']['depth'])
if item.get('destination_group'):
item['destination_group'] = str(item['destination_group'])
if item.get('sensor_group'):
if item.get('sensor_group').get('id'):
item['sensor_group']['id'] = str(item['sensor_group']['id'])
if item.get('sensor_group').get('sample_interval'):
item['sensor_group']['sample_interval'] = str(item['sensor_group']['sample_interval'])
if item.get('destination_group') and item.get('sensor_group'):
item_copy = deepcopy(item)
del item_copy['sensor_group']
del item['destination_group']
massaged[subgroup][id].append(item_copy)
massaged[subgroup][id].append(item)
continue
if item.get('path') and item.get('data_source'):
item_copy = deepcopy(item)
del item_copy['data_source']
del item['path']
massaged[subgroup][id].append(item_copy)
massaged[subgroup][id].append(item)
continue
massaged[subgroup][id].append(item)
return massaged

@ -1,138 +0,0 @@
import socket
from ansible.module_utils.six import iteritems
def search_obj_in_list(name, lst, identifier):
for o in lst:
if o[identifier] == name:
return o
return None
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 validate_ipv4_addr(address):
address = address.split('/')[0]
try:
socket.inet_aton(address)
except socket.error:
return False
return address.count('.') == 3
def validate_ipv6_addr(address):
address = address.split('/')[0]
try:
socket.inet_pton(socket.AF_INET6, address)
except socket.error:
return False
return True
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('et'):
if_type = 'Ethernet'
elif name.lower().startswith('vl'):
if_type = 'Vlan'
elif name.lower().startswith('lo'):
if_type = 'loopback'
elif name.lower().startswith('po'):
if_type = 'port-channel'
elif name.lower().startswith('nv'):
if_type = 'nve'
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('ET'):
return 'ethernet'
elif interface.upper().startswith('VL'):
return 'svi'
elif interface.upper().startswith('LO'):
return 'loopback'
elif interface.upper().startswith('MG'):
return 'management'
elif interface.upper().startswith('MA'):
return 'management'
elif interface.upper().startswith('PO'):
return 'portchannel'
elif interface.upper().startswith('NV'):
return 'nve'
else:
return 'unknown'
def remove_rsvd_interfaces(interfaces):
"""Exclude reserved interfaces from user management
"""
return [i for i in interfaces if get_interface_type(i['name']) != 'management']
def vlan_range_to_list(vlans):
result = []
if vlans:
for part in vlans.split(','):
if part == 'none':
break
if '-' in part:
a, b = part.split('-')
a, b = int(a), int(b)
result.extend(range(a, b + 1))
else:
a = int(part)
result.append(a)
return numerical_sort(result)
return result
def numerical_sort(string_int_list):
"""Sorts list of integers that are digits in numerical order.
"""
as_int_list = []
for vlan in string_int_list:
as_int_list.append(int(vlan))
as_int_list.sort()
return as_int_list

@ -1,764 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 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: nxos_interface
extends_documentation_fragment: nxos
version_added: "2.1"
short_description: Manages physical attributes of interfaces.
description:
- Manages physical attributes of interfaces of NX-OS switches.
deprecated:
removed_in: '2.13'
alternative: nxos_interfaces
why: Updated modules released with more functionality
author:
- Jason Edelman (@jedelman8)
- Trishna Guha (@trishnaguha)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- This module is also used to create logical interfaces such as
svis and loopbacks.
- Be cautious of platform specific idiosyncrasies. For example,
when you default a loopback interface, the admin state toggles
on certain versions of NX-OS.
- The M(nxos_overlay_global) C(anycast_gateway_mac) attribute must be
set before setting the C(fabric_forwarding_anycast_gateway) property.
options:
name:
description:
- Full name of interface, i.e. Ethernet1/1, port-channel10.
required: true
aliases: [interface]
interface_type:
description:
- Interface type to be unconfigured from the device.
choices: ['loopback', 'portchannel', 'svi', 'nve']
version_added: 2.2
speed:
description:
- Interface link speed. Applicable for ethernet interface only.
version_added: 2.5
admin_state:
description:
- Administrative state of the interface.
default: up
choices: ['up','down']
description:
description:
- Interface description.
mode:
description:
- Manage Layer 2 or Layer 3 state of the interface.
This option is supported for ethernet and portchannel interface.
Applicable for ethernet and portchannel interface only.
choices: ['layer2','layer3']
mtu:
description:
- MTU for a specific interface. Must be an even number between 576 and 9216.
Applicable for ethernet interface only.
version_added: 2.5
ip_forward:
description:
- Enable/Disable ip forward feature on SVIs.
choices: ['enable','disable']
version_added: 2.2
fabric_forwarding_anycast_gateway:
description:
- Associate SVI with anycast gateway under VLAN configuration mode.
Applicable for SVI interface only.
type: bool
version_added: 2.2
duplex:
description:
- Interface link status. Applicable for ethernet interface only.
default: auto
choices: ['full', 'half', 'auto']
version_added: 2.5
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)
version_added: 2.5
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)
version_added: 2.5
neighbors:
description:
- Check the operational state of given interface C(name) for LLDP neighbor.
- The following suboptions are available. This is state check parameter only.
suboptions:
host:
description:
- "LLDP neighbor host for given interface C(name)."
port:
description:
- "LLDP neighbor port to which given interface C(name) is connected."
version_added: 2.5
aggregate:
description: List of Interfaces definitions.
version_added: 2.5
state:
description:
- Specify desired state of the resource.
default: present
choices: ['present','absent','default']
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state arguments.
default: 10
"""
EXAMPLES = """
- name: Ensure an interface is a Layer 3 port and that it has the proper description
nxos_interface:
name: Ethernet1/1
description: 'Configured by Ansible'
mode: layer3
- name: Admin down an interface
nxos_interface:
name: Ethernet2/1
admin_state: down
- name: Remove all loopback interfaces
nxos_interface:
name: loopback
state: absent
- name: Remove all logical interfaces
nxos_interface:
interface_type: "{{ item }} "
state: absent
loop:
- loopback
- portchannel
- svi
- nve
- name: Admin up all loopback interfaces
nxos_interface:
name: loopback 0-1023
admin_state: up
- name: Admin down all loopback interfaces
nxos_interface:
name: loopback 0-1023
admin_state: down
- name: Check neighbors intent arguments
nxos_interface:
name: Ethernet2/3
neighbors:
- port: Ethernet2/3
host: abc.mycompany.com
- name: Add interface using aggregate
nxos_interface:
aggregate:
- { name: Ethernet0/1, mtu: 256, description: test-interface-1 }
- { name: Ethernet0/2, mtu: 516, description: test-interface-2 }
duplex: full
speed: 100
state: present
- name: Delete interface using aggregate
nxos_interface:
aggregate:
- name: Loopback9
- name: Loopback10
state: absent
- name: Check intent arguments
nxos_interface:
name: Ethernet0/2
state: up
tx_rate: ge(0)
rx_rate: le(0)
"""
RETURN = """
commands:
description: command list sent to the device
returned: always
type: list
sample:
- interface Ethernet2/3
- mtu 1500
- speed 10
"""
import re
import time
from copy import deepcopy
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, normalize_interface
from ansible.module_utils.network.nxos.nxos import get_interface_type
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
def execute_show_command(command, module):
if 'show run' not in command:
output = 'json'
else:
output = 'text'
cmds = [{
'command': command,
'output': output,
}]
body = run_commands(module, cmds, check_rc=False)
if body and "Invalid" in body[0]:
return []
else:
return body
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
return None
def get_interfaces_dict(module):
"""Gets all active interfaces on a given switch
"""
try:
body = execute_show_command('show interface', module)[0]
except IndexError:
return {}
interfaces = {
'ethernet': [],
'svi': [],
'loopback': [],
'management': [],
'portchannel': [],
'nve': [],
'unknown': []
}
if body:
interface_list = body['TABLE_interface']['ROW_interface']
for index in interface_list:
intf = index['interface']
intf_type = get_interface_type(intf)
interfaces[intf_type].append(intf)
return interfaces
def get_vlan_interface_attributes(name, intf_type, module):
""" Returns dictionary that has two k/v pairs:
admin_state & description if not an svi, returns None
"""
command = 'show run interface {0} all'.format(name)
try:
body = execute_show_command(command, module)[0]
except (IndexError, TypeError):
return None
if body:
command_list = body.split('\n')
desc = None
admin_state = 'down'
for each in command_list:
if 'description' in each:
desc = each.lstrip().split("description")[1].lstrip()
elif 'no shutdown' in each:
admin_state = 'up'
return dict(description=desc, admin_state=admin_state)
else:
return None
def get_interface_type_removed_cmds(interfaces):
commands = []
for interface in interfaces:
if interface != 'Vlan1':
commands.append('no interface {0}'.format(interface))
return commands
def get_admin_state(admin_state):
command = ''
if admin_state == 'up':
command = 'no shutdown'
elif admin_state == 'down':
command = 'shutdown'
return command
def is_default_interface(name, module):
"""Checks to see if interface exists and if it is a default config
"""
command = 'show run interface {0}'.format(name)
try:
body = execute_show_command(command, module)[0]
except (IndexError, TypeError):
body = ''
if body:
raw_list = body.split('\n')
found = False
for line in raw_list:
if line.startswith('interface'):
found = True
if found and line and not line.startswith('interface'):
return False
return True
else:
return 'DNE'
def add_command_to_interface(interface, cmd, commands):
if interface not in commands:
commands.append(interface)
commands.append(cmd)
def map_obj_to_commands(updates, module):
commands = list()
commands2 = list()
want, have = updates
args = ('speed', 'description', 'duplex', 'mtu')
for w in want:
name = w['name']
mode = w['mode']
ip_forward = w['ip_forward']
fabric_forwarding_anycast_gateway = w['fabric_forwarding_anycast_gateway']
admin_state = w['admin_state']
state = w['state']
interface_type = w['interface_type']
del w['state']
if name:
w['interface_type'] = None
if interface_type:
obj_in_have = {}
if state in ('present', 'default'):
module.fail_json(msg='The interface_type param can be used only with state absent.')
else:
obj_in_have = search_obj_in_list(name, have)
is_default = is_default_interface(name, module)
if name:
interface = 'interface ' + name
if state == 'absent':
if obj_in_have:
commands.append('no interface {0}'.format(name))
elif interface_type and not obj_in_have:
intfs = get_interfaces_dict(module)[interface_type]
cmds = get_interface_type_removed_cmds(intfs)
commands.extend(cmds)
elif state == 'present':
if obj_in_have:
# Don't run switchport command for loopback and svi interfaces
if get_interface_type(name) in ('ethernet', 'portchannel'):
if mode == 'layer2' and mode != obj_in_have.get('mode'):
add_command_to_interface(interface, 'switchport', commands)
elif mode == 'layer3' and mode != obj_in_have.get('mode'):
add_command_to_interface(interface, 'no switchport', commands)
if admin_state == 'up' and admin_state != obj_in_have.get('admin_state'):
add_command_to_interface(interface, 'no shutdown', commands)
elif admin_state == 'down' and admin_state != obj_in_have.get('admin_state'):
add_command_to_interface(interface, 'shutdown', commands)
if ip_forward == 'enable' and ip_forward != obj_in_have.get('ip_forward'):
add_command_to_interface(interface, 'ip forward', commands)
elif ip_forward == 'disable' and ip_forward != obj_in_have.get('ip forward'):
add_command_to_interface(interface, 'no ip forward', commands)
if (fabric_forwarding_anycast_gateway is True and
obj_in_have.get('fabric_forwarding_anycast_gateway') is False):
add_command_to_interface(interface, 'fabric forwarding mode anycast-gateway', commands)
elif (fabric_forwarding_anycast_gateway is False and
obj_in_have.get('fabric_forwarding_anycast_gateway') is True):
add_command_to_interface(interface, 'no fabric forwarding mode anycast-gateway', commands)
for item in args:
candidate = w.get(item)
if candidate and candidate != obj_in_have.get(item):
cmd = item + ' ' + str(candidate)
add_command_to_interface(interface, cmd, commands)
if name and get_interface_type(name) == 'ethernet':
if mode != obj_in_have.get('mode'):
admin_state = w.get('admin_state') or obj_in_have.get('admin_state')
if admin_state:
c1 = 'interface {0}'.format(normalize_interface(w['name']))
c2 = get_admin_state(admin_state)
commands2.append(c1)
commands2.append(c2)
else:
commands.append(interface)
# Don't run switchport command for loopback and svi interfaces
if get_interface_type(name) in ('ethernet', 'portchannel'):
if mode == 'layer2':
commands.append('switchport')
elif mode == 'layer3':
commands.append('no switchport')
if admin_state == 'up':
commands.append('no shutdown')
elif admin_state == 'down':
commands.append('shutdown')
if ip_forward == 'enable':
commands.append('ip forward')
elif ip_forward == 'disable':
commands.append('no ip forward')
if fabric_forwarding_anycast_gateway is True:
commands.append('fabric forwarding mode anycast-gateway')
elif fabric_forwarding_anycast_gateway is False:
commands.append('no fabric forwarding mode anycast-gateway')
for item in args:
candidate = w.get(item)
if candidate:
commands.append(item + ' ' + str(candidate))
elif state == 'default':
if is_default is False:
commands.append('default interface {0}'.format(name))
elif is_default == 'DNE':
module.exit_json(msg='interface you are trying to default does not exist')
return commands, commands2
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
d = item.copy()
name = d['name']
d['name'] = normalize_interface(name)
obj.append(d)
else:
obj.append({
'name': normalize_interface(module.params['name']),
'description': module.params['description'],
'speed': module.params['speed'],
'mode': module.params['mode'],
'mtu': module.params['mtu'],
'duplex': module.params['duplex'],
'ip_forward': module.params['ip_forward'],
'fabric_forwarding_anycast_gateway': module.params['fabric_forwarding_anycast_gateway'],
'admin_state': module.params['admin_state'],
'state': module.params['state'],
'interface_type': module.params['interface_type'],
'tx_rate': module.params['tx_rate'],
'rx_rate': module.params['rx_rate'],
'neighbors': module.params['neighbors']
})
return obj
def map_config_to_obj(want, module):
objs = list()
for w in want:
obj = dict(name=None, description=None, admin_state=None, speed=None,
mtu=None, mode=None, duplex=None, interface_type=None,
ip_forward=None, fabric_forwarding_anycast_gateway=None)
if not w['name']:
return obj
command = 'show interface {0}'.format(w['name'])
try:
body = execute_show_command(command, module)[0]
except IndexError:
return list()
if body:
try:
interface_table = body['TABLE_interface']['ROW_interface']
except (KeyError, TypeError):
return list()
if interface_table:
if interface_table.get('eth_mode') == 'fex-fabric':
module.fail_json(msg='nxos_interface does not support interfaces with mode "fex-fabric"')
intf_type = get_interface_type(w['name'])
if intf_type in ['portchannel', 'ethernet']:
mode = interface_table.get('eth_mode')
if mode in ('access', 'trunk', 'dot1q-tunnel'):
obj['mode'] = 'layer2'
elif mode in ('routed', 'layer3'):
obj['mode'] = 'layer3'
else:
obj['mode'] = 'layer3'
if intf_type == 'ethernet':
obj['name'] = normalize_interface(interface_table.get('interface'))
obj['admin_state'] = interface_table.get('admin_state')
obj['description'] = interface_table.get('desc')
obj['mtu'] = interface_table.get('eth_mtu')
obj['duplex'] = interface_table.get('eth_duplex')
command = 'show run interface {0}'.format(obj['name'])
body = execute_show_command(command, module)[0]
speed_match = re.search(r'speed (\d+)', body)
if speed_match is None:
obj['speed'] = 'auto'
else:
obj['speed'] = speed_match.group(1)
duplex_match = re.search(r'duplex (\S+)', body)
if duplex_match is None:
obj['duplex'] = 'auto'
else:
obj['duplex'] = duplex_match.group(1)
if 'ip forward' in body:
obj['ip_forward'] = 'enable'
else:
obj['ip_forward'] = 'disable'
elif intf_type == 'svi':
obj['name'] = normalize_interface(interface_table.get('interface'))
attributes = get_vlan_interface_attributes(obj['name'], intf_type, module)
obj['admin_state'] = str(attributes.get('admin_state',
'nxapibug'))
obj['description'] = str(attributes.get('description',
'nxapi_bug'))
obj['mtu'] = interface_table.get('svi_mtu')
command = 'show run interface {0}'.format(obj['name'])
body = execute_show_command(command, module)[0]
if 'ip forward' in body:
obj['ip_forward'] = 'enable'
else:
obj['ip_forward'] = 'disable'
if 'fabric forwarding mode anycast-gateway' in body:
obj['fabric_forwarding_anycast_gateway'] = True
else:
obj['fabric_forwarding_anycast_gateway'] = False
elif intf_type in ('loopback', 'management', 'nve'):
obj['name'] = normalize_interface(interface_table.get('interface'))
obj['admin_state'] = interface_table.get('admin_state')
if obj['admin_state'] is None and intf_type == 'loopback':
# Some platforms don't have the 'admin_state' key.
# For loopback interfaces it's safe to use the
# 'state' key instead.
obj['admin_state'] = interface_table.get('state')
obj['description'] = interface_table.get('desc')
elif intf_type == 'portchannel':
obj['name'] = normalize_interface(interface_table.get('interface'))
obj['admin_state'] = interface_table.get('admin_state')
obj['description'] = interface_table.get('desc')
obj['mtu'] = interface_table.get('eth_mtu')
if obj['admin_state'] is None:
# Some nxos platforms do not have the 'admin_state' key.
# Use the 'state_rsn_desc' key instead to determine the
# admin state of the interface.
state_description = interface_table.get('state_rsn_desc')
if state_description == 'Administratively down':
obj['admin_state'] = 'down'
elif state_description is not None:
obj['admin_state'] = 'up'
objs.append(obj)
return objs
def check_declarative_intent_params(module, want):
failed_conditions = []
have_neighbors = None
for w in want:
if w['interface_type']:
continue
want_tx_rate = w.get('tx_rate')
want_rx_rate = w.get('rx_rate')
want_neighbors = w.get('neighbors')
if not (want_tx_rate or want_rx_rate or want_neighbors):
continue
time.sleep(module.params['delay'])
cmd = [{'command': 'show interface {0}'.format(w['name']), 'output': 'text'}]
try:
out = run_commands(module, cmd, check_rc=False)[0]
except (AttributeError, IndexError, TypeError):
out = ''
if want_tx_rate:
match = re.search(r'output rate (\d+)', 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'input rate (\d+)', 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 want_neighbors:
have_host = []
have_port = []
if have_neighbors is None:
cmd = [{'command': 'show lldp neighbors interface {0} detail'.format(w['name']), 'output': 'text'}]
output = run_commands(module, cmd, check_rc=False)
if output:
have_neighbors = output[0]
else:
have_neighbors = ''
if have_neighbors and 'Total entries displayed: 0' not in have_neighbors:
for line in have_neighbors.strip().split('\n'):
if line.startswith('Port Description'):
have_port.append(line.split(': ')[1])
if line.startswith('System Name'):
have_host.append(line.split(': ')[1])
for item in want_neighbors:
host = item.get('host')
port = item.get('port')
if host and host not in have_host:
failed_conditions.append('host ' + host)
if port and port not in have_port:
failed_conditions.append('port ' + port)
return failed_conditions
def main():
""" main entry point for module execution
"""
neighbors_spec = dict(
host=dict(),
port=dict()
)
element_spec = dict(
name=dict(aliases=['interface']),
admin_state=dict(default='up', choices=['up', 'down']),
description=dict(),
speed=dict(),
mode=dict(choices=['layer2', 'layer3']),
mtu=dict(),
duplex=dict(choices=['full', 'half', 'auto']),
interface_type=dict(choices=['loopback', 'portchannel', 'svi', 'nve']),
ip_forward=dict(choices=['enable', 'disable']),
fabric_forwarding_anycast_gateway=dict(type='bool'),
tx_rate=dict(),
rx_rate=dict(),
neighbors=dict(type='list', elements='dict', options=neighbors_spec),
delay=dict(default=10, type='int'),
state=dict(choices=['absent', 'present', 'default'], default='present')
)
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,
mutually_exclusive=[['name', 'interface_type']])
)
argument_spec.update(element_spec)
argument_spec.update(nxos_argument_spec)
required_one_of = [['name', 'aggregate', 'interface_type']]
mutually_exclusive = [['name', 'aggregate'],
['name', 'interface_type']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module)
have = map_config_to_obj(want, module)
commands = []
commands1, commands2 = map_obj_to_commands((want, have), module)
commands.extend(commands1)
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
# if the mode changes from L2 to L3, the admin state
# seems to change after the API call, so adding a second API
# call to ensure it's in the desired state.
if commands2:
load_config(module, commands2)
commands.extend(commands2)
commands = [cmd for cmd in commands if cmd != 'configure']
result['commands'] = commands
if result['changed']:
failed_conditions = check_declarative_intent_params(module, want)
if failed_conditions:
msg = 'One or more conditional statements have not been satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,578 +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': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_l2_interface
extends_documentation_fragment: nxos
version_added: "2.5"
short_description: Manage Layer-2 interface on Cisco NXOS devices.
description:
- This module provides declarative management of Layer-2 interface on
Cisco NXOS devices.
deprecated:
removed_in: '2.13'
alternative: nxos_l2_interfaces
why: Updated modules released with more functionality
author:
- Trishna Guha (@trishnaguha)
notes:
- Tested against NXOSv 7.0(3)I5(1).
options:
name:
description:
- Full name of the interface excluding any logical
unit number, i.e. Ethernet1/1.
required: true
aliases: ['interface']
mode:
description:
- Mode in which interface needs to be configured.
choices: ['access','trunk']
access_vlan:
description:
- Configure given VLAN in access port.
If C(mode=access), used as the access VLAN ID.
native_vlan:
description:
- Native VLAN to be configured in trunk port.
If C(mode=trunk), used as the trunk native VLAN ID.
trunk_vlans:
description:
- List of VLANs to be configured in trunk port.
If C(mode=trunk), used as the VLAN range to ADD or REMOVE
from the trunk.
aliases: ['trunk_add_vlans']
trunk_allowed_vlans:
description:
- List of allowed VLANs in a given trunk port.
If C(mode=trunk), these are the only VLANs that will be
configured on the trunk, i.e. "2-10,15".
aggregate:
description:
- List of Layer-2 interface definitions.
state:
description:
- Manage the state of the Layer-2 Interface configuration.
default: present
choices: ['present','absent', 'unconfigured']
"""
EXAMPLES = """
- name: Ensure Eth1/5 is in its default l2 interface state
nxos_l2_interface:
name: Ethernet1/5
state: unconfigured
- name: Ensure Eth1/5 is configured for access vlan 20
nxos_l2_interface:
name: Ethernet1/5
mode: access
access_vlan: 20
- name: Ensure Eth1/5 only has vlans 5-10 as trunk vlans
nxos_l2_interface:
name: Ethernet1/5
mode: trunk
native_vlan: 10
trunk_vlans: 5-10
- name: Ensure eth1/5 is a trunk port and ensure 2-50 are being tagged (doesn't mean others aren't also being tagged)
nxos_l2_interface:
name: Ethernet1/5
mode: trunk
native_vlan: 10
trunk_vlans: 2-50
- name: Ensure these VLANs are not being tagged on the trunk
nxos_l2_interface:
name: Ethernet1/5
mode: trunk
trunk_vlans: 51-4094
state: absent
- name: Aggregate Configure interfaces for access_vlan with aggregate
nxos_l2_interface:
aggregate:
- { name: "Ethernet1/2", access_vlan: 6 }
- { name: "Ethernet1/7", access_vlan: 15 }
mode: access
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- interface eth1/5
- switchport access vlan 20
"""
from copy import deepcopy
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, get_interface_type
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
def get_interface_mode(name, module):
"""Gets current mode of interface: layer2 or layer3
Args:
device (Device): This is the device object of an NX-API enabled device
using the Device class within device.py
interface (string): full name of interface, i.e. Ethernet1/1,
loopback10, port-channel20, vlan20
Returns:
str: 'layer2' or 'layer3'
"""
command = 'show interface {0} | json'.format(name)
intf_type = get_interface_type(name)
mode = 'unknown'
interface_table = {}
try:
body = run_commands(module, [command])[0]
interface_table = body['TABLE_interface']['ROW_interface']
except (KeyError, AttributeError, IndexError):
return mode
if interface_table:
# HACK FOR NOW
if intf_type in ['ethernet', 'portchannel']:
mode = str(interface_table.get('eth_mode', 'layer3'))
if mode in ['access', 'trunk']:
mode = 'layer2'
if mode == 'routed':
mode = 'layer3'
elif intf_type == 'loopback' or intf_type == 'svi':
mode = 'layer3'
return mode
def interface_is_portchannel(name, module):
"""Checks to see if an interface is part of portchannel bundle
Args:
interface (str): full name of interface, i.e. Ethernet1/1
Returns:
True/False based on if interface is a member of a portchannel bundle
"""
intf_type = get_interface_type(name)
if intf_type == 'ethernet':
command = 'show interface {0} | json'.format(name)
try:
body = run_commands(module, [command])[0]
interface_table = body['TABLE_interface']['ROW_interface']
except (KeyError, AttributeError, IndexError):
interface_table = None
if interface_table:
state = interface_table.get('eth_bundle')
if state:
return True
else:
return False
return False
def get_switchport(port, module):
"""Gets current config of L2 switchport
Args:
device (Device): This is the device object of an NX-API enabled device
using the Device class within device.py
port (str): full name of interface, i.e. Ethernet1/1
Returns:
dictionary with k/v pairs for L2 vlan config
"""
command = 'show interface {0} switchport | json'.format(port)
try:
body = run_commands(module, [command])[0]
sp_table = body['TABLE_interface']['ROW_interface']
except (KeyError, AttributeError, IndexError):
sp_table = None
if sp_table:
key_map = {
"interface": "name",
"oper_mode": "mode",
"switchport": "switchport",
"access_vlan": "access_vlan",
"access_vlan_name": "access_vlan_name",
"native_vlan": "native_vlan",
"native_vlan_name": "native_vlan_name",
"trunk_vlans": "trunk_vlans"
}
sp = apply_key_map(key_map, sp_table)
return sp
else:
return {}
def remove_switchport_config_commands(name, existing, proposed, module):
mode = proposed.get('mode')
commands = []
command = None
if mode == 'access':
av_check = existing.get('access_vlan') == proposed.get('access_vlan')
if av_check:
command = 'no switchport access vlan {0}'.format(existing.get('access_vlan'))
commands.append(command)
elif mode == 'trunk':
existing_vlans = existing.get('trunk_vlans_list')
proposed_vlans = proposed.get('trunk_vlans_list')
vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
if vlans_to_remove:
proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
commands.append(command)
native_check = existing.get('native_vlan') == proposed.get('native_vlan')
if native_check and proposed.get('native_vlan'):
command = 'no switchport trunk native vlan {0}'.format(existing.get('native_vlan'))
commands.append(command)
if commands:
commands.insert(0, 'interface ' + name)
return commands
def get_switchport_config_commands(name, existing, proposed, module):
"""Gets commands required to config a given switchport interface
"""
proposed_mode = proposed.get('mode')
existing_mode = existing.get('mode')
commands = []
command = None
if proposed_mode != existing_mode:
if proposed_mode == 'trunk':
command = 'switchport mode trunk'
elif proposed_mode == 'access':
command = 'switchport mode access'
if command:
commands.append(command)
if proposed_mode == 'access':
av_check = str(existing.get('access_vlan')) == str(proposed.get('access_vlan'))
if not av_check:
command = 'switchport access vlan {0}'.format(proposed.get('access_vlan'))
commands.append(command)
elif proposed_mode == 'trunk':
tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list')
if not tv_check:
if proposed.get('allowed'):
command = 'switchport trunk allowed vlan {0}'.format(proposed.get('trunk_allowed_vlans'))
commands.append(command)
else:
existing_vlans = existing.get('trunk_vlans_list')
proposed_vlans = proposed.get('trunk_vlans_list')
vlans_to_add = set(proposed_vlans).difference(existing_vlans)
if vlans_to_add:
command = 'switchport trunk allowed vlan add {0}'.format(proposed.get('trunk_vlans'))
commands.append(command)
native_check = str(existing.get('native_vlan')) == str(proposed.get('native_vlan'))
if not native_check and proposed.get('native_vlan'):
command = 'switchport trunk native vlan {0}'.format(proposed.get('native_vlan'))
commands.append(command)
if commands:
commands.insert(0, 'interface ' + name)
return commands
def is_switchport_default(existing):
"""Determines if switchport has a default config based on mode
Args:
existing (dict): existing switchport configuration from Ansible mod
Returns:
boolean: True if switchport has OOB Layer 2 config, i.e.
vlan 1 and trunk all and mode is access
"""
c1 = str(existing['access_vlan']) == '1'
c2 = str(existing['native_vlan']) == '1'
c3 = existing['trunk_vlans'] == '1-4094'
c4 = existing['mode'] == 'access'
default = c1 and c2 and c3 and c4
return default
def default_switchport_config(name):
commands = []
commands.append('interface ' + name)
commands.append('switchport mode access')
commands.append('switch access vlan 1')
commands.append('switchport trunk native vlan 1')
commands.append('switchport trunk allowed vlan all')
return commands
def vlan_range_to_list(vlans):
result = []
if vlans:
for part in vlans.split(','):
if part == 'none':
break
if '-' in part:
a, b = part.split('-')
a, b = int(a), int(b)
result.extend(range(a, b + 1))
else:
a = int(part)
result.append(a)
return numerical_sort(result)
return result
def get_list_of_vlans(module):
command = 'show vlan | json'
vlan_list = []
try:
body = run_commands(module, [command])[0]
vlan_table = body['TABLE_vlanbrief']['ROW_vlanbrief']
except (KeyError, AttributeError, IndexError):
return []
if isinstance(vlan_table, list):
for vlan in vlan_table:
vlan_list.append(str(vlan['vlanshowbr-vlanid-utf']))
else:
vlan_list.append('1')
return vlan_list
def numerical_sort(string_int_list):
"""Sorts list of strings/integers that are digits in numerical order.
"""
as_int_list = []
as_str_list = []
for vlan in string_int_list:
as_int_list.append(int(vlan))
as_int_list.sort()
for vlan in as_int_list:
as_str_list.append(str(vlan))
return as_str_list
def apply_key_map(key_map, table):
new_dict = {}
for key, value in table.items():
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = str(value)
return new_dict
def apply_value_map(value_map, resource):
for key, value in value_map.items():
resource[key] = value[resource.get(key)]
return resource
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
d = item.copy()
obj.append(d)
else:
obj.append({
'name': module.params['name'],
'mode': module.params['mode'],
'access_vlan': module.params['access_vlan'],
'native_vlan': module.params['native_vlan'],
'trunk_vlans': module.params['trunk_vlans'],
'trunk_allowed_vlans': module.params['trunk_allowed_vlans'],
'state': module.params['state']
})
return obj
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(type='str', aliases=['interface']),
mode=dict(choices=['access', 'trunk']),
access_vlan=dict(type='str'),
native_vlan=dict(type='str'),
trunk_vlans=dict(type='str', aliases=['trunk_add_vlans']),
trunk_allowed_vlans=dict(type='str'),
state=dict(choices=['absent', 'present', 'unconfigured'], default='present')
)
aggregate_spec = deepcopy(element_spec)
# 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(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[['access_vlan', 'trunk_vlans'],
['access_vlan', 'native_vlan'],
['access_vlan', 'trunk_allowed_vlans']],
supports_check_mode=True)
warnings = list()
commands = []
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module)
for w in want:
name = w['name']
mode = w['mode']
access_vlan = w['access_vlan']
state = w['state']
trunk_vlans = w['trunk_vlans']
native_vlan = w['native_vlan']
trunk_allowed_vlans = w['trunk_allowed_vlans']
args = dict(name=name, mode=mode, access_vlan=access_vlan,
native_vlan=native_vlan, trunk_vlans=trunk_vlans,
trunk_allowed_vlans=trunk_allowed_vlans)
proposed = dict((k, v) for k, v in args.items() if v is not None)
name = name.lower()
if mode == 'access' and state == 'present' and not access_vlan:
module.fail_json(msg='access_vlan param is required when mode=access && state=present')
if mode == 'trunk' and access_vlan:
module.fail_json(msg='access_vlan param not supported when using mode=trunk')
current_mode = get_interface_mode(name, module)
# Current mode will return layer3, layer2, or unknown
if current_mode == 'unknown' or current_mode == 'layer3':
module.fail_json(msg='Ensure interface is configured to be a L2'
'\nport first before using this module. You can use'
'\nthe nxos_interface module for this.')
if interface_is_portchannel(name, module):
module.fail_json(msg='Cannot change L2 config on physical '
'\nport because it is in a portchannel. '
'\nYou should update the portchannel config.')
# existing will never be null for Eth intfs as there is always a default
existing = get_switchport(name, module)
# Safeguard check
# If there isn't an existing, something is wrong per previous comment
if not existing:
module.fail_json(msg='Make sure you are using the FULL interface name')
if trunk_vlans or trunk_allowed_vlans:
if trunk_vlans:
trunk_vlans_list = vlan_range_to_list(trunk_vlans)
elif trunk_allowed_vlans:
trunk_vlans_list = vlan_range_to_list(trunk_allowed_vlans)
proposed['allowed'] = True
existing_trunks_list = vlan_range_to_list((existing['trunk_vlans']))
existing['trunk_vlans_list'] = existing_trunks_list
proposed['trunk_vlans_list'] = trunk_vlans_list
current_vlans = get_list_of_vlans(module)
if state == 'present':
if access_vlan and access_vlan not in current_vlans:
module.fail_json(msg='You are trying to configure a VLAN'
' on an interface that\ndoes not exist on the '
' switch yet!', vlan=access_vlan)
elif native_vlan and native_vlan not in current_vlans:
module.fail_json(msg='You are trying to configure a VLAN'
' on an interface that\ndoes not exist on the '
' switch yet!', vlan=native_vlan)
else:
command = get_switchport_config_commands(name, existing, proposed, module)
commands.append(command)
elif state == 'unconfigured':
is_default = is_switchport_default(existing)
if not is_default:
command = default_switchport_config(name)
commands.append(command)
elif state == 'absent':
command = remove_switchport_config_commands(name, existing, proposed, module)
commands.append(command)
if trunk_vlans or trunk_allowed_vlans:
existing.pop('trunk_vlans_list')
proposed.pop('trunk_vlans_list')
cmds = flatten_list(commands)
if cmds:
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
result['changed'] = True
load_config(module, cmds)
if 'configure' in cmds:
cmds.pop(0)
result['commands'] = cmds
result['warnings'] = warnings
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,251 +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': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_l3_interface
version_added: "2.5"
author: "Trishna Guha (@trishnaguha)"
short_description: Manage L3 interfaces on Cisco NXOS network devices
description:
- This module provides declarative management of L3 interfaces
on Cisco NXOS network devices.
deprecated:
removed_in: '2.13'
alternative: nxos_l3_interfaces
why: Updated modules released with more functionality
notes:
- Tested against NXOSv 7.0(3)I5(1).
options:
name:
description:
- Name of the L3 interface.
ipv4:
description:
- IPv4 of the L3 interface.
ipv6:
description:
- IPv6 of the L3 interface.
aggregate:
description: List of L3 interfaces definitions.
state:
description:
- State of the L3 interface configuration.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: nxos
"""
EXAMPLES = """
- name: Set interface IPv4 address
nxos_l3_interface:
name: Ethernet2/3
ipv4: 192.168.0.1/24
- name: Remove interface IPv4 address
nxos_l3_interface:
name: Ethernet2/3
state: absent
- name: Set IP addresses on aggregate
nxos_l3_interface:
aggregate:
- { name: Ethernet2/1, ipv4: 192.168.2.10/24 }
- { name: Ethernet2/5, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
- name: Remove IP addresses on aggregate
nxos_l3_interface:
aggregate:
- { name: Ethernet2/1, ipv4: 192.168.2.10/24 }
- { name: Ethernet2/5, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- interface ethernet2/3
- no switchport
- ip address 192.168.22.1/24
- ipv6 address "fd5d:12c9:2201:1::1/64"
- no ip address 192.168.22.1/24
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, normalize_interface
def search_obj_in_list(name, lst):
for o in lst:
if o['name'] == name:
return o
def map_obj_to_commands(updates, module, warnings):
commands = list()
want, have = updates
for w in want:
name = w['name']
ipv4 = w['ipv4']
ipv6 = w['ipv6']
state = w['state']
del w['state']
obj_in_have = search_obj_in_list(name, have)
if not obj_in_have:
warnings.append('Unknown interface {0}'.format(name))
elif state == 'absent':
command = []
if obj_in_have['name'] == name:
if ipv4 and ipv4 == obj_in_have['ipv4']:
command.append('no ip address {0}'.format(ipv4))
if ipv6 and ipv6 in obj_in_have['ipv6']:
command.append('no ipv6 address {0}'.format(ipv6))
if command:
command.append('exit')
command.insert(0, 'interface {0}'.format(name))
commands.extend(command)
elif state == 'present':
command = []
if obj_in_have['name'] == name:
if ipv4 and ipv4 != obj_in_have['ipv4']:
command.append('ip address {0}'.format(ipv4))
if ipv6 and ipv6 not in obj_in_have['ipv6']:
command.append('ipv6 address {0}'.format(ipv6))
if command:
command.append('exit')
command.insert(0, 'interface {0}'.format(name))
elif not ipv4 and not ipv6:
command.append('interface {0}'.format(name))
commands.extend(command)
return commands
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
d = item.copy()
name = d['name']
d['name'] = normalize_interface(name)
obj.append(d)
else:
obj.append({
'name': normalize_interface(module.params['name']),
'ipv4': module.params['ipv4'],
'ipv6': module.params['ipv6'],
'state': module.params['state']
})
return obj
def map_config_to_obj(want, module):
objs = list()
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
for w in want:
parents = ['interface {0}'.format(w['name'])]
config = netcfg.get_section(parents)
obj = dict(name=None, ipv4=None, ipv6=[])
if config:
match_name = re.findall(r'interface (\S+)', config, re.M)
if match_name:
obj['name'] = normalize_interface(match_name[0])
match_ipv4 = re.findall(r'ip address (\S+)', config, re.M)
if match_ipv4:
obj['ipv4'] = match_ipv4[0]
match_ipv6 = re.findall(r'ipv6 address (\S+)', config, re.M)
if match_ipv6:
obj['ipv6'] = match_ipv6
objs.append(obj)
return objs
def main():
""" main entry point for module execution
"""
element_spec = dict(
name=dict(),
ipv4=dict(),
ipv6=dict(),
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)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec),
)
argument_spec.update(element_spec)
argument_spec.update(nxos_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)
warnings = list()
result = {'changed': False}
want = map_params_to_obj(module)
have = map_config_to_obj(want, module)
commands = map_obj_to_commands((want, have), module, warnings)
result['commands'] = commands
if warnings:
result['warnings'] = warnings
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,443 +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': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_linkagg
extends_documentation_fragment: nxos
version_added: "2.5"
short_description: Manage link aggregation groups on Cisco NXOS devices.
description:
- This module provides declarative management of link aggregation groups
on Cisco NXOS devices.
deprecated:
removed_in: '2.13'
alternative: nxos_lag_interfaces
why: Updated modules released with more functionality.
author:
- Trishna Guha (@trishnaguha)
notes:
- Tested against NXOSv 7.0(3)I5(1).
- C(state=absent) removes the portchannel config and interface if it
already exists. If members to be removed are not explicitly
passed, all existing members (if any), are removed.
- Members must be a list.
- LACP needs to be enabled first if active/passive modes are used.
options:
group:
description:
- Channel-group number for the port-channel
Link aggregation group.
required: true
type: str
mode:
description:
- Mode for the link aggregation group.
choices: [ active, 'on', passive ]
default: 'on'
type: str
min_links:
description:
- Minimum number of ports required up
before bringing up the link aggregation group.
type: int
members:
description:
- List of interfaces that will be managed in the link aggregation group.
type: list
force:
description:
- When true it forces link aggregation group members to match what
is declared in the members param. This can be used to remove members.
type: bool
default: 'no'
aggregate:
description: List of link aggregation definitions.
type: list
state:
description:
- State of the link aggregation group.
default: present
choices: ['present','absent']
type: str
purge:
description:
- Purge links not defined in the I(aggregate) parameter.
type: bool
default: 'no'
"""
EXAMPLES = """
- name: create link aggregation group
nxos_linkagg:
group: 99
state: present
- name: delete link aggregation group
nxos_linkagg:
group: 99
state: absent
- name: set link aggregation group to members
nxos_linkagg:
group: 10
min_links: 3
mode: active
members:
- Ethernet1/2
- Ethernet1/4
- name: remove link aggregation group from Ethernet1/2
nxos_linkagg:
group: 10
min_links: 3
mode: active
members:
- Ethernet1/4
- name: Create aggregate of linkagg definitions
nxos_linkagg:
aggregate:
- { group: 3 }
- { group: 100, min_links: 3 }
- name: Remove aggregate of linkagg definitions
nxos_linkagg:
aggregate:
- { group: 3 }
- { group: 100, min_links: 3 }
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- interface port-channel 30
- lacp min-links 5
- interface Ethernet2/1
- channel-group 30 mode active
- no interface port-channel 30
"""
import re
from copy import deepcopy
from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.network.nxos.nxos import normalize_interface
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
def search_obj_in_list(group, lst):
for o in lst:
if o['group'] == group:
return o
def get_diff(w, obj):
c = deepcopy(w)
o = deepcopy(obj)
if o['group'] == c['group'] and o.get('members') == c.get('members'):
if 'members' in o:
del o['members']
if 'members' in c:
del c['members']
diff_dict = dict(set(c.items()) - set(o.items()))
return diff_dict
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
force = module.params['force']
for w in want:
group = w['group']
mode = w['mode']
min_links = w['min_links']
members = w.get('members') or []
state = w['state']
del w['state']
obj_in_have = search_obj_in_list(group, have)
if state == 'absent':
if obj_in_have:
members_to_remove = list(set(obj_in_have['members']) - set(members))
if members_to_remove:
for m in members_to_remove:
commands.append('interface {0}'.format(m))
commands.append('no channel-group {0}'.format(obj_in_have['group']))
commands.append('exit')
commands.append('no interface port-channel {0}'.format(group))
elif state == 'present':
if not obj_in_have:
commands.append('interface port-channel {0}'.format(group))
if min_links != 'None':
commands.append('lacp min-links {0}'.format(min_links))
commands.append('exit')
if members:
for m in members:
commands.append('interface {0}'.format(m))
if force:
commands.append('channel-group {0} force mode {1}'.format(group, mode))
else:
commands.append('channel-group {0} mode {1}'.format(group, mode))
else:
if members:
if not obj_in_have['members']:
for m in members:
commands.append('interface port-channel {0}'.format(group))
commands.append('exit')
commands.append('interface {0}'.format(m))
if force:
commands.append('channel-group {0} force mode {1}'.format(group, mode))
else:
commands.append('channel-group {0} mode {1}'.format(group, mode))
elif set(members) != set(obj_in_have['members']):
missing_members = list(set(members) - set(obj_in_have['members']))
for m in missing_members:
commands.append('interface port-channel {0}'.format(group))
commands.append('exit')
commands.append('interface {0}'.format(m))
if force:
commands.append('channel-group {0} force mode {1}'.format(group, mode))
else:
commands.append('channel-group {0} mode {1}'.format(group, mode))
superfluous_members = list(set(obj_in_have['members']) - set(members))
for m in superfluous_members:
commands.append('interface port-channel {0}'.format(group))
commands.append('exit')
commands.append('interface {0}'.format(m))
commands.append('no channel-group {0}'.format(group))
else:
diff = get_diff(w, obj_in_have)
if diff and 'mode' in diff:
mode = diff['mode']
for i in members:
commands.append('interface {0}'.format(i))
if force:
commands.append('channel-group {0} force mode {1}'.format(group, mode))
else:
commands.append('channel-group {0} mode {1}'.format(group, mode))
if purge:
for h in have:
obj_in_want = search_obj_in_list(h['group'], want)
if not obj_in_want:
commands.append('no interface port-channel {0}'.format(h['group']))
return commands
def map_params_to_obj(module):
obj = []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
d = item.copy()
d['group'] = str(d['group'])
d['min_links'] = str(d['min_links'])
if d['members']:
d['members'] = [normalize_interface(i) for i in d['members']]
obj.append(d)
else:
members = None
if module.params['members']:
members = [normalize_interface(i) for i in module.params['members']]
obj.append({
'group': str(module.params['group']),
'mode': module.params['mode'],
'min_links': str(module.params['min_links']),
'members': members,
'state': module.params['state']
})
return obj
def parse_min_links(module, group):
min_links = None
flags = ['| section interface.port-channel{0}'.format(group)]
config = get_config(module, flags=flags)
match = re.search(r'lacp min-links (\S+)', config, re.M)
if match:
min_links = match.group(1)
return min_links
def parse_mode(module, m):
mode = None
flags = ['| section interface.{0}'.format(m)]
config = get_config(module, flags=flags)
match = re.search(r'channel-group [0-9]+ (force )?mode (\S+)', config, re.M)
if match:
mode = match.group(2)
return mode
def get_members(channel):
members = []
if 'TABLE_member' in channel.keys():
interfaces = channel['TABLE_member']['ROW_member']
else:
return list()
if isinstance(interfaces, dict):
members.append(normalize_interface(interfaces.get('port')))
elif isinstance(interfaces, list):
for i in interfaces:
members.append(normalize_interface(i.get('port')))
return members
def parse_members(output, group):
channels = output['TABLE_channel']['ROW_channel']
if isinstance(channels, list):
for channel in channels:
if channel['group'] == group:
members = get_members(channel)
elif isinstance(channels, dict):
if channels['group'] == group:
members = get_members(channels)
else:
return list()
return members
def parse_channel_options(module, output, channel):
obj = {}
group = channel['group']
obj['group'] = str(group)
obj['min_links'] = parse_min_links(module, group)
members = parse_members(output, group)
obj['members'] = members
for m in members:
obj['mode'] = parse_mode(module, m)
return obj
def map_config_to_obj(module):
objs = list()
output = run_commands(module, ['show port-channel summary | json'])[0]
if not output:
return list()
try:
channels = output['TABLE_channel']['ROW_channel']
except (TypeError, KeyError):
return objs
if channels:
if isinstance(channels, list):
for channel in channels:
obj = parse_channel_options(module, output, channel)
objs.append(obj)
elif isinstance(channels, dict):
obj = parse_channel_options(module, output, channels)
objs.append(obj)
return objs
def main():
""" main entry point for module execution
"""
element_spec = dict(
group=dict(type='str'),
mode=dict(required=False, choices=['on', 'active', 'passive'], default='on', type='str'),
min_links=dict(required=False, default=None, type='int'),
members=dict(required=False, default=None, type='list'),
force=dict(required=False, default=False, type='bool'),
state=dict(required=False, choices=['absent', 'present'], default='present')
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['group'] = 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),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
argument_spec.update(nxos_argument_spec)
required_one_of = [['group', 'aggregate']]
mutually_exclusive = [['group', 'aggregate']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands((want, have), module)
result['commands'] = commands
if commands:
if not module.check_mode:
resp = load_config(module, commands, True)
if resp:
for item in resp:
if item:
if isinstance(item, dict):
err_str = item['clierror']
else:
err_str = item
if 'cannot add' in err_str.lower():
module.fail_json(msg=err_str)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,712 +0,0 @@
#!/usr/bin/python
#
# 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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_vlan
extends_documentation_fragment: nxos
version_added: "2.1"
short_description: Manages VLAN resources and attributes.
description:
- Manages VLAN configurations on NX-OS switches.
deprecated:
removed_in: '2.13'
alternative: nxos_vlans
why: Updated modules released with more functionality
author: Jason Edelman (@jedelman8)
options:
vlan_id:
description:
- Single VLAN ID.
type: int
vlan_range:
description:
- Range of VLANs such as 2-10 or 2,5,10-15, etc.
type: str
name:
description:
- Name of VLAN or keyword 'default'.
type: str
interfaces:
description:
- List of interfaces that should be associated to the VLAN or keyword 'default'.
version_added: "2.5"
type: list
associated_interfaces:
description:
- This is a intent option and checks the operational state of the for given vlan C(name)
for associated interfaces. If the value in the C(associated_interfaces) does not match with
the operational state of vlan interfaces on device it will result in failure.
version_added: "2.5"
type: list
vlan_state:
description:
- Manage the vlan operational state of the VLAN
default: active
choices: ['active','suspend']
type: str
admin_state:
description:
- Manage the VLAN administrative state of the VLAN equivalent
to shut/no shut in VLAN config mode.
default: up
choices: ['up','down']
type: str
mapped_vni:
description:
- The Virtual Network Identifier (VNI) ID that is mapped to the
VLAN. Valid values are integer and keyword 'default'. Range 4096-16773119.
version_added: "2.2"
type: str
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','absent']
type: str
mode:
description:
- Set VLAN mode to classical ethernet or fabricpath.
This is a valid option for Nexus 5000 and 7000 series.
choices: ['ce','fabricpath']
default: 'ce'
type: str
version_added: "2.4"
aggregate:
description: List of VLANs definitions.
version_added: "2.5"
type: list
purge:
description:
- Purge VLANs not defined in the I(aggregate) parameter.
This parameter can be used without aggregate as well.
- Removal of Vlan 1 is not allowed and will be ignored by purge.
type: bool
default: 'no'
delay:
description:
- Time in seconds to wait before checking for the operational state on remote
device. This wait is applicable for operational state arguments.
default: 10
type: int
'''
EXAMPLES = '''
- name: Ensure a range of VLANs are not present on the switch
nxos_vlan:
vlan_range: "2-10,20,50,55-60,100-150"
state: absent
- name: Ensure VLAN 50 exists with the name WEB and is in the shutdown state
nxos_vlan:
vlan_id: 50
admin_state: down
name: WEB
- name: Ensure VLAN is NOT on the device
nxos_vlan:
vlan_id: 50
state: absent
- name: Add interfaces to VLAN and check intent (config + intent)
nxos_vlan:
vlan_id: 100
interfaces:
- Ethernet2/1
- Ethernet2/5
associated_interfaces:
- Ethernet2/1
- Ethernet2/5
- name: Check interfaces assigned to VLAN
nxos_vlan:
vlan_id: 100
associated_interfaces:
- Ethernet2/1
- Ethernet2/5
- name: Create aggregate of vlans
nxos_vlan:
aggregate:
- { vlan_id: 4000, mode: ce }
- { vlan_id: 4001, name: vlan-4001 }
- name: purge vlans - removes all other vlans except the ones mentioned in aggregate)
nxos_vlan:
aggregate:
- vlan_id: 1
- vlan_id: 4001
purge: yes
'''
RETURN = '''
commands:
description: Set of command strings to send to the remote device
returned: always
type: list
sample: ["vlan 20", "vlan 55", "vn-segment 5000"]
'''
import re
import time
from copy import deepcopy
from ansible.module_utils.network.nxos.nxos import get_capabilities
from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands
from ansible.module_utils.network.nxos.nxos import normalize_interface, nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
from ansible.module_utils.network.common.utils import remove_default_spec
def search_obj_in_list(vlan_id, lst):
for o in lst:
if o['vlan_id'] == vlan_id:
return o
def get_diff(w, obj):
c = deepcopy(w)
entries = ('interfaces', 'associated_interfaces', 'delay', 'vlan_range')
for key in entries:
if key in c:
del c[key]
o = deepcopy(obj)
del o['interfaces']
if o['vlan_id'] == w['vlan_id']:
diff_dict = dict(set(c.items()) - set(o.items()))
return diff_dict
def is_default_name(obj, vlan_id):
cname = obj['name']
if ('VLAN' in cname):
vid = int(cname[4:])
if vid == int(vlan_id):
return True
return False
def map_obj_to_commands(updates, module):
commands = list()
purge = module.params['purge']
want, have = updates
info = get_capabilities(module).get('device_info')
os_platform = info.get('network_os_platform')
for w in want:
vlan_id = w['vlan_id']
name = w['name']
interfaces = w.get('interfaces') or []
mapped_vni = w['mapped_vni']
mode = w['mode']
vlan_state = w['vlan_state']
admin_state = w['admin_state']
state = w['state']
del w['state']
obj_in_have = search_obj_in_list(vlan_id, have) or {}
if not re.match('N[567]', os_platform) or (not obj_in_have.get('mode') and mode == 'ce'):
mode = w['mode'] = None
if state == 'absent':
if obj_in_have:
commands.append('no vlan {0}'.format(vlan_id))
elif state == 'present':
if not obj_in_have:
commands.append('vlan {0}'.format(vlan_id))
if name and name != 'default':
commands.append('name {0}'.format(name))
if mode:
commands.append('mode {0}'.format(mode))
if vlan_state:
commands.append('state {0}'.format(vlan_state))
if mapped_vni != 'None' and mapped_vni != 'default':
commands.append('vn-segment {0}'.format(mapped_vni))
if admin_state == 'up':
commands.append('no shutdown')
if admin_state == 'down':
commands.append('shutdown')
commands.append('exit')
if interfaces and interfaces[0] != 'default':
for i in interfaces:
commands.append('interface {0}'.format(i))
commands.append('switchport')
commands.append('switchport mode access')
commands.append('switchport access vlan {0}'.format(vlan_id))
else:
diff = get_diff(w, obj_in_have)
if diff:
commands.append('vlan {0}'.format(vlan_id))
for key, value in diff.items():
if key == 'name':
if name != 'default':
if name is not None:
commands.append('name {0}'.format(value))
else:
if not is_default_name(obj_in_have, vlan_id):
commands.append('no name')
if key == 'vlan_state' and value:
commands.append('state {0}'.format(value))
if key == 'mapped_vni':
if value == 'default':
if obj_in_have['mapped_vni'] != 'None':
commands.append('no vn-segment')
elif value != 'None':
commands.append('vn-segment {0}'.format(value))
if key == 'admin_state':
if value == 'up':
commands.append('no shutdown')
elif value == 'down':
commands.append('shutdown')
if key == 'mode' and value:
commands.append('mode {0}'.format(value))
if len(commands) > 1:
commands.append('exit')
else:
del commands[:]
if interfaces and interfaces[0] != 'default':
if not obj_in_have['interfaces']:
for i in interfaces:
commands.append('vlan {0}'.format(vlan_id))
commands.append('exit')
commands.append('interface {0}'.format(i))
commands.append('switchport')
commands.append('switchport mode access')
commands.append('switchport access vlan {0}'.format(vlan_id))
elif set(interfaces) != set(obj_in_have['interfaces']):
missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces']))
for i in missing_interfaces:
commands.append('vlan {0}'.format(vlan_id))
commands.append('exit')
commands.append('interface {0}'.format(i))
commands.append('switchport')
commands.append('switchport mode access')
commands.append('switchport access vlan {0}'.format(vlan_id))
superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces))
for i in superfluous_interfaces:
commands.append('vlan {0}'.format(vlan_id))
commands.append('exit')
commands.append('interface {0}'.format(i))
commands.append('switchport')
commands.append('switchport mode access')
commands.append('no switchport access vlan {0}'.format(vlan_id))
elif interfaces and interfaces[0] == 'default':
if obj_in_have['interfaces']:
for i in obj_in_have['interfaces']:
commands.append('vlan {0}'.format(vlan_id))
commands.append('exit')
commands.append('interface {0}'.format(i))
commands.append('switchport')
commands.append('switchport mode access')
commands.append('no switchport access vlan {0}'.format(vlan_id))
if purge:
for h in have:
if h['vlan_id'] == '1':
module.warn("Deletion of vlan 1 is not allowed; purge will ignore vlan 1")
continue
obj_in_want = search_obj_in_list(h['vlan_id'], want)
if not obj_in_want:
commands.append('no vlan {0}'.format(h['vlan_id']))
return commands
def want_vlan_list(module):
result = []
vlan_range = module.params['vlan_range']
for part in vlan_range.split(','):
if part == 'none':
break
if '-' in part:
start, end = part.split('-')
start, end = int(start), int(end)
result.extend([str(i) for i in range(start, end + 1)])
else:
result.append(part)
return result
def have_vlan_list(have):
result = []
if have:
for h in have:
result.append(str(h.get('vlan_id')))
return result
def vlan_range_commands(module, have):
commands = list()
proposed_vlans_list = want_vlan_list(module)
existing_vlans_list = have_vlan_list(have)
if module.params['state'] == 'absent':
vlans = set(proposed_vlans_list).intersection(existing_vlans_list)
for vlan in vlans:
commands.append('no vlan {0}'.format(vlan))
elif module.params['state'] == 'present':
vlans = set(proposed_vlans_list).difference(existing_vlans_list)
for vlan in vlans:
commands.append('vlan {0}'.format(vlan))
return commands
def normalize(interfaces):
normalized = None
if interfaces:
normalized = [normalize_interface(i) for i in interfaces]
return normalized
def map_params_to_obj(module):
obj = []
if module.params['vlan_range']:
return []
aggregate = module.params.get('aggregate')
if aggregate:
for item in aggregate:
for key in item:
if item.get(key) is None:
item[key] = module.params[key]
d = item.copy()
d['vlan_id'] = str(d['vlan_id'])
d['mapped_vni'] = str(d['mapped_vni'])
d['interfaces'] = normalize(d['interfaces'])
d['associated_interfaces'] = normalize(d['associated_interfaces'])
obj.append(d)
else:
interfaces = normalize(module.params['interfaces'])
associated_interfaces = normalize(module.params['associated_interfaces'])
obj.append({
'vlan_id': str(module.params['vlan_id']),
'name': module.params['name'],
'interfaces': interfaces,
'vlan_state': module.params['vlan_state'],
'mapped_vni': str(module.params['mapped_vni']),
'state': module.params['state'],
'admin_state': module.params['admin_state'],
'mode': module.params['mode'],
'associated_interfaces': associated_interfaces
})
return obj
def parse_admin_state(vlan):
shutstate = vlan.get('vlanshowbr-shutstate')
if shutstate == 'noshutdown':
return 'up'
elif shutstate == 'shutdown':
return 'down'
def parse_mode(config):
mode = None
if config:
match = re.search(r'mode (\S+)', config)
if match:
mode = match.group(1)
return mode
def parse_vni(config):
vni = None
if config:
match = re.search(r'vn-segment (\S+)', config)
if match:
vni = match.group(1)
return str(vni)
def get_vlan_int(interfaces):
vlan_int = []
for i in interfaces.split(','):
if 'eth' in i.lower() and '-' in i:
int_range = i.split('-')
stop = int((int_range)[1])
start = int(int_range[0].split('/')[1])
eth = int_range[0].split('/')[0]
for r in range(start, stop + 1):
vlan_int.append(eth + '/' + str(r))
else:
vlan_int.append(i)
return vlan_int
def parse_interfaces(module, vlan):
vlan_int = []
interfaces = vlan.get('vlanshowplist-ifidx')
if interfaces:
if isinstance(interfaces, list):
interfaces_list = [i.strip() for i in interfaces]
interfaces_str = ','.join(interfaces_list)
vlan_int = get_vlan_int(interfaces_str)
else:
vlan_int = get_vlan_int(interfaces)
return vlan_int
def parse_vlan_config(netcfg, vlan_id):
parents = ['vlan {0}'.format(vlan_id)]
config = netcfg.get_section(parents)
return config
def parse_vlan_options(module, netcfg, output, vlan):
obj = {}
vlan_id = vlan['vlanshowbr-vlanid-utf']
config = parse_vlan_config(netcfg, vlan_id)
obj['vlan_id'] = str(vlan_id)
obj['name'] = vlan.get('vlanshowbr-vlanname')
obj['vlan_state'] = vlan.get('vlanshowbr-vlanstate')
obj['admin_state'] = parse_admin_state(vlan)
obj['mode'] = parse_mode(config)
obj['mapped_vni'] = parse_vni(config)
obj['interfaces'] = parse_interfaces(module, vlan)
return obj
def parse_vlan_non_structured(module, netcfg, vlans):
objs = list()
for vlan in vlans:
vlan_match = re.search(r'(\d+)', vlan, re.M)
if vlan_match:
obj = {}
vlan_id = vlan_match.group(1)
obj['vlan_id'] = str(vlan_id)
name_match = re.search(r'{0}\s*(\S+)'.format(vlan_id), vlan, re.M)
if name_match:
name = name_match.group(1)
obj['name'] = name
state_match = re.search(r'{0}\s*{1}\s*(\S+)'.format(vlan_id, re.escape(name)), vlan, re.M)
if state_match:
vlan_state_match = state_match.group(1)
if vlan_state_match == 'suspended':
vlan_state = 'suspend'
admin_state = 'up'
elif vlan_state_match == 'sus/lshut':
vlan_state = 'suspend'
admin_state = 'down'
if vlan_state_match == 'active':
vlan_state = 'active'
admin_state = 'up'
if vlan_state_match == 'act/lshut':
vlan_state = 'active'
admin_state = 'down'
obj['vlan_state'] = vlan_state
obj['admin_state'] = admin_state
vlan = ','.join(vlan.splitlines())
interfaces = list()
intfs_match = re.search(r'{0}\s*{1}\s*{2}\s*(.*)'.format(vlan_id, re.escape(name), vlan_state_match),
vlan, re.M)
if intfs_match:
intfs = intfs_match.group(1)
intfs = intfs.split()
for i in intfs:
intf = normalize_interface(i.strip(','))
interfaces.append(intf)
if interfaces:
obj['interfaces'] = interfaces
else:
obj['interfaces'] = None
config = parse_vlan_config(netcfg, vlan_id)
obj['mode'] = parse_mode(config)
obj['mapped_vni'] = parse_vni(config)
objs.append(obj)
return objs
def map_config_to_obj(module):
objs = list()
output = None
command = ['show vlan brief | json']
output = run_commands(module, command, check_rc='retry_json')[0]
if output:
netcfg = CustomNetworkConfig(indent=2,
contents=get_config(module, flags=['all']))
if isinstance(output, dict):
vlans = None
try:
vlans = output['TABLE_vlanbriefxbrief']['ROW_vlanbriefxbrief']
except KeyError:
return objs
if vlans:
if isinstance(vlans, list):
for vlan in vlans:
obj = parse_vlan_options(module, netcfg, output, vlan)
objs.append(obj)
elif isinstance(vlans, dict):
obj = parse_vlan_options(module, netcfg, output, vlans)
objs.append(obj)
else:
vlans = list()
splitted_line = re.split(r'\n(\d+)|\n{2}', output.strip())
for line in splitted_line:
if not line:
continue
if len(line) > 0:
line = line.strip()
if line[0].isdigit():
match = re.search(r'^(\d+)$', line, re.M)
if match:
v = match.group(1)
pos1 = splitted_line.index(v)
pos2 = pos1 + 1
vlaninfo = ''.join(splitted_line[pos1:pos2 + 1])
vlans.append(vlaninfo)
if vlans:
objs = parse_vlan_non_structured(module, netcfg, vlans)
else:
return objs
return objs
def check_declarative_intent_params(want, module, result):
have = None
is_delay = False
for w in want:
if w.get('associated_interfaces') is None:
continue
if result['changed'] and not is_delay:
time.sleep(module.params['delay'])
is_delay = True
if have is None:
have = map_config_to_obj(module)
for i in w['associated_interfaces']:
obj_in_have = search_obj_in_list(w['vlan_id'], have)
if obj_in_have and 'interfaces' in obj_in_have and i not in obj_in_have['interfaces']:
module.fail_json(msg="Interface %s not configured on vlan %s" % (i, w['vlan_id']))
def main():
""" main entry point for module execution
"""
element_spec = dict(
vlan_id=dict(required=False, type='int'),
vlan_range=dict(required=False),
name=dict(required=False),
interfaces=dict(type='list'),
associated_interfaces=dict(type='list'),
vlan_state=dict(choices=['active', 'suspend'], required=False, default='active'),
mapped_vni=dict(required=False),
delay=dict(default=10, type='int'),
state=dict(choices=['present', 'absent'], default='present', required=False),
admin_state=dict(choices=['up', 'down'], required=False, default='up'),
mode=dict(default='ce', choices=['ce', 'fabricpath']),
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['vlan_id'] = 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),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
argument_spec.update(nxos_argument_spec)
required_one_of = [['vlan_id', 'aggregate', 'vlan_range']]
mutually_exclusive = [['vlan_id', 'aggregate'],
['vlan_range', 'name'],
['vlan_id', 'vlan_range']]
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
have = map_config_to_obj(module)
want = map_params_to_obj(module)
if module.params['vlan_range']:
commands = vlan_range_commands(module, have)
result['commands'] = commands
else:
commands = map_obj_to_commands((want, have), module)
result['commands'] = commands
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
if want:
check_declarative_intent_params(want, module, result)
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,319 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_aaa_server
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages AAA server global configuration.
description:
- Manages AAA server global configuration
author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- The server_type parameter is always required.
- If encrypt_type is not supplied, the global AAA server key will be
stored as encrypted (type 7).
- Changes to the global AAA server key with encrypt_type=0
are not idempotent.
- state=default will set the supplied parameters to their default values.
The parameters that you want to default must also be set to default.
If global_key=default, the global key will be removed.
options:
server_type:
description:
- The server type is either radius or tacacs.
required: true
choices: ['radius', 'tacacs']
global_key:
description:
- Global AAA shared secret or keyword 'default'.
encrypt_type:
description:
- The state of encryption applied to the entered global key.
O clear text, 7 encrypted. Type-6 encryption is not supported.
choices: ['0', '7']
deadtime:
description:
- Duration for which a non-reachable AAA server is skipped,
in minutes or keyword 'default.
Range is 1-1440. Device default is 0.
server_timeout:
description:
- Global AAA server timeout period, in seconds or keyword 'default.
Range is 1-60. Device default is 5.
directed_request:
description:
- Enables direct authentication requests to AAA server or keyword 'default'
Device default is disabled.
choices: ['enabled', 'disabled']
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','default']
'''
EXAMPLES = '''
# Radius Server Basic settings
- name: "Radius Server Basic settings"
nxos_aaa_server:
server_type: radius
server_timeout: 9
deadtime: 20
directed_request: enabled
# Tacacs Server Basic settings
- name: "Tacacs Server Basic settings"
nxos_aaa_server:
server_type: tacacs
server_timeout: 8
deadtime: 19
directed_request: disabled
# Setting Global Key
- name: "AAA Server Global Key"
nxos_aaa_server:
server_type: radius
global_key: test_key
'''
RETURN = '''
commands:
description: command sent to the device
returned: always
type: list
sample: ["radius-server deadtime 22", "radius-server timeout 11",
"radius-server directed-request"]
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
PARAM_TO_DEFAULT_KEYMAP = {
'server_timeout': '5',
'deadtime': '0',
'directed_request': 'disabled',
}
def execute_show_command(command, module):
command = {
'command': command,
'output': 'text',
}
return run_commands(module, command)
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def get_aaa_server_info(server_type, module):
aaa_server_info = {}
server_command = 'show {0}-server'.format(server_type)
request_command = 'show {0}-server directed-request'.format(server_type)
global_key_command = 'show run | sec {0}'.format(server_type)
aaa_regex = r'.*{0}-server\skey\s\d\s+(?P<key>\S+).*'.format(server_type)
server_body = execute_show_command(server_command, module)[0]
split_server = server_body.splitlines()
for line in split_server:
if line.startswith('timeout'):
aaa_server_info['server_timeout'] = line.split(':')[1]
elif line.startswith('deadtime'):
aaa_server_info['deadtime'] = line.split(':')[1]
request_body = execute_show_command(request_command, module)[0]
if bool(request_body):
aaa_server_info['directed_request'] = request_body.replace('\n', '')
else:
aaa_server_info['directed_request'] = 'disabled'
key_body = execute_show_command(global_key_command, module)[0]
try:
match_global_key = re.match(aaa_regex, key_body, re.DOTALL)
group_key = match_global_key.groupdict()
aaa_server_info['global_key'] = group_key["key"].replace('\"', '')
except (AttributeError, TypeError):
aaa_server_info['global_key'] = None
return aaa_server_info
def config_aaa_server(params, server_type):
cmds = []
deadtime = params.get('deadtime')
server_timeout = params.get('server_timeout')
directed_request = params.get('directed_request')
encrypt_type = params.get('encrypt_type', '7')
global_key = params.get('global_key')
if deadtime is not None:
cmds.append('{0}-server deadtime {1}'.format(server_type, deadtime))
if server_timeout is not None:
cmds.append('{0}-server timeout {1}'.format(server_type, server_timeout))
if directed_request is not None:
if directed_request == 'enabled':
cmds.append('{0}-server directed-request'.format(server_type))
elif directed_request == 'disabled':
cmds.append('no {0}-server directed-request'.format(server_type))
if global_key is not None:
cmds.append('{0}-server key {1} {2}'.format(server_type, encrypt_type,
global_key))
return cmds
def default_aaa_server(existing, params, server_type):
cmds = []
deadtime = params.get('deadtime')
server_timeout = params.get('server_timeout')
directed_request = params.get('directed_request')
global_key = params.get('global_key')
existing_key = existing.get('global_key')
if deadtime is not None and existing.get('deadtime') != PARAM_TO_DEFAULT_KEYMAP['deadtime']:
cmds.append('no {0}-server deadtime 1'.format(server_type))
if server_timeout is not None and existing.get('server_timeout') != PARAM_TO_DEFAULT_KEYMAP['server_timeout']:
cmds.append('no {0}-server timeout 1'.format(server_type))
if directed_request is not None and existing.get('directed_request') != PARAM_TO_DEFAULT_KEYMAP['directed_request']:
cmds.append('no {0}-server directed-request'.format(server_type))
if global_key is not None and existing_key is not None:
cmds.append('no {0}-server key 7 {1}'.format(server_type, existing_key))
return cmds
def main():
argument_spec = dict(
server_type=dict(type='str', choices=['radius', 'tacacs'], required=True),
global_key=dict(type='str'),
encrypt_type=dict(type='str', choices=['0', '7']),
deadtime=dict(type='str'),
server_timeout=dict(type='str'),
directed_request=dict(type='str', choices=['enabled', 'disabled', 'default']),
state=dict(choices=['default', 'present'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
results = {'changed': False, 'commands': [], 'warnings': warnings}
server_type = module.params['server_type']
global_key = module.params['global_key']
encrypt_type = module.params['encrypt_type']
deadtime = module.params['deadtime']
server_timeout = module.params['server_timeout']
directed_request = module.params['directed_request']
state = module.params['state']
if encrypt_type and not global_key:
module.fail_json(msg='encrypt_type must be used with global_key.')
args = dict(server_type=server_type, global_key=global_key,
encrypt_type=encrypt_type, deadtime=deadtime,
server_timeout=server_timeout, directed_request=directed_request)
proposed = dict((k, v) for k, v in args.items() if v is not None)
existing = get_aaa_server_info(server_type, module)
commands = []
if state == 'present':
if deadtime:
try:
if int(deadtime) < 0 or int(deadtime) > 1440:
raise ValueError
except ValueError:
module.fail_json(
msg='deadtime must be an integer between 0 and 1440')
if server_timeout:
try:
if int(server_timeout) < 1 or int(server_timeout) > 60:
raise ValueError
except ValueError:
module.fail_json(
msg='server_timeout must be an integer between 1 and 60')
delta = dict(set(proposed.items()).difference(
existing.items()))
if delta:
command = config_aaa_server(delta, server_type)
if command:
commands.append(command)
elif state == 'default':
for key, value in proposed.items():
if key != 'server_type' and value != 'default':
module.fail_json(
msg='Parameters must be set to "default"'
'when state=default')
command = default_aaa_server(existing, proposed, server_type)
if command:
commands.append(command)
cmds = flatten_list(commands)
if cmds:
results['changed'] = True
if not module.check_mode:
load_config(module, cmds)
if 'configure' in cmds:
cmds.pop(0)
results['commands'] = cmds
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,343 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_aaa_server_host
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages AAA server host-specific configuration.
description:
- Manages AAA server host-specific configuration.
author: Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Changes to the host key (shared secret) are not idempotent for type 0.
- If C(state=absent) removes the whole host configuration.
options:
server_type:
description:
- The server type is either radius or tacacs.
required: true
choices: ['radius', 'tacacs']
address:
description:
- Address or name of the radius or tacacs host.
required: true
key:
description:
- Shared secret for the specified host or keyword 'default'.
encrypt_type:
description:
- The state of encryption applied to the entered key.
O for clear text, 7 for encrypted. Type-6 encryption is
not supported.
choices: ['0', '7']
host_timeout:
description:
- Timeout period for specified host, in seconds or keyword 'default.
Range is 1-60.
auth_port:
description:
- Alternate UDP port for RADIUS authentication or keyword 'default'.
acct_port:
description:
- Alternate UDP port for RADIUS accounting or keyword 'default'.
tacacs_port:
description:
- Alternate TCP port TACACS Server or keyword 'default'.
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# Radius Server Host Basic settings
- name: "Radius Server Host Basic settings"
nxos_aaa_server_host:
state: present
server_type: radius
address: 1.2.3.4
acct_port: 2084
host_timeout: 10
# Radius Server Host Key Configuration
- name: "Radius Server Host Key Configuration"
nxos_aaa_server_host:
state: present
server_type: radius
address: 1.2.3.4
key: hello
encrypt_type: 7
# TACACS Server Host Configuration
- name: "Tacacs Server Host Configuration"
nxos_aaa_server_host:
state: present
server_type: tacacs
tacacs_port: 89
host_timeout: 10
address: 5.6.7.8
'''
RETURN = '''
proposed:
description: k/v pairs of parameters passed into module
returned: always
type: dict
sample: {"address": "1.2.3.4", "auth_port": "2084",
"host_timeout": "10", "server_type": "radius"}
existing:
description:
- k/v pairs of existing configuration
returned: always
type: dict
sample: {}
end_state:
description: k/v pairs of configuration after module execution
returned: always
type: dict
sample: {"address": "1.2.3.4", "auth_port": "2084",
"host_timeout": "10", "server_type": "radius"}
updates:
description: command sent to the device
returned: always
type: list
sample: ["radius-server host 1.2.3.4 auth-port 2084 timeout 10"]
changed:
description: check to see if a change was made on the device
returned: always
type: bool
sample: true
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def execute_show_command(command, module):
device_info = get_capabilities(module)
network_api = device_info.get('network_api', 'nxapi')
if network_api == 'cliconf':
cmds = [command]
body = run_commands(module, cmds)
elif network_api == 'nxapi':
cmds = {'command': command, 'output': 'text'}
body = run_commands(module, cmds)
return body
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def get_aaa_host_info(module, server_type, address):
aaa_host_info = {}
command = 'show run | inc {0}-server.host.{1}'.format(server_type, address)
body = execute_show_command(command, module)[0]
if body:
try:
if 'radius' in body:
pattern = (r'\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+auth-port (\d+))?'
r'(?:\s+acct-port (\d+))?(?:\s+authentication)?'
r'(?:\s+accounting)?(?:\s+timeout (\d+))?')
match = re.search(pattern, body)
aaa_host_info['key'] = match.group(1)
if aaa_host_info['key']:
aaa_host_info['key'] = aaa_host_info['key'].replace('"', '')
aaa_host_info['encrypt_type'] = '7'
aaa_host_info['auth_port'] = match.group(2)
aaa_host_info['acct_port'] = match.group(3)
aaa_host_info['host_timeout'] = match.group(4)
elif 'tacacs' in body:
pattern = (r'\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+port (\d+))?'
r'(?:\s+timeout (\d+))?')
match = re.search(pattern, body)
aaa_host_info['key'] = match.group(1)
if aaa_host_info['key']:
aaa_host_info['key'] = aaa_host_info['key'].replace('"', '')
aaa_host_info['encrypt_type'] = '7'
aaa_host_info['tacacs_port'] = match.group(2)
aaa_host_info['host_timeout'] = match.group(3)
aaa_host_info['server_type'] = server_type
aaa_host_info['address'] = address
except TypeError:
return {}
else:
return {}
return aaa_host_info
def config_aaa_host(server_type, address, params, existing):
cmds = []
cmd_str = '{0}-server host {1}'.format(server_type, address)
cmd_no_str = 'no ' + cmd_str
key = params.get('key')
enc_type = params.get('encrypt_type', '')
defval = False
nondef = False
if key:
if key != 'default':
cmds.append(cmd_str + ' key {0} {1}'.format(enc_type, key))
else:
cmds.append(cmd_no_str + ' key 7 {0}'.format(existing.get('key')))
locdict = {'auth_port': 'auth-port', 'acct_port': 'acct-port',
'tacacs_port': 'port', 'host_timeout': 'timeout'}
# platform CLI needs the keywords in the following order
for key in ['auth_port', 'acct_port', 'tacacs_port', 'host_timeout']:
item = params.get(key)
if item:
if item != 'default':
cmd_str += ' {0} {1}'.format(locdict.get(key), item)
nondef = True
else:
cmd_no_str += ' {0} 1'.format(locdict.get(key))
defval = True
if defval:
cmds.append(cmd_no_str)
if nondef or not existing:
cmds.append(cmd_str)
return cmds
def main():
argument_spec = dict(
server_type=dict(choices=['radius', 'tacacs'], required=True),
address=dict(type='str', required=True),
key=dict(type='str'),
encrypt_type=dict(type='str', choices=['0', '7']),
host_timeout=dict(type='str'),
auth_port=dict(type='str'),
acct_port=dict(type='str'),
tacacs_port=dict(type='str'),
state=dict(choices=['absent', 'present'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
server_type = module.params['server_type']
address = module.params['address']
key = module.params['key']
encrypt_type = module.params['encrypt_type']
host_timeout = module.params['host_timeout']
auth_port = module.params['auth_port']
acct_port = module.params['acct_port']
tacacs_port = module.params['tacacs_port']
state = module.params['state']
args = dict(server_type=server_type, address=address, key=key,
encrypt_type=encrypt_type, host_timeout=host_timeout,
auth_port=auth_port, acct_port=acct_port,
tacacs_port=tacacs_port)
proposed = dict((k, v) for k, v in args.items() if v is not None)
changed = False
if encrypt_type and not key:
module.fail_json(msg='encrypt_type must be used with key')
if tacacs_port and server_type != 'tacacs':
module.fail_json(
msg='tacacs_port can only be used with server_type=tacacs')
if (auth_port or acct_port) and server_type != 'radius':
module.fail_json(msg='auth_port and acct_port can only be used'
'when server_type=radius')
existing = get_aaa_host_info(module, server_type, address)
end_state = existing
commands = []
delta = {}
if state == 'present':
if not existing:
delta = proposed
else:
for key, value in proposed.items():
if key == 'encrypt_type':
delta[key] = value
if value != existing.get(key):
if value != 'default' or existing.get(key):
delta[key] = value
command = config_aaa_host(server_type, address, delta, existing)
if command:
commands.append(command)
elif state == 'absent':
intersect = dict(
set(proposed.items()).intersection(existing.items()))
if intersect.get('address') and intersect.get('server_type'):
command = 'no {0}-server host {1}'.format(
intersect.get('server_type'), intersect.get('address'))
commands.append(command)
cmds = flatten_list(commands)
if cmds:
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
changed = True
load_config(module, cmds)
end_state = get_aaa_host_info(module, server_type, address)
results = {}
results['proposed'] = proposed
results['existing'] = existing
results['updates'] = cmds
results['changed'] = changed
results['warnings'] = warnings
results['end_state'] = end_state
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,536 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_acl
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages access list entries for ACLs.
description:
- Manages access list entries for ACLs.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state=absent) removes the ACE if it exists.
- C(state=delete_acl) deletes the ACL if it exists.
- For idempotency, use port numbers for the src/dest port
params like I(src_port1) and names for the well defined protocols
for the I(proto) param.
- Although this module is idempotent in that if the ace as presented in
the task is identical to the one on the switch, no changes will be made.
If there is any difference, what is in Ansible will be pushed (configured
options will be overridden). This is to improve security, but at the
same time remember an ACE is removed, then re-added, so if there is a
change, the new ACE will be exactly what parameters you are sending to
the module.
options:
seq:
description:
- Sequence number of the entry (ACE).
name:
description:
- Case sensitive name of the access list (ACL).
required: true
action:
description:
- Action of the ACE.
choices: ['permit', 'deny', 'remark']
remark:
description:
- If action is set to remark, this is the description.
proto:
description:
- Port number or protocol (as supported by the switch).
src:
description:
- Source ip and mask using IP/MASK notation and
supports keyword 'any'.
src_port_op:
description:
- Source port operands such as eq, neq, gt, lt, range.
choices: ['any', 'eq', 'gt', 'lt', 'neq', 'range']
src_port1:
description:
- Port/protocol and also first (lower) port when using range
operand.
src_port2:
description:
- Second (end) port when using range operand.
dest:
description:
- Destination ip and mask using IP/MASK notation and supports the
keyword 'any'.
dest_port_op:
description:
- Destination port operands such as eq, neq, gt, lt, range.
choices: ['any', 'eq', 'gt', 'lt', 'neq', 'range']
dest_port1:
description:
- Port/protocol and also first (lower) port when using range
operand.
dest_port2:
description:
- Second (end) port when using range operand.
log:
description:
- Log matches against this entry.
choices: ['enable']
urg:
description:
- Match on the URG bit.
choices: ['enable']
ack:
description:
- Match on the ACK bit.
choices: ['enable']
psh:
description:
- Match on the PSH bit.
choices: ['enable']
rst:
description:
- Match on the RST bit.
choices: ['enable']
syn:
description:
- Match on the SYN bit.
choices: ['enable']
fin:
description:
- Match on the FIN bit.
choices: ['enable']
established:
description:
- Match established connections.
choices: ['enable']
fragments:
description:
- Check non-initial fragments.
choices: ['enable']
time_range:
description:
- Name of time-range to apply.
precedence:
description:
- Match packets with given precedence.
choices: ['critical', 'flash', 'flash-override', 'immediate',
'internet', 'network', 'priority', 'routine']
dscp:
description:
- Match packets with given dscp value.
choices: ['af11', 'af12', 'af13', 'af21', 'af22', 'af23','af31','af32',
'af33', 'af41', 'af42', 'af43', 'cs1', 'cs2', 'cs3', 'cs4',
'cs5', 'cs6', 'cs7', 'default', 'ef']
state:
description:
- Specify desired state of the resource.
default: present
choices: ['present','absent','delete_acl']
'''
EXAMPLES = '''
# configure ACL ANSIBLE
- nxos_acl:
name: ANSIBLE
seq: 10
action: permit
proto: tcp
src: 192.0.2.1/24
dest: any
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["ip access-list ANSIBLE", "10 permit tcp 192.0.2.1/24 any"]
'''
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def execute_show_command(command, module, check_rc=True):
command += ' | json'
cmds = [command]
body = run_commands(module, cmds, check_rc=check_rc)
return body
def get_acl(module, acl_name, seq_number):
command = 'show ip access-list'
new_acl = []
saveme = {}
acl_body = {}
body = execute_show_command(command, module, check_rc=False)
if 'Structured output unsupported' in repr(body):
# Some older versions raise 501 and return a string when no ACLs exist
return {}, []
if body and body[0]:
all_acl_body = body[0]['TABLE_ip_ipv6_mac']['ROW_ip_ipv6_mac']
else:
# no access-lists configured on the device
return {}, []
if isinstance(all_acl_body, dict):
# Only 1 ACL configured.
if all_acl_body.get('acl_name') == acl_name:
acl_body = all_acl_body
else:
for acl in all_acl_body:
if acl.get('acl_name') == acl_name:
acl_body = acl
break
try:
acl_entries = acl_body['TABLE_seqno']['ROW_seqno']
acl_name = acl_body.get('acl_name')
except KeyError: # could be raised if no ACEs are configured for an ACL
return {}, [{'acl': 'no_entries'}]
if isinstance(acl_entries, dict):
acl_entries = [acl_entries]
for each in acl_entries:
temp = {}
options = {}
remark = each.get('remark')
temp['name'] = acl_name
temp['seq'] = str(each.get('seqno'))
if remark:
temp['remark'] = remark
temp['action'] = 'remark'
else:
temp['action'] = each.get('permitdeny')
temp['proto'] = str(each.get('proto', each.get('proto_str', each.get('ip'))))
temp['src'] = each.get('src_any', each.get('src_ip_prefix'))
temp['src_port_op'] = each.get('src_port_op')
temp['src_port1'] = each.get('src_port1_num')
temp['src_port2'] = each.get('src_port2_num')
temp['dest'] = each.get('dest_any', each.get('dest_ip_prefix'))
temp['dest_port_op'] = each.get('dest_port_op')
temp['dest_port1'] = each.get('dest_port1_num')
temp['dest_port2'] = each.get('dest_port2_num')
options['log'] = each.get('log')
options['urg'] = each.get('urg')
options['ack'] = each.get('ack')
options['psh'] = each.get('psh')
options['rst'] = each.get('rst')
options['syn'] = each.get('syn')
options['fin'] = each.get('fin')
options['established'] = each.get('established')
options['dscp'] = each.get('dscp_str')
options['precedence'] = each.get('precedence_str')
options['fragments'] = each.get('fragments')
options['time_range'] = each.get('timerange')
keep = {}
for key, value in temp.items():
if value:
keep[key] = value
options_no_null = {}
for key, value in options.items():
if value is not None:
options_no_null[key] = value
keep['options'] = options_no_null
if keep.get('seq') == seq_number:
saveme = dict(keep)
new_acl.append(keep)
return saveme, new_acl
def _acl_operand(operand, srcp1, sprcp2):
sub_entry = ' ' + operand
if operand == 'range':
sub_entry += ' ' + srcp1 + ' ' + sprcp2
else:
sub_entry += ' ' + srcp1
return sub_entry
def config_core_acl(proposed):
seq = proposed.get('seq')
action = proposed.get('action')
remark = proposed.get('remark')
proto = proposed.get('proto')
src = proposed.get('src')
src_port_op = proposed.get('src_port_op')
src_port1 = proposed.get('src_port1')
src_port2 = proposed.get('src_port2')
dest = proposed.get('dest')
dest_port_op = proposed.get('dest_port_op')
dest_port1 = proposed.get('dest_port1')
dest_port2 = proposed.get('dest_port2')
ace_start_entries = [action, proto, src]
if not remark:
ace = seq + ' ' + ' '.join(ace_start_entries)
if src_port_op:
ace += _acl_operand(src_port_op, src_port1, src_port2)
ace += ' ' + dest
if dest_port_op:
ace += _acl_operand(dest_port_op, dest_port1, dest_port2)
else:
ace = seq + ' remark ' + remark
return ace
def config_acl_options(options):
ENABLE_ONLY = ['psh', 'urg', 'log', 'ack', 'syn',
'established', 'rst', 'fin', 'fragments',
'log']
OTHER = ['dscp', 'precedence', 'time-range']
# packet-length is the only option not currently supported
if options.get('time_range'):
options['time-range'] = options.get('time_range')
options.pop('time_range')
command = ''
for option, value in options.items():
if option in ENABLE_ONLY:
if value == 'enable':
command += ' ' + option
elif option in OTHER:
command += ' ' + option + ' ' + value
if command:
command = command.strip()
return command
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def main():
argument_spec = dict(
seq=dict(required=False, type='str'),
name=dict(required=True, type='str'),
action=dict(required=False, choices=['remark', 'permit', 'deny']),
remark=dict(required=False, type='str'),
proto=dict(required=False, type='str'),
src=dict(required=False, type='str'),
src_port_op=dict(required=False),
src_port1=dict(required=False, type='str'),
src_port2=dict(required=False, type='str'),
dest=dict(required=False, type='str'),
dest_port_op=dict(required=False),
dest_port1=dict(required=False, type='str'),
dest_port2=dict(required=False, type='str'),
log=dict(required=False, choices=['enable']),
urg=dict(required=False, choices=['enable']),
ack=dict(required=False, choices=['enable']),
psh=dict(required=False, choices=['enable']),
rst=dict(required=False, choices=['enable']),
syn=dict(required=False, choices=['enable']),
fragments=dict(required=False, choices=['enable']),
fin=dict(required=False, choices=['enable']),
established=dict(required=False, choices=['enable']),
time_range=dict(required=False),
precedence=dict(required=False, choices=['critical', 'flash',
'flash-override',
'immediate', 'internet',
'network', 'priority',
'routine']),
dscp=dict(required=False, choices=['af11', 'af12', 'af13', 'af21',
'af22', 'af23', 'af31', 'af32',
'af33', 'af41', 'af42', 'af43',
'cs1', 'cs2', 'cs3', 'cs4',
'cs5', 'cs6', 'cs7', 'default',
'ef']),
state=dict(choices=['absent', 'present', 'delete_acl'], default='present')
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
results = dict(changed=False, warnings=warnings)
state = module.params['state']
action = module.params['action']
remark = module.params['remark']
dscp = module.params['dscp']
precedence = module.params['precedence']
seq = module.params['seq']
name = module.params['name']
seq = module.params['seq']
if action == 'remark' and not remark:
module.fail_json(msg='when state is action, remark param is also required')
REQUIRED = ['seq', 'name', 'action', 'proto', 'src', 'dest']
ABSENT = ['name', 'seq']
if state == 'present':
if action and remark and seq:
pass
else:
for each in REQUIRED:
if module.params[each] is None:
module.fail_json(msg="req'd params when state is present:",
params=REQUIRED)
elif state == 'absent':
for each in ABSENT:
if module.params[each] is None:
module.fail_json(msg='require params when state is absent',
params=ABSENT)
elif state == 'delete_acl':
if module.params['name'] is None:
module.fail_json(msg="param name req'd when state is delete_acl")
if dscp and precedence:
module.fail_json(msg='only one of the params dscp/precedence '
'are allowed')
OPTIONS_NAMES = ['log', 'urg', 'ack', 'psh', 'rst', 'syn', 'fin',
'established', 'dscp', 'precedence', 'fragments',
'time_range']
CORE = ['seq', 'name', 'action', 'proto', 'src', 'src_port_op',
'src_port1', 'src_port2', 'dest', 'dest_port_op',
'dest_port1', 'dest_port2', 'remark']
proposed_core = dict((param, value) for (param, value) in
module.params.items()
if param in CORE and value is not None)
proposed_options = dict((param, value) for (param, value) in
module.params.items()
if param in OPTIONS_NAMES and value is not None)
proposed = {}
proposed.update(proposed_core)
proposed.update(proposed_options)
existing_options = {}
# getting existing existing_core=dict, acl=list, seq=list
existing_core, acl = get_acl(module, name, seq)
if existing_core:
existing_options = existing_core.get('options')
existing_core.pop('options')
commands = []
delta_core = {}
delta_options = {}
if not existing_core.get('remark'):
dcore = dict(
set(proposed_core.items()).difference(
existing_core.items())
)
if not dcore:
# check the diff in the other way just in case
dcore = dict(
set(existing_core.items()).difference(
proposed_core.items())
)
delta_core = dcore
if delta_core:
delta_options = proposed_options
else:
doptions = dict(
set(proposed_options.items()).difference(
existing_options.items())
)
# check the diff in the other way just in case
if not doptions:
doptions = dict(
set(existing_options.items()).difference(
proposed_options.items())
)
delta_options = doptions
else:
delta_core = dict(
set(proposed_core.items()).difference(
existing_core.items())
)
if state == 'present':
if delta_core or delta_options:
if existing_core: # if the ace exists already
commands.append(['no {0}'.format(seq)])
if delta_options:
myacl_str = config_core_acl(proposed_core)
myacl_str += ' ' + config_acl_options(proposed_options)
else:
myacl_str = config_core_acl(proposed_core)
command = [myacl_str]
commands.append(command)
elif state == 'absent':
if existing_core:
commands.append(['no {0}'.format(seq)])
elif state == 'delete_acl':
if acl and acl[0].get('acl') != 'no_entries':
commands.append(['no ip access-list {0}'.format(name)])
cmds = []
if commands:
preface = []
if state in ['present', 'absent']:
preface = ['ip access-list {0}'.format(name)]
commands.insert(0, preface)
cmds = flatten_list(commands)
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
load_config(module, cmds)
results['changed'] = True
if 'configure' in cmds:
cmds.pop(0)
results['commands'] = cmds
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,201 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_acl_interface
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages applying ACLs to interfaces.
description:
- Manages applying ACLs to interfaces.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
options:
name:
description:
- Case sensitive name of the access list (ACL).
required: true
interface:
description:
- Full name of interface, e.g. I(Ethernet1/1).
required: true
direction:
description:
- Direction ACL to be applied in on the interface.
required: true
choices: ['ingress', 'egress']
state:
description:
- Specify desired state of the resource.
required: false
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
- name: apply egress acl to ethernet1/41
nxos_acl_interface:
name: ANSIBLE
interface: ethernet1/41
direction: egress
state: present
'''
RETURN = '''
acl_applied_to:
description: list of interfaces the ACL is applied to
returned: always
type: list
sample: [{"acl_type": "Router ACL", "direction": "egress",
"interface": "Ethernet1/41", "name": "ANSIBLE"}]
commands:
description: commands sent to the device
returned: always
type: list
sample: ["interface ethernet1/41", "ip access-group ANSIBLE out"]
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def check_for_acl_int_present(module, name, intf, direction):
# Need to Capitalize the interface name as the nxos
# output has capitalization
command = [{
'command': 'show running-config aclmgr | section {0}'.format(intf.title()),
'output': 'text',
}]
body = run_commands(module, command)
if direction == 'ingress':
mdir = 'in'
elif direction == 'egress':
mdir = 'out'
match = re.search('ip access-group {0} {1}'.format(name, mdir), str(body[0]))
return bool(match)
def apply_acl(proposed):
commands = []
commands.append('interface ' + proposed.get('interface'))
direction = proposed.get('direction')
if direction == 'egress':
cmd = 'ip access-group {0} {1}'.format(proposed.get('name'), 'out')
elif direction == 'ingress':
cmd = 'ip access-group {0} {1}'.format(proposed.get('name'), 'in')
commands.append(cmd)
return commands
def remove_acl(proposed):
commands = []
commands.append('interface ' + proposed.get('interface'))
direction = proposed.get('direction')
if direction == 'egress':
cmd = 'no ip access-group {0} {1}'.format(proposed.get('name'), 'out')
elif direction == 'ingress':
cmd = 'no ip access-group {0} {1}'.format(proposed.get('name'), 'in')
commands.append(cmd)
return commands
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def main():
argument_spec = dict(
name=dict(required=False, type='str'),
interface=dict(required=True),
direction=dict(required=True, choices=['egress', 'ingress']),
state=dict(choices=['absent', 'present'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
results = dict(changed=False, warnings=warnings)
state = module.params['state']
name = module.params['name']
interface = module.params['interface'].lower()
direction = module.params['direction'].lower()
proposed = dict(name=name, interface=interface, direction=direction)
existing = check_for_acl_int_present(module, name, interface, direction)
cmds = []
commands = []
if state == 'present':
if not existing:
command = apply_acl(proposed)
if command:
commands.append(command)
elif state == 'absent':
if existing:
command = remove_acl(proposed)
if command:
commands.append(command)
if commands:
cmds = flatten_list(commands)
if cmds:
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
load_config(module, cmds)
results['changed'] = True
if 'configure' in cmds:
cmds.pop(0)
else:
cmds = []
results['commands'] = cmds
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,408 +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 nxos_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: nxos_acl_interfaces
version_added: '2.10'
short_description: Add and remove Access Control Lists on interfaces in NX-OS platform
description: Add and remove Access Control Lists on interfaces in NX-OS platform
author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
notes:
- Tested against NX-OS 7.3.(0)D1(1) on VIRL
options:
running_config:
description:
- Used to parse given commands into structured format, only in parsed state
type: str
config:
description: A list of interfaces to be configured with ACLs
type: list
elements: dict
suboptions:
name:
description: Name of the interface
type: str
required: true
access_groups:
description: List of address family indicators with ACLs to be configured on the interface
type: list
elements: dict
suboptions:
afi:
description: Address Family Indicator of the ACLs to be configured
type: str
required: true
choices: ['ipv4','ipv6']
acls:
description: List of Access Control Lists for the interface
type: list
elements: dict
suboptions:
name:
description: Name of the ACL to be added/removed
type: str
required: true
direction:
description: Direction to be applied for the ACL
type: str
required: true
choices: ['in','out']
port:
description: Use ACL as port policy.
type: bool
state:
description: The state the configuration should be left in
type: str
choices:
- deleted
- gathered
- merged
- overridden
- rendered
- replaced
- parsed
default: merged
"""
EXAMPLES = """# Using merged
# Before state:
# ------------
#
- name: Merge ACL interfaces configuration
nxos_acl_interfaces:
config:
- name: Ethernet1/2
access_groups:
- afi: ipv6
acls:
- name: ACL1v6
direction: in
- name: Eth1/5
access_groups:
- afi: ipv4
acls:
- name: PortACL
direction: in
port: true
- name: ACL1v4
direction: out
- afi: ipv6
acls:
- name: ACL1v6
direction: in
state: merged
# After state:
# ------------
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/5
# ip port access-group PortACL in
# ip access-group ACL1v4 out
# ipv6 traffic-filter ACL1v6 in
# Using replaced
# Before state:
# ------------
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/5
# ip port access-group PortACL in
# ip access-group ACL1v4 out
# ipv6 traffic-filter ACL1v6 in
- name: Replace interface configuration with given configuration
nxos_acl_interfaces:
config:
- name: Eth1/5
access_groups:
- afi: ipv4
acls:
- name: NewACLv4
direction: out
- name: Ethernet1/3
access_groups:
- afi: ipv6
acls:
- name: NewACLv6
direction: in
port: true
state: replaced
# After state:
# ------------
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/3
# ipv6 port traffic-filter NewACLv6 in
# interface Ethernet1/5
# ip access-group NewACLv4 out
# Using overridden
# Before state:
# ------------
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/5
# ip port access-group PortACL in
# ip access-group ACL1v4 out
# ipv6 traffic-filter ACL1v6 in
- name: Override interface configuration with given configuration
nxos_acl_interfaces:
config:
- name: Ethernet1/3
access_groups:
- afi: ipv4
acls:
- name: ACL1v4
direction: out
- name: PortACL
port: true
direction: in
- afi: ipv6
acls:
- name: NewACLv6
direction: in
port: true
state: overridden
# After state:
# ------------
# interface Ethernet1/3
# ip access-group ACL1v4 out
# ip port access-group PortACL in
# ipv6 port traffic-filter NewACLv6 in
# Using deleted
# Before state:
# -------------
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/5
# ip port access-group PortACL in
# ip access-group ACL1v4 out
# ipv6 traffic-filter ACL1v6 in
- name: Delete ACL configuration on interfaces
nxos_acl_interfaces:
config:
- name: Ethernet1/5
access_groups:
- afi: ipv6
- afi: ipv4
acls:
- name: ACL1v4
direction: out
- name: Ethernet1/2
state: deleted
# After state:
# -------------
# interface Ethernet1/2
# interface Ethernet1/5
# ip port access-group PortACL in
# ip access-group ACL1v4 out
# ipv6 traffic-filter ACL1v6 in
# Using parsed
- name: Parse given configuration into structured format
nxos_acl_interfaces:
running_config: |
interface Ethernet1/2
ipv6 traffic-filter ACL1v6 in
interface Ethernet1/5
ipv6 traffic-filter ACL1v6 in
ip access-group ACL1v4 out
ip port access-group PortACL in
state: parsed
# returns
# parsed:
# - name: Ethernet1/2
# access_groups:
# - afi: ipv6
# acls:
# - name: ACL1v6
# direction: in
# - name: Ethernet1/5
# access_groups:
# - afi: ipv4
# acls:
# - name: PortACL
# direction: in
# port: True
# - name: ACL1v4
# direction: out
# - afi: ipv6
# acls:
# - name: ACL1v6
# direction: in
# Using gathered:
# Before state:
# ------------
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/5
# ipv6 traffic-filter ACL1v6 in
# ip access-group ACL1v4 out
# ip port access-group PortACL in
- name: Gather existing configuration from device
nxos_acl_interfaces:
config:
state: gathered
# returns
# gathered:
# - name: Ethernet1/2
# access_groups:
# - afi: ipv6
# acls:
# - name: ACL1v6
# direction: in
# - name: Ethernet1/5
# access_groups:
# - afi: ipv4
# acls:
# - name: PortACL
# direction: in
# port: True
# - name: ACL1v4
# direction: out
# - afi: ipv6
# acls:
# - name: ACL1v6
# direction: in
# Using rendered
- name: Render required configuration to be pushed to the device
nxos_acl_interfaces:
config:
- name: Ethernet1/2
access_groups:
- afi: ipv6
acls:
- name: ACL1v6
direction: in
- name: Ethernet1/5
access_groups:
- afi: ipv4
acls:
- name: PortACL
direction: in
port: true
- name: ACL1v4
direction: out
- afi: ipv6
acls:
- name: ACL1v6
direction: in
state: rendered
# returns
# rendered:
# interface Ethernet1/2
# ipv6 traffic-filter ACL1v6 in
# interface Ethernet1/5
# ipv6 traffic-filter ACL1v6 in
# ip access-group ACL1v4 out
# ip port access-group PortACL in
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: dict
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: 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: ['interface Ethernet1/2', 'ipv6 traffic-filter ACL1v6 out', 'ip port access-group PortACL in']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
from ansible.module_utils.network.nxos.config.acl_interfaces.acl_interfaces import Acl_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Acl_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Acl_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,825 +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 nxos_acls
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: nxos_acls
version_added: '2.10'
short_description: Manage named IP ACLs on the Cisco NX-OS platform
description: Manage named IP ACLs on the Cisco NX-OS platform
author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
notes:
- Tested against NX-OS 7.3.(0)D1(1) on VIRL
- As NX-OS allows configuring a rule again with different sequence numbers,
the user is expected to provide sequence numbers for the access control entries to preserve idempotency.
If no sequence number is given, the rule will be added as a new rule by the device.
- To parse configuration text, provide the output of show running-config | section access-list or a mocked up config
options:
running_config:
description:
- Parse given commands into structured format. Required if I(state=parsed).
type: str
config:
description: A dictionary of ACL options.
type: list
elements: dict
suboptions:
afi:
description: The Address Family Indicator (AFI) for the ACL.
type: str
required: true
choices: ['ipv4', 'ipv6']
acls:
description: A list of the ACLs.
type: list
elements: dict
suboptions:
name:
description: Name of the ACL.
type: str
required: true
aces:
description: The entries within the ACL.
type: list
elements: dict
suboptions:
grant:
description: Action to be applied on the rule.
type: str
choices: ['permit', 'deny']
destination:
description: Specify the packet destination.
type: dict
suboptions:
address:
description: Destination network address.
type: str
any:
description: Any destination address.
type: bool
host:
description: Host IP address.
type: str
port_protocol:
description: Specify the destination port or protocol (only for TCP and UDP).
type: dict
suboptions:
eq:
description: Match only packets on a given port number.
type: str
gt:
description: Match only packets with a greater port number.
type: str
lt:
description: Match only packets with a lower port number.
type: str
neq:
description: Match only packets not on a given port number.
type: str
range:
description: Match only packets in the range of port numbers.
type: dict
suboptions:
start:
description: Specify the start of the port range.
type: str
end:
description: Specify the end of the port range.
type: str
prefix:
description: Destination network prefix. Only for prefixes of value less than 31 for ipv4 and 127 for ipv6.
Prefixes of 32 (ipv4) and 128 (ipv6) should be given in the 'host' key.
type: str
wildcard_bits:
description: Destination wildcard bits.
type: str
dscp:
description: Match packets with given DSCP value.
type: str
fragments:
description: Check non-initial fragments.
type: bool
remark:
description: Access list entry comment.
type: str
sequence:
description: Sequence number.
type: int
source:
description: Specify the packet source.
type: dict
suboptions:
address:
description: Source network address.
type: str
any:
description: Any source address.
type: bool
host:
description: Host IP address.
type: str
port_protocol:
description: Specify the destination port or protocol (only for TCP and UDP).
type: dict
suboptions:
eq:
description: Match only packets on a given port number.
type: str
gt:
description: Match only packets with a greater port number.
type: str
lt:
description: Match only packets with a lower port number.
type: str
neq:
description: Match only packets not on a given port number.
type: str
range:
description: Match only packets in the range of port numbers.
type: dict
suboptions:
start:
description: Specify the start of the port range.
type: str
end:
description: Specify the end of the port range.
type: str
prefix:
description: Source network prefix. Only for prefixes of mask value less than 31 for ipv4 and 127 for ipv6.
Prefixes of mask 32 (ipv4) and 128 (ipv6) should be given in the 'host' key.
type: str
wildcard_bits:
description: Source wildcard bits.
type: str
log:
description: Log matches against this entry.
type: bool
precedence:
description: Match packets with given precedence value.
type: str
protocol:
description: Specify the protocol.
type: str
protocol_options:
description: All possible suboptions for the protocol chosen.
type: dict
suboptions:
icmp:
description: ICMP protocol options.
type: dict
suboptions:
administratively_prohibited:
description: Administratively prohibited
type: bool
alternate_address:
description: Alternate address
type: bool
conversion_error:
description: Datagram conversion
type: bool
dod_host_prohibited:
description: Host prohibited
type: bool
dod_net_prohibited:
description: Net prohibited
type: bool
echo:
description: Echo (ping)
type: bool
echo_reply:
description: Echo reply
type: bool
general_parameter_problem:
description: Parameter problem
type: bool
host_isolated:
description: Host isolated
type: bool
host_precedence_unreachable:
description: Host unreachable for precedence
type: bool
host_redirect:
description: Host redirect
type: bool
host_tos_redirect:
description: Host redirect for TOS
type: bool
host_tos_unreachable:
description: Host unreachable for TOS
type: bool
host_unknown:
description: Host unknown
type: bool
host_unreachable:
description: Host unreachable
type: bool
information_reply:
description: Information replies
type: bool
information_request:
description: Information requests
type: bool
mask_reply:
description: Mask replies
type: bool
mask_request:
description: Mask requests
type: bool
message_code:
description: ICMP message code
type: int
message_type:
description: ICMP message type
type: int
mobile_redirect:
description: Mobile host redirect
type: bool
net_redirect:
description: Network redirect
type: bool
net_tos_redirect:
description: Net redirect for TOS
type: bool
net_tos_unreachable:
description: Network unreachable for TOS
type: bool
net_unreachable:
description: Net unreachable
type: bool
network_unknown:
description: Network unknown
type: bool
no_room_for_option:
description: Parameter required but no room
type: bool
option_missing:
description: Parameter required but not present
type: bool
packet_too_big:
description: Fragmentation needed and DF set
type: bool
parameter_problem:
description: All parameter problems
type: bool
port_unreachable:
description: Port unreachable
type: bool
precedence_unreachable:
description: Precedence cutoff
type: bool
protocol_unreachable:
description: Protocol unreachable
type: bool
reassembly_timeout:
description: Reassembly timeout
type: bool
redirect:
description: All redirects
type: bool
router_advertisement:
description: Router discovery advertisements
type: bool
router_solicitation:
description: Router discovery solicitations
type: bool
source_quench:
description: Source quenches
type: bool
source_route_failed:
description: Source route failed
type: bool
time_exceeded:
description: All time exceeded.
type: bool
timestamp_reply:
description: Timestamp replies
type: bool
timestamp_request:
description: Timestamp requests
type: bool
traceroute:
description: Traceroute
type: bool
ttl_exceeded:
description: TTL exceeded
type: bool
unreachable:
description: All unreachables
type: bool
tcp:
description: TCP flags.
type: dict
suboptions:
ack:
description: Match on the ACK bit
type: bool
established:
description: Match established connections
type: bool
fin:
description: Match on the FIN bit
type: bool
psh:
description: Match on the PSH bit
type: bool
rst:
description: Match on the RST bit
type: bool
syn:
description: Match on the SYN bit
type: bool
urg:
description: Match on the URG bit
type: bool
igmp:
description: IGMP protocol options.
type: dict
suboptions:
dvmrp:
description: Distance Vector Multicast Routing Protocol
type: bool
host_query:
description: Host Query
type: bool
host_report:
description: Host Report
type: bool
state:
description:
- The state the configuration should be left in
type: str
choices:
- deleted
- gathered
- merged
- overridden
- rendered
- replaced
- parsed
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
#
- name: Merge new ACLs configuration
nxos_acls:
config:
- afi: ipv4
acls:
- name: ACL1v4
aces:
- grant: deny
destination:
address: 192.0.2.64
wildcard_bits: 0.0.0.255
source:
any: true
port_protocol:
lt: 55
protocol: tcp
protocol_options:
tcp:
ack: true
fin: true
sequence: 50
- afi: ipv6
acls:
- name: ACL1v6
aces:
- grant: permit
sequence: 10
source:
any: true
destination:
prefix: 2001:db8:12::/32
protocol: sctp
state: merged
# After state:
# ------------
#
# ip access-list ACL1v4
# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
# ipv6 access-list ACL1v6
# 10 permit sctp any any
# Using replaced
# Before state:
# ----------------
#
# ip access-list ACL1v4
# 10 permit ip any any
# 20 deny udp any any
# ip access-list ACL2v4
# 10 permit ahp 192.0.2.0 0.0.0.255 any
# ip access-list ACL1v6
# 10 permit sctp any any
# 20 remark IPv6 ACL
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
- name: Replace existing ACL configuration with provided configuration
nxos_acls:
config:
- afi: ipv4
- afi: ipv6
acls:
- name: ACL1v6
aces:
- sequence: 20
grant: permit
source:
any: true
destination:
any: true
protocol: pip
- remark: Replaced ACE
- name: ACL2v6
state: replaced
# After state:
# ---------------
#
# ipv6 access-list ACL1v6
# 20 permit pip any any
# 30 remark Replaced ACE
# ipv6 access-list ACL2v6
# Using overridden
# Before state:
# ----------------
#
# ip access-list ACL1v4
# 10 permit ip any any
# 20 deny udp any any
# ip access-list ACL2v4
# 10 permit ahp 192.0.2.0 0.0.0.255 any
# ip access-list ACL1v6
# 10 permit sctp any any
# 20 remark IPv6 ACL
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
- name: Override existing configuration with provided configuration
nxos_acls:
config:
- afi: ipv4
acls:
- name: NewACL
aces:
- grant: deny
source:
address: 192.0.2.0
wildcard_bits: 0.0.255.255
destination:
any: true
protocol: eigrp
- remark: Example for overridden state
state: overridden
# After state:
# ------------
#
# ip access-list NewACL
# 10 deny eigrp 192.0.2.0 0.0.255.255 any
# 20 remark Example for overridden state
# Using deleted:
#
# Before state:
# -------------
#
# ip access-list ACL1v4
# 10 permit ip any any
# 20 deny udp any any
# ip access-list ACL2v4
# 10 permit ahp 192.0.2.0 0.0.0.255 any
# ip access-list ACL1v6
# 10 permit sctp any any
# 20 remark IPv6 ACL
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
- name: Delete all ACLs
nxos_acls:
config:
state: deleted
# After state:
# -----------
#
# Before state:
# -------------
#
# ip access-list ACL1v4
# 10 permit ip any any
# 20 deny udp any any
# ip access-list ACL2v4
# 10 permit ahp 192.0.2.0 0.0.0.255 any
# ip access-list ACL1v6
# 10 permit sctp any any
# 20 remark IPv6 ACL
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
- name: Delete all ACLs in given AFI
nxos_acls:
config:
- afi: ipv4
state: deleted
# After state:
# ------------
#
# ip access-list ACL1v6
# 10 permit sctp any any
# 20 remark IPv6 ACL
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
# Before state:
# -------------
#
# ip access-list ACL1v4
# 10 permit ip any any
# 20 deny udp any any
# ip access-list ACL2v4
# 10 permit ahp 192.0.2.0 0.0.0.255 any
# ip access-list ACL1v6
# 10 permit sctp any any
# 20 remark IPv6 ACL
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
- name: Delete specific ACLs
nxos_acls:
config:
- afi: ipv6
acls:
- name: ACL1v6
aces:
- grant: permit
sequence: 10
source:
any: true
destination:
any: true
protocol: sctp
- sequence: 20
state: deleted
# After state:
# ------------
#
# ip access-list ACL1v4
# 10 permit ip any any
# 20 deny udp any any
# ip access-list ACL2v4
# 10 permit ahp 192.0.2.0 0.0.0.255 any
# ip access-list ACl1v6
# ip access-list ACL2v6
# 10 deny ipv6 any 2001:db8:3000::/36
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
# Using parsed
- name: Parse given config to structured data
nxos_acls:
running_config: |
ip access-list ACL1v4
50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
ipv6 access-list ACL1v6
10 permit sctp any any
state: parsed
# returns:
# parsed:
# - afi: ipv4
# acls:
# - name: ACL1v4
# aces:
# - grant: deny
# destination:
# address: 192.0.2.64
# wildcard_bits: 0.0.0.255
# source:
# any: true
# port_protocol:
# lt: 55
# protocol: tcp
# protocol_options:
# tcp:
# ack: true
# fin: true
# sequence: 50
#
# - afi: ipv6
# acls:
# - name: ACL1v6
# aces:
# - grant: permit
# sequence: 10
# source:
# any: true
# destination:
# prefix: 2001:db8:12::/32
# protocol: sctp
# Using gathered:
# Before state:
# ------------
#
# ip access-list ACL1v4
# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
# ipv6 access-list ACL1v6
# 10 permit sctp any any
- name: Gather existing configuration
nxos_acls:
state: gathered
# returns:
# gathered:
# - afi: ipv4
# acls:
# - name: ACL1v4
# aces:
# - grant: deny
# destination:
# address: 192.0.2.64
# wildcard_bits: 0.0.0.255
# source:
# any: true
# port_protocol:
# lt: 55
# protocol: tcp
# protocol_options:
# tcp:
# ack: true
# fin: true
# sequence: 50
# - afi: ipv6
# acls:
# - name: ACL1v6
# aces:
# - grant: permit
# sequence: 10
# source:
# any: true
# destination:
# prefix: 2001:db8:12::/32
# protocol: sctp
# Using rendered
- name: Render required configuration to be pushed to the device
nxos_acls:
config:
- afi: ipv4
acls:
- name: ACL1v4
aces:
- grant: deny
destination:
address: 192.0.2.64
wildcard_bits: 0.0.0.255
source:
any: true
port_protocol:
lt: 55
protocol: tcp
protocol_options:
tcp:
ack: true
fin: true
sequence: 50
- afi: ipv6
acls:
- name: ACL1v6
aces:
- grant: permit
sequence: 10
source:
any: true
destination:
prefix: 2001:db8:12::/32
protocol: sctp
state: rendered
# returns:
# rendered:
# ip access-list ACL1v4
# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
# ipv6 access-list ACL1v6
# 10 permit sctp any any
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: dict
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: 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: ['ip access-list ACL1v4', '10 permit ip any any precedence critical log', '20 deny tcp any lt smtp host 192.0.2.64 ack fin']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.acls.acls import AclsArgs
from ansible.module_utils.network.nxos.config.acls.acls import Acls
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=AclsArgs.argument_spec,
supports_check_mode=True)
result = Acls(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,208 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017, Ansible by Red Hat, inc
#
# This file is part of Ansible by Red Hat
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_banner
version_added: "2.4"
author: "Trishna Guha (@trishnaguha)"
short_description: Manage multiline banners on Cisco NXOS devices
description:
- This will configure both exec and motd banners on remote devices
running Cisco NXOS. It allows playbooks to add or remote
banner text from the active running configuration.
options:
banner:
description:
- Specifies which banner that should be
configured on the remote device.
required: true
choices: ['exec', 'motd']
text:
description:
- The banner text that should be
present in the remote device running configuration. This argument
accepts a multiline string, with no empty lines. Requires I(state=present).
state:
description:
- Specifies whether or not the configuration is present in the current
devices active running configuration.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: nxos
"""
EXAMPLES = """
- name: configure the exec banner
nxos_banner:
banner: exec
text: |
this is my exec banner
that contains a multiline
string
state: present
- name: remove the motd banner
nxos_banner:
banner: motd
state: absent
- name: Configure banner from file
nxos_banner:
banner: motd
text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}"
state: present
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- banner exec
- this is my exec banner
- that contains a multiline
- string
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
import re
def execute_show_command(module, command):
format = 'text'
cmds = [{
'command': command,
'output': format,
}]
output = run_commands(module, cmds)
return output
def map_obj_to_commands(want, have, module):
commands = list()
state = module.params['state']
platform_regex = 'Nexus.*Switch'
if state == 'absent':
if (have.get('text') and not ((have.get('text') == 'User Access Verification') or re.match(platform_regex, have.get('text')))):
commands.append('no banner %s' % module.params['banner'])
elif state == 'present' and want.get('text') != have.get('text'):
banner_cmd = 'banner %s @\n%s\n@' % (module.params['banner'], want['text'].strip())
commands.append(banner_cmd)
return commands
def map_config_to_obj(module):
command = 'show banner %s' % module.params['banner']
output = execute_show_command(module, command)[0]
if "Invalid command" in output:
module.fail_json(msg="banner: %s may not be supported on this platform. Possible values are : exec | motd" % module.params['banner'])
if isinstance(output, dict):
output = list(output.values())
if output != []:
output = output[0]
else:
output = ''
if isinstance(output, dict):
output = list(output.values())
if output != []:
output = output[0]
else:
output = ''
else:
output = output.rstrip()
obj = {'banner': module.params['banner'], 'state': 'absent'}
if output:
obj['text'] = output
obj['state'] = 'present'
return obj
def map_params_to_obj(module):
text = module.params['text']
if text:
text = str(text).strip()
return {
'banner': module.params['banner'],
'text': text,
'state': module.params['state']
}
def main():
""" main entry point for module execution
"""
argument_spec = dict(
banner=dict(required=True, choices=['exec', 'motd']),
text=dict(),
state=dict(default='present', choices=['present', 'absent'])
)
argument_spec.update(nxos_argument_spec)
required_if = [('state', 'present', ('text',))]
module = AnsibleModule(argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands(want, have, module)
result['commands'] = commands
if commands:
if not module.check_mode:
msgs = load_config(module, commands, True)
if msgs:
for item in msgs:
if item:
if isinstance(item, dict):
err_str = item['clierror']
else:
err_str = item
if 'more than 40 lines' in err_str or 'buffer overflowed' in err_str:
load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,330 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_bfd_global
extends_documentation_fragment: nxos
version_added: "2.9"
short_description: Bidirectional Forwarding Detection (BFD) global-level configuration
description:
- Manages Bidirectional Forwarding Detection (BFD) global-level configuration.
author:
- Chris Van Heuveln (@chrisvanheuveln)
notes:
- Tested against NXOSv 9.2(2)
- BFD global will automatically enable 'feature bfd' if it is disabled.
- BFD global does not have a 'state' parameter. All of the BFD commands are unique and are defined if 'feature bfd' is enabled.
options:
# Top-level commands
echo_interface:
description:
- Loopback interface used for echo frames.
- Valid values are loopback interface name or 'deleted'.
- Not supported on N5K/N6K
required: false
type: str
echo_rx_interval:
description:
- BFD Echo receive interval in milliseconds.
required: false
type: int
interval:
description:
- BFD interval timer values.
- Value must be a dict defining values for keys (tx, min_rx, and multiplier)
required: false
type: dict
slow_timer:
description:
- BFD slow rate timer in milliseconds.
required: false
type: int
startup_timer:
description:
- BFD delayed startup timer in seconds.
- Not supported on N5K/N6K/N7K
required: false
type: int
# IPv4/IPv6 specific commands
ipv4_echo_rx_interval:
description:
- BFD IPv4 session echo receive interval in milliseconds.
required: false
type: int
ipv4_interval:
description:
- BFD IPv4 interval timer values.
- Value must be a dict defining values for keys (tx, min_rx, and multiplier).
required: false
type: dict
ipv4_slow_timer:
description:
- BFD IPv4 slow rate timer in milliseconds.
required: false
type: int
ipv6_echo_rx_interval:
description:
- BFD IPv6 session echo receive interval in milliseconds.
required: false
type: int
ipv6_interval:
description:
- BFD IPv6 interval timer values.
- Value must be a dict defining values for keys (tx, min_rx, and multiplier).
required: false
type: dict
ipv6_slow_timer:
description:
- BFD IPv6 slow rate timer in milliseconds.
required: false
type: int
# Fabricpath commands
fabricpath_interval:
description:
- BFD fabricpath interval timer values.
- Value must be a dict defining values for keys (tx, min_rx, and multiplier).
required: false
type: dict
fabricpath_slow_timer:
description:
- BFD fabricpath slow rate timer in milliseconds.
required: false
type: int
fabricpath_vlan:
description:
- BFD fabricpath control vlan.
required: false
type: int
'''
EXAMPLES = '''
- nxos_bfd_global:
echo_interface: Ethernet1/2
echo_rx_interval: 50
interval:
tx: 50
min_rx: 50
multiplier: 4
'''
RETURN = '''
cmds:
description: commands sent to the device
returned: always
type: list
sample: ["bfd echo-interface loopback1", "bfd slow-timer 2000"]
'''
import re
from ansible.module_utils.network.nxos.nxos import NxosCmdRef
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.network.nxos.nxos import load_config
from ansible.module_utils.basic import AnsibleModule
BFD_CMD_REF = """
# The cmd_ref is a yaml formatted list of module commands.
# A leading underscore denotes a non-command variable; e.g. _template.
# BFD does not have convenient json data so this cmd_ref uses raw cli configs.
---
_template: # _template holds common settings for all commands
# Enable feature bfd if disabled
feature: bfd
# Common get syntax for BFD commands
get_command: show run bfd all | incl '^(no )*bfd'
echo_interface:
kind: str
getval: (no )*bfd echo-interface *(\\S+)*$
setval: 'bfd echo-interface {0}'
default: ~
echo_rx_interval:
_exclude: ['N5K', 'N6K']
kind: int
getval: bfd echo-rx-interval (\\d+)$
setval: bfd echo-rx-interval {0}
default: 50
N3K:
default: 250
interval:
kind: dict
getval: bfd interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
setval: bfd interval {tx} min_rx {min_rx} multiplier {multiplier}
default: &def_interval
tx: 50
min_rx: 50
multiplier: 3
N3K:
default: &n3k_def_interval
tx: 250
min_rx: 250
multiplier: 3
slow_timer:
kind: int
getval: bfd slow-timer (\\d+)$
setval: bfd slow-timer {0}
default: 2000
startup_timer:
_exclude: ['N5K', 'N6K', 'N7K']
kind: int
getval: bfd startup-timer (\\d+)$
setval: bfd startup-timer {0}
default: 5
# IPv4/IPv6 specific commands
ipv4_echo_rx_interval:
_exclude: ['N5K', 'N6K']
kind: int
getval: bfd ipv4 echo-rx-interval (\\d+)$
setval: bfd ipv4 echo-rx-interval {0}
default: 50
N3K:
default: 250
ipv4_interval:
_exclude: ['N5K', 'N6K']
kind: dict
getval: bfd ipv4 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
setval: bfd ipv4 interval {tx} min_rx {min_rx} multiplier {multiplier}
default: *def_interval
N3K:
default: *n3k_def_interval
ipv4_slow_timer:
_exclude: ['N5K', 'N6K']
kind: int
getval: bfd ipv4 slow-timer (\\d+)$
setval: bfd ipv4 slow-timer {0}
default: 2000
ipv6_echo_rx_interval:
_exclude: ['N35', 'N5K', 'N6K']
kind: int
getval: bfd ipv6 echo-rx-interval (\\d+)$
setval: bfd ipv6 echo-rx-interval {0}
default: 50
N3K:
default: 250
ipv6_interval:
_exclude: ['N35', 'N5K', 'N6K']
kind: dict
getval: bfd ipv6 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
setval: bfd ipv6 interval {tx} min_rx {min_rx} multiplier {multiplier}
default: *def_interval
N3K:
default: *n3k_def_interval
ipv6_slow_timer:
_exclude: ['N35', 'N5K', 'N6K']
kind: int
getval: bfd ipv6 slow-timer (\\d+)$
setval: bfd ipv6 slow-timer {0}
default: 2000
# Fabricpath Commands
fabricpath_interval:
_exclude: ['N35', 'N3K', 'N9K']
kind: dict
getval: bfd fabricpath interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
setval: bfd fabricpath interval {tx} min_rx {min_rx} multiplier {multiplier}
default: *def_interval
fabricpath_slow_timer:
_exclude: ['N35', 'N3K', 'N9K']
kind: int
getval: bfd fabricpath slow-timer (\\d+)$
setval: bfd fabricpath slow-timer {0}
default: 2000
fabricpath_vlan:
_exclude: ['N35', 'N3K', 'N9K']
kind: int
getval: bfd fabricpath vlan (\\d+)$
setval: bfd fabricpath vlan {0}
default: 1
"""
def reorder_cmds(cmds):
'''
There is a bug in some image versions where bfd echo-interface and
bfd echo-rx-interval need to be applied last for them to nvgen properly.
'''
regex1 = re.compile(r'^bfd echo-interface')
regex2 = re.compile(r'^bfd echo-rx-interval')
filtered_cmds = [i for i in cmds if not regex1.match(i)]
filtered_cmds = [i for i in filtered_cmds if not regex2.match(i)]
echo_int_cmd = [i for i in cmds if regex1.match(i)]
echo_rx_cmd = [i for i in cmds if regex2.match(i)]
filtered_cmds.extend(echo_int_cmd)
filtered_cmds.extend(echo_rx_cmd)
return filtered_cmds
def main():
argument_spec = dict(
echo_interface=dict(required=False, type='str'),
echo_rx_interval=dict(required=False, type='int'),
interval=dict(required=False, type='dict'),
slow_timer=dict(required=False, type='int'),
startup_timer=dict(required=False, type='int'),
ipv4_echo_rx_interval=dict(required=False, type='int'),
ipv4_interval=dict(required=False, type='dict'),
ipv4_slow_timer=dict(required=False, type='int'),
ipv6_echo_rx_interval=dict(required=False, type='int'),
ipv6_interval=dict(required=False, type='dict'),
ipv6_slow_timer=dict(required=False, type='int'),
fabricpath_interval=dict(required=False, type='dict'),
fabricpath_slow_timer=dict(required=False, type='int'),
fabricpath_vlan=dict(required=False, type='int'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
cmd_ref = NxosCmdRef(module, BFD_CMD_REF)
cmd_ref.get_existing()
cmd_ref.get_playvals()
cmds = reorder_cmds(cmd_ref.get_proposed())
result = {'changed': False, 'commands': cmds, 'warnings': warnings,
'check_mode': module.check_mode}
if cmds:
result['changed'] = True
if not module.check_mode:
load_config(module, cmds)
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,173 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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 nxos_bfd_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: nxos_bfd_interfaces
version_added: 2.9
short_description: 'Manages BFD attributes of nxos interfaces.'
description: 'Manages attributes of Bidirectional Forwarding Detection (BFD) on the interface.'
author: Chris Van Heuveln (@chrisvanheuveln)
notes:
options:
config:
description: The provided configuration
type: list
elements: dict
suboptions:
name:
type: str
description: The name of the interface.
bfd:
type: str
description:
- Enable/Disable Bidirectional Forwarding Detection (BFD) on the interface.
choices:
- enable
- disable
echo:
type: str
description:
- Enable/Disable BFD Echo functionality on the interface.
choices:
- enable
- disable
state:
description:
- The state of the configuration after module completion
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
"""
EXAMPLES = """
# Using deleted
- name: Configure interfaces
nxos_bfd_interfaces:
state: deleted
# Using merged
- name: Configure interfaces
nxos_bfd_interfaces:
config:
- name: Ethernet1/1
bfd: enable
echo: enable
- name: Ethernet1/2
bfd: disable
echo: disable
state: merged
# Using overridden
- name: Configure interfaces
nxos_bfd_interfaces:
config:
- name: Ethernet1/1
bfd: enable
echo: enable
- name: Ethernet1/2
bfd: disable
echo: disable
state: overridden
# Using replaced
- name: Configure interfaces
nxos_bfd_interfaces:
config:
- name: Ethernet1/1
bfd: enable
echo: enable
- name: Ethernet1/2
bfd: disable
echo: disable
state: replaced
"""
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 Ethernet1/1', 'no bfd', 'no bfd echo']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import Bfd_interfacesArgs
from ansible.module_utils.network.nxos.config.bfd_interfaces.bfd_interfaces import Bfd_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Bfd_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Bfd_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,663 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_bgp
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages BGP configuration.
description:
- Manages BGP configurations on NX-OS switches.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state=absent) removes the whole BGP ASN configuration when
C(vrf=default) or the whole VRF instance within the BGP process when
using a different VRF.
- Default when supported restores params default value.
- Configuring global params is only permitted if C(vrf=default).
options:
asn:
description:
- BGP autonomous system number. Valid values are String,
Integer in ASPLAIN or ASDOT notation.
required: true
vrf:
description:
- Name of the VRF. The name 'default' is a valid VRF representing
the global BGP.
bestpath_always_compare_med:
description:
- Enable/Disable MED comparison on paths from different
autonomous systems.
type: bool
bestpath_aspath_multipath_relax:
description:
- Enable/Disable load sharing across the providers with
different (but equal-length) AS paths.
type: bool
bestpath_compare_routerid:
description:
- Enable/Disable comparison of router IDs for identical eBGP paths.
type: bool
bestpath_compare_neighborid:
description:
- Enable/Disable neighborid. Use this when more paths available than max path config.
type: bool
bestpath_cost_community_ignore:
description:
- Enable/Disable Ignores the cost community for BGP best-path
calculations.
type: bool
bestpath_med_confed:
description:
- Enable/Disable enforcement of bestpath to do a MED comparison
only between paths originated within a confederation.
type: bool
bestpath_med_missing_as_worst:
description:
- Enable/Disable assigns the value of infinity to received
routes that do not carry the MED attribute, making these routes
the least desirable.
type: bool
bestpath_med_non_deterministic:
description:
- Enable/Disable deterministic selection of the best MED pat
from among the paths from the same autonomous system.
type: bool
cluster_id:
description:
- Route Reflector Cluster-ID.
confederation_id:
description:
- Routing domain confederation AS.
confederation_peers:
description:
- AS confederation parameters.
disable_policy_batching:
description:
- Enable/Disable the batching evaluation of prefix advertisement
to all peers.
type: bool
disable_policy_batching_ipv4_prefix_list:
description:
- Enable/Disable the batching evaluation of prefix advertisements
to all peers with prefix list.
disable_policy_batching_ipv6_prefix_list:
description:
- Enable/Disable the batching evaluation of prefix advertisements
to all peers with prefix list.
enforce_first_as:
description:
- Enable/Disable enforces the neighbor autonomous system to be
the first AS number listed in the AS path attribute for eBGP.
On NX-OS, this property is only supported in the
global BGP context.
type: bool
event_history_cli:
description:
- Enable/Disable cli event history buffer.
choices: ['size_small', 'size_medium', 'size_large', 'size_disable', 'default']
event_history_detail:
description:
- Enable/Disable detail event history buffer.
choices: ['size_small', 'size_medium', 'size_large', 'size_disable', 'default']
event_history_events:
description:
- Enable/Disable event history buffer.
choices: ['size_small', 'size_medium', 'size_large', 'size_disable', 'default']
event_history_periodic:
description:
- Enable/Disable periodic event history buffer.
choices: ['size_small', 'size_medium', 'size_large', 'size_disable', 'default']
fast_external_fallover:
description:
- Enable/Disable immediately reset the session if the link to a
directly connected BGP peer goes down. Only supported in the
global BGP context.
type: bool
flush_routes:
description:
- Enable/Disable flush routes in RIB upon controlled restart.
On NX-OS, this property is only supported in the global
BGP context.
type: bool
graceful_restart:
description:
- Enable/Disable graceful restart.
type: bool
graceful_restart_helper:
description:
- Enable/Disable graceful restart helper mode.
type: bool
graceful_restart_timers_restart:
description:
- Set maximum time for a restart sent to the BGP peer.
graceful_restart_timers_stalepath_time:
description:
- Set maximum time that BGP keeps the stale routes from the
restarting BGP peer.
isolate:
description:
- Enable/Disable isolate this router from BGP perspective.
type: bool
local_as:
description:
- Local AS number to be used within a VRF instance.
log_neighbor_changes:
description:
- Enable/Disable message logging for neighbor up/down event.
type: bool
maxas_limit:
description:
- Specify Maximum number of AS numbers allowed in the AS-path
attribute. Valid values are between 1 and 512.
neighbor_down_fib_accelerate:
description:
- Enable/Disable handle BGP neighbor down event, due to
various reasons.
type: bool
reconnect_interval:
description:
- The BGP reconnection interval for dropped sessions.
Valid values are between 1 and 60.
router_id:
description:
- Router Identifier (ID) of the BGP router VRF instance.
shutdown:
description:
- Administratively shutdown the BGP protocol.
type: bool
suppress_fib_pending:
description:
- Enable/Disable advertise only routes programmed in hardware
to peers.
type: bool
timer_bestpath_limit:
description:
- Specify timeout for the first best path after a restart,
in seconds.
timer_bgp_hold:
description:
- Set BGP hold timer.
timer_bgp_keepalive:
description:
- Set BGP keepalive timer.
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
- name: Configure a simple ASN
nxos_bgp:
asn: 65535
vrf: test
router_id: 192.0.2.1
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
BOOL_PARAMS = [
'bestpath_always_compare_med',
'bestpath_aspath_multipath_relax',
'bestpath_compare_neighborid',
'bestpath_compare_routerid',
'bestpath_cost_community_ignore',
'bestpath_med_confed',
'bestpath_med_missing_as_worst',
'bestpath_med_non_deterministic',
'disable_policy_batching',
'enforce_first_as',
'fast_external_fallover',
'flush_routes',
'graceful_restart',
'graceful_restart_helper',
'isolate',
'log_neighbor_changes',
'neighbor_down_fib_accelerate',
'shutdown',
'suppress_fib_pending'
]
GLOBAL_PARAMS = [
'disable_policy_batching',
'disable_policy_batching_ipv4_prefix_list',
'disable_policy_batching_ipv6_prefix_list',
'enforce_first_as',
'event_history_cli',
'event_history_detail',
'event_history_events',
'event_history_periodic',
'fast_external_fallover',
'flush_routes',
'isolate',
'suppress_fib_pending',
'shutdown'
]
PARAM_TO_DEFAULT_KEYMAP = {
'timer_bgp_keepalive': '60',
'timer_bgp_hold': '180',
'timer_bestpath_limit': '300',
'graceful_restart': True,
'graceful_restart_timers_restart': '120',
'graceful_restart_timers_stalepath_time': '300',
'reconnect_interval': '60',
'suppress_fib_pending': True,
'fast_external_fallover': True,
'enforce_first_as': True,
'event_history_cli': True,
'event_history_detail': False,
'event_history_events': True,
'event_history_periodic': True,
'maxas_limit': '',
'router_id': '',
'cluster_id': '',
'disable_policy_batching_ipv4_prefix_list': '',
'disable_policy_batching_ipv6_prefix_list': '',
'local_as': '',
'confederation_id': '',
}
PARAM_TO_COMMAND_KEYMAP = {
'asn': 'router bgp',
'bestpath_always_compare_med': 'bestpath always-compare-med',
'bestpath_aspath_multipath_relax': 'bestpath as-path multipath-relax',
'bestpath_compare_neighborid': 'bestpath compare-neighborid',
'bestpath_compare_routerid': 'bestpath compare-routerid',
'bestpath_cost_community_ignore': 'bestpath cost-community ignore',
'bestpath_med_confed': 'bestpath med confed',
'bestpath_med_missing_as_worst': 'bestpath med missing-as-worst',
'bestpath_med_non_deterministic': 'bestpath med non-deterministic',
'cluster_id': 'cluster-id',
'confederation_id': 'confederation identifier',
'confederation_peers': 'confederation peers',
'disable_policy_batching': 'disable-policy-batching',
'disable_policy_batching_ipv4_prefix_list': 'disable-policy-batching ipv4 prefix-list',
'disable_policy_batching_ipv6_prefix_list': 'disable-policy-batching ipv6 prefix-list',
'enforce_first_as': 'enforce-first-as',
'event_history_cli': 'event-history cli',
'event_history_detail': 'event-history detail',
'event_history_events': 'event-history events',
'event_history_periodic': 'event-history periodic',
'fast_external_fallover': 'fast-external-fallover',
'flush_routes': 'flush-routes',
'graceful_restart': 'graceful-restart',
'graceful_restart_helper': 'graceful-restart-helper',
'graceful_restart_timers_restart': 'graceful-restart restart-time',
'graceful_restart_timers_stalepath_time': 'graceful-restart stalepath-time',
'isolate': 'isolate',
'local_as': 'local-as',
'log_neighbor_changes': 'log-neighbor-changes',
'maxas_limit': 'maxas-limit',
'neighbor_down_fib_accelerate': 'neighbor-down fib-accelerate',
'reconnect_interval': 'reconnect-interval',
'router_id': 'router-id',
'shutdown': 'shutdown',
'suppress_fib_pending': 'suppress-fib-pending',
'timer_bestpath_limit': 'timers bestpath-limit',
'timer_bgp_hold': 'timers bgp',
'timer_bgp_keepalive': 'timers bgp',
'vrf': 'vrf'
}
def get_value(arg, config):
command = PARAM_TO_COMMAND_KEYMAP.get(arg)
if command.split()[0] == 'event-history':
has_size = re.search(r'^\s+{0} size\s(?P<value>.*)$'.format(command), config, re.M)
if command == 'event-history detail':
value = False
else:
value = 'size_small'
if has_size:
value = 'size_%s' % has_size.group('value')
elif arg in ['enforce_first_as', 'fast_external_fallover']:
no_command_re = re.compile(r'no\s+{0}\s*'.format(command), re.M)
value = True
if no_command_re.search(config):
value = False
elif arg in BOOL_PARAMS:
has_command = re.search(r'^\s+{0}\s*$'.format(command), config, re.M)
value = False
if has_command:
value = True
else:
command_val_re = re.compile(r'(?:{0}\s)(?P<value>.*)'.format(command), re.M)
value = ''
has_command = command_val_re.search(config)
if has_command:
found_value = has_command.group('value')
if arg == 'confederation_peers':
value = found_value.split()
elif arg == 'timer_bgp_keepalive':
value = found_value.split()[0]
elif arg == 'timer_bgp_hold':
split_values = found_value.split()
if len(split_values) == 2:
value = split_values[1]
elif found_value:
value = found_value
return value
def get_existing(module, args, warnings):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=['bgp all']))
asn_re = re.compile(r'.*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*', re.S)
asn_match = asn_re.match(str(netcfg))
if asn_match:
existing_asn = asn_match.group('existing_asn')
bgp_parent = 'router bgp {0}'.format(existing_asn)
if module.params['vrf'] != 'default':
parents = [bgp_parent, 'vrf {0}'.format(module.params['vrf'])]
else:
parents = [bgp_parent]
config = netcfg.get_section(parents)
if config:
for arg in args:
if arg != 'asn' and (module.params['vrf'] == 'default' or
arg not in GLOBAL_PARAMS):
existing[arg] = get_value(arg, config)
existing['asn'] = existing_asn
if module.params['vrf'] == 'default':
existing['vrf'] = 'default'
if not existing and module.params['vrf'] != 'default' and module.params['state'] == 'present':
msg = ("VRF {0} doesn't exist.".format(module.params['vrf']))
warnings.append(msg)
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = table.get(key)
return new_dict
def state_present(module, existing, proposed, candidate):
commands = list()
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if value is True:
commands.append(key)
elif value is False:
commands.append('no {0}'.format(key))
elif value == 'default':
default_value = PARAM_TO_DEFAULT_KEYMAP.get(key)
existing_value = existing_commands.get(key)
if default_value:
commands.append('{0} {1}'.format(key, default_value))
elif existing_value:
if key == 'confederation peers':
existing_value = ' '.join(existing_value)
commands.append('no {0} {1}'.format(key, existing_value))
elif not value:
existing_value = existing_commands.get(key)
if existing_value:
commands.append('no {0} {1}'.format(key, existing_value))
elif key == 'confederation peers':
commands.append('{0} {1}'.format(key, value))
elif key.startswith('timers bgp'):
command = 'timers bgp {0} {1}'.format(
proposed['timer_bgp_keepalive'],
proposed['timer_bgp_hold'])
if command not in commands:
commands.append(command)
else:
if value.startswith('size'):
value = value.replace('_', ' ')
command = '{0} {1}'.format(key, value)
commands.append(command)
parents = []
if commands:
commands = fix_commands(commands)
parents = ['router bgp {0}'.format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
elif proposed:
if module.params['vrf'] != 'default':
commands.append('vrf {0}'.format(module.params['vrf']))
parents = ['router bgp {0}'.format(module.params['asn'])]
else:
commands.append('router bgp {0}'.format(module.params['asn']))
candidate.add(commands, parents=parents)
def state_absent(module, existing, candidate):
commands = []
parents = []
if module.params['vrf'] == 'default':
commands.append('no router bgp {0}'.format(module.params['asn']))
elif existing.get('vrf') == module.params['vrf']:
commands.append('no vrf {0}'.format(module.params['vrf']))
parents = ['router bgp {0}'.format(module.params['asn'])]
candidate.add(commands, parents=parents)
def fix_commands(commands):
local_as_command = ''
confederation_id_command = ''
confederation_peers_command = ''
for command in commands:
if 'local-as' in command:
local_as_command = command
elif 'confederation identifier' in command:
confederation_id_command = command
elif 'confederation peers' in command:
confederation_peers_command = command
if local_as_command and confederation_id_command:
if 'no' in confederation_id_command:
commands.pop(commands.index(local_as_command))
commands.pop(commands.index(confederation_id_command))
commands.append(confederation_id_command)
commands.append(local_as_command)
else:
commands.pop(commands.index(local_as_command))
commands.pop(commands.index(confederation_id_command))
commands.append(local_as_command)
commands.append(confederation_id_command)
if confederation_peers_command and confederation_id_command:
if local_as_command:
if 'no' in local_as_command:
commands.pop(commands.index(local_as_command))
commands.pop(commands.index(confederation_id_command))
commands.pop(commands.index(confederation_peers_command))
commands.append(confederation_id_command)
commands.append(confederation_peers_command)
commands.append(local_as_command)
else:
commands.pop(commands.index(local_as_command))
commands.pop(commands.index(confederation_id_command))
commands.pop(commands.index(confederation_peers_command))
commands.append(local_as_command)
commands.append(confederation_id_command)
commands.append(confederation_peers_command)
else:
commands.pop(commands.index(confederation_peers_command))
commands.pop(commands.index(confederation_id_command))
commands.append(confederation_id_command)
commands.append(confederation_peers_command)
return commands
def main():
argument_spec = dict(
asn=dict(required=True, type='str'),
vrf=dict(required=False, type='str', default='default'),
bestpath_always_compare_med=dict(required=False, type='bool'),
bestpath_aspath_multipath_relax=dict(required=False, type='bool'),
bestpath_compare_neighborid=dict(required=False, type='bool'),
bestpath_compare_routerid=dict(required=False, type='bool'),
bestpath_cost_community_ignore=dict(required=False, type='bool'),
bestpath_med_confed=dict(required=False, type='bool'),
bestpath_med_missing_as_worst=dict(required=False, type='bool'),
bestpath_med_non_deterministic=dict(required=False, type='bool'),
cluster_id=dict(required=False, type='str'),
confederation_id=dict(required=False, type='str'),
confederation_peers=dict(required=False, type='list'),
disable_policy_batching=dict(required=False, type='bool'),
disable_policy_batching_ipv4_prefix_list=dict(required=False, type='str'),
disable_policy_batching_ipv6_prefix_list=dict(required=False, type='str'),
enforce_first_as=dict(required=False, type='bool'),
event_history_cli=dict(required=False, choices=['true', 'false', 'default', 'size_small', 'size_medium', 'size_large', 'size_disable']),
event_history_detail=dict(required=False, choices=['true', 'false', 'default', 'size_small', 'size_medium', 'size_large', 'size_disable']),
event_history_events=dict(required=False, choices=['true', 'false', 'default', 'size_small', 'size_medium', 'size_large', 'size_disable']),
event_history_periodic=dict(required=False, choices=['true', 'false', 'default', 'size_small', 'size_medium', 'size_large', 'size_disable']),
fast_external_fallover=dict(required=False, type='bool'),
flush_routes=dict(required=False, type='bool'),
graceful_restart=dict(required=False, type='bool'),
graceful_restart_helper=dict(required=False, type='bool'),
graceful_restart_timers_restart=dict(required=False, type='str'),
graceful_restart_timers_stalepath_time=dict(required=False, type='str'),
isolate=dict(required=False, type='bool'),
local_as=dict(required=False, type='str'),
log_neighbor_changes=dict(required=False, type='bool'),
maxas_limit=dict(required=False, type='str'),
neighbor_down_fib_accelerate=dict(required=False, type='bool'),
reconnect_interval=dict(required=False, type='str'),
router_id=dict(required=False, type='str'),
shutdown=dict(required=False, type='bool'),
suppress_fib_pending=dict(required=False, type='bool'),
timer_bestpath_limit=dict(required=False, type='str'),
timer_bgp_hold=dict(required=False, type='str'),
timer_bgp_keepalive=dict(required=False, type='str'),
state=dict(choices=['present', 'absent'], default='present', required=False),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
required_together=[['timer_bgp_hold', 'timer_bgp_keepalive']],
supports_check_mode=True)
warnings = list()
result = dict(changed=False, warnings=warnings)
state = module.params['state']
if module.params['vrf'] != 'default':
for param in GLOBAL_PARAMS:
if module.params[param]:
module.fail_json(msg='Global params can be modified only under "default" VRF.',
vrf=module.params['vrf'],
global_param=param)
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args, warnings)
if existing.get('asn') and state == 'present':
if existing.get('asn') != module.params['asn']:
module.fail_json(msg='Another BGP ASN already exists.',
proposed_asn=module.params['asn'],
existing_asn=existing.get('asn'))
proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
proposed = {}
for key, value in proposed_args.items():
if key not in ['asn', 'vrf']:
if str(value).lower() == 'default':
value = PARAM_TO_DEFAULT_KEYMAP.get(key, 'default')
if key == 'confederation_peers':
if value[0] == 'default':
if existing.get(key):
proposed[key] = 'default'
else:
v = set([int(i) for i in value])
ex = set([int(i) for i in existing.get(key)])
if v != ex:
proposed[key] = ' '.join(str(s) for s in v)
else:
if existing.get(key) != value:
proposed[key] = value
candidate = CustomNetworkConfig(indent=3)
if state == 'present':
state_present(module, existing, proposed, candidate)
elif existing.get('asn') == module.params['asn']:
state_absent(module, existing, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate
else:
result['commands'] = []
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,776 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_bgp_af
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages BGP Address-family configuration.
description:
- Manages BGP Address-family configurations on NX-OS switches.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state=absent) removes the whole BGP ASN configuration
- Default, where supported, restores params default value.
options:
asn:
description:
- BGP autonomous system number. Valid values are String,
Integer in ASPLAIN or ASDOT notation.
required: true
vrf:
description:
- Name of the VRF. The name 'default' is a valid VRF representing
the global bgp.
required: true
afi:
description:
- Address Family Identifier.
required: true
choices: ['ipv4','ipv6', 'vpnv4', 'vpnv6', 'l2vpn']
safi:
description:
- Sub Address Family Identifier.
required: true
choices: ['unicast','multicast', 'evpn']
additional_paths_install:
description:
- Install a backup path into the forwarding table and provide
prefix independent convergence (PIC) in case of a PE-CE link
failure.
type: bool
additional_paths_receive:
description:
- Enables the receive capability of additional paths for all of
the neighbors under this address family for which the capability
has not been disabled.
type: bool
additional_paths_selection:
description:
- Configures the capability of selecting additional paths for
a prefix. Valid values are a string defining the name of
the route-map.
additional_paths_send:
description:
- Enables the send capability of additional paths for all of
the neighbors under this address family for which the capability
has not been disabled.
type: bool
advertise_l2vpn_evpn:
description:
- Advertise evpn routes.
type: bool
client_to_client:
description:
- Configure client-to-client route reflection.
type: bool
dampen_igp_metric:
description:
- Specify dampen value for IGP metric-related changes, in seconds.
Valid values are integer and keyword 'default'.
dampening_state:
description:
- Enable/disable route-flap dampening.
type: bool
dampening_half_time:
description:
- Specify decay half-life in minutes for route-flap dampening.
Valid values are integer and keyword 'default'.
dampening_max_suppress_time:
description:
- Specify max suppress time for route-flap dampening stable route.
Valid values are integer and keyword 'default'.
dampening_reuse_time:
description:
- Specify route reuse time for route-flap dampening.
Valid values are integer and keyword 'default'.
dampening_routemap:
description:
- Specify route-map for route-flap dampening. Valid values are a
string defining the name of the route-map.
dampening_suppress_time:
description:
- Specify route suppress time for route-flap dampening.
Valid values are integer and keyword 'default'.
default_information_originate:
description:
- Default information originate.
type: bool
default_metric:
description:
- Sets default metrics for routes redistributed into BGP.
Valid values are Integer or keyword 'default'
distance_ebgp:
description:
- Sets the administrative distance for eBGP routes.
Valid values are Integer or keyword 'default'.
distance_ibgp:
description:
- Sets the administrative distance for iBGP routes.
Valid values are Integer or keyword 'default'.
distance_local:
description:
- Sets the administrative distance for local BGP routes.
Valid values are Integer or keyword 'default'.
inject_map:
description:
- An array of route-map names which will specify prefixes to
inject. Each array entry must first specify the inject-map name,
secondly an exist-map name, and optionally the copy-attributes
keyword which indicates that attributes should be copied from
the aggregate. For example [['lax_inject_map', 'lax_exist_map'],
['nyc_inject_map', 'nyc_exist_map', 'copy-attributes'],
['fsd_inject_map', 'fsd_exist_map']].
maximum_paths:
description:
- Configures the maximum number of equal-cost paths for
load sharing. Valid value is an integer in the range 1-64.
maximum_paths_ibgp:
description:
- Configures the maximum number of ibgp equal-cost paths for
load sharing. Valid value is an integer in the range 1-64.
networks:
description:
- Networks to configure. Valid value is a list of network
prefixes to advertise. The list must be in the form of an array.
Each entry in the array must include a prefix address and an
optional route-map. For example [['10.0.0.0/16', 'routemap_LA'],
['192.168.1.1', 'Chicago'], ['192.168.2.0/24'],
['192.168.3.0/24', 'routemap_NYC']].
next_hop_route_map:
description:
- Configure a route-map for valid nexthops. Valid values are a
string defining the name of the route-map.
redistribute:
description:
- A list of redistribute directives. Multiple redistribute entries
are allowed. The list must be in the form of a nested array.
the first entry of each array defines the source-protocol to
redistribute from; the second entry defines a route-map name.
A route-map is highly advised but may be optional on some
platforms, in which case it may be omitted from the array list.
For example [['direct', 'rm_direct'], ['lisp', 'rm_lisp']].
suppress_inactive:
description:
- Advertises only active routes to peers.
type: bool
table_map:
description:
- Apply table-map to filter routes downloaded into URIB.
Valid values are a string.
table_map_filter:
description:
- Filters routes rejected by the route-map and does not download
them to the RIB.
type: bool
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# configure a simple address-family
- nxos_bgp_af:
asn: 65535
vrf: TESTING
afi: ipv4
safi: unicast
advertise_l2vpn_evpn: true
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["router bgp 65535", "vrf TESTING",
"address-family ipv4 unicast", "advertise l2vpn evpn"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
BOOL_PARAMS = [
'additional_paths_install',
'additional_paths_receive',
'additional_paths_send',
'advertise_l2vpn_evpn',
'dampening_state',
'default_information_originate',
'suppress_inactive',
]
PARAM_TO_DEFAULT_KEYMAP = {
'maximum_paths': '1',
'maximum_paths_ibgp': '1',
'client_to_client': True,
'distance_ebgp': '20',
'distance_ibgp': '200',
'distance_local': '220',
'dampen_igp_metric': '600'
}
PARAM_TO_COMMAND_KEYMAP = {
'asn': 'router bgp',
'afi': 'address-family',
'safi': 'address-family',
'additional_paths_install': 'additional-paths install backup',
'additional_paths_receive': 'additional-paths receive',
'additional_paths_selection': 'additional-paths selection route-map',
'additional_paths_send': 'additional-paths send',
'advertise_l2vpn_evpn': 'advertise l2vpn evpn',
'client_to_client': 'client-to-client reflection',
'dampen_igp_metric': 'dampen-igp-metric',
'dampening_state': 'dampening',
'dampening_half_time': 'dampening',
'dampening_max_suppress_time': 'dampening',
'dampening_reuse_time': 'dampening',
'dampening_routemap': 'dampening route-map',
'dampening_suppress_time': 'dampening',
'default_information_originate': 'default-information originate',
'default_metric': 'default-metric',
'distance_ebgp': 'distance',
'distance_ibgp': 'distance',
'distance_local': 'distance',
'inject_map': 'inject-map',
'maximum_paths': 'maximum-paths',
'maximum_paths_ibgp': 'maximum-paths ibgp',
'networks': 'network',
'redistribute': 'redistribute',
'next_hop_route_map': 'nexthop route-map',
'suppress_inactive': 'suppress-inactive',
'table_map': 'table-map',
'table_map_filter': 'table-map-filter',
'vrf': 'vrf'
}
DAMPENING_PARAMS = [
'dampening_half_time',
'dampening_suppress_time',
'dampening_reuse_time',
'dampening_max_suppress_time'
]
def get_value(arg, config, module):
command = PARAM_TO_COMMAND_KEYMAP[arg]
command_val_re = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(command), re.M)
has_command_val = command_val_re.search(config)
if arg in ['networks', 'redistribute', 'inject_map']:
value = []
for ele in command_val_re.findall(config):
tl = ele.split()
if 'exist-map' in tl:
tl.remove('exist-map')
elif 'route-map' in tl:
tl.remove('route-map')
value.append(tl)
elif command == 'distance':
distance_re = r'.*distance\s(?P<d_ebgp>\w+)\s(?P<d_ibgp>\w+)\s(?P<d_local>\w+)'
match_distance = re.match(distance_re, config, re.DOTALL)
value = ''
if match_distance:
distance_group = match_distance.groupdict()
if arg == 'distance_ebgp':
value = distance_group['d_ebgp']
elif arg == 'distance_ibgp':
value = distance_group['d_ibgp']
elif arg == 'distance_local':
value = distance_group['d_local']
elif command.split()[0] == 'dampening':
value = ''
if arg == 'dampen_igp_metric' or arg == 'dampening_routemap':
if command in config:
value = has_command_val.group('value')
else:
dampening_re = r'.*dampening\s(?P<half>\w+)\s(?P<reuse>\w+)\s(?P<suppress>\w+)\s(?P<max_suppress>\w+)'
match_dampening = re.match(dampening_re, config, re.DOTALL)
if match_dampening:
dampening_group = match_dampening.groupdict()
if arg == 'dampening_half_time':
value = dampening_group['half']
elif arg == 'dampening_reuse_time':
value = dampening_group['reuse']
elif arg == 'dampening_suppress_time':
value = dampening_group['suppress']
elif arg == 'dampening_max_suppress_time':
value = dampening_group['max_suppress']
else:
if arg == 'dampening_state':
value = True if 'dampening' in config else False
elif arg == 'table_map_filter':
tmf_regex = re.compile(r'\s+table-map.*filter$', re.M)
value = False
if tmf_regex.search(config):
value = True
elif arg == 'table_map':
tm_regex = re.compile(r'(?:table-map\s)(?P<value>\S+)(\sfilter)?$', re.M)
has_tablemap = tm_regex.search(config)
value = ''
if has_tablemap:
value = has_tablemap.group('value')
elif arg == 'client_to_client':
no_command_re = re.compile(r'^\s+no\s{0}\s*$'.format(command), re.M)
value = True
if no_command_re.search(config):
value = False
elif arg in BOOL_PARAMS:
command_re = re.compile(r'^\s+{0}\s*$'.format(command), re.M)
value = False
if command_re.search(config):
value = True
else:
value = ''
if has_command_val:
value = has_command_val.group('value')
return value
def get_existing(module, args, warnings):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
asn_regex = re.compile(r'.*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*', re.DOTALL)
match_asn = asn_regex.match(str(netcfg))
if match_asn:
existing_asn = match_asn.group('existing_asn')
parents = ["router bgp {0}".format(existing_asn)]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('address-family {0} {1}'.format(module.params['afi'], module.params['safi']))
config = netcfg.get_section(parents)
if config:
for arg in args:
if arg not in ['asn', 'afi', 'safi', 'vrf']:
gv = get_value(arg, config, module)
if gv:
existing[arg] = gv
else:
if arg != 'client_to_client' and arg in PARAM_TO_DEFAULT_KEYMAP.keys():
existing[arg] = PARAM_TO_DEFAULT_KEYMAP.get(arg)
else:
existing[arg] = gv
existing['asn'] = existing_asn
existing['afi'] = module.params['afi']
existing['safi'] = module.params['safi']
existing['vrf'] = module.params['vrf']
else:
warnings.append("The BGP process {0} didn't exist but the task just created it.".format(module.params['asn']))
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key, value in table.items():
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = value
return new_dict
def fix_proposed(module, proposed, existing):
commands = list()
command = ''
fixed_proposed = {}
for key, value in proposed.items():
if key in DAMPENING_PARAMS:
if value != 'default':
command = 'dampening {0} {1} {2} {3}'.format(
proposed.get('dampening_half_time'),
proposed.get('dampening_reuse_time'),
proposed.get('dampening_suppress_time'),
proposed.get('dampening_max_suppress_time'))
else:
if existing.get(key):
command = ('no dampening {0} {1} {2} {3}'.format(
existing['dampening_half_time'],
existing['dampening_reuse_time'],
existing['dampening_suppress_time'],
existing['dampening_max_suppress_time']))
if 'default' in command:
command = ''
elif key.startswith('distance'):
command = 'distance {0} {1} {2}'.format(
proposed.get('distance_ebgp'),
proposed.get('distance_ibgp'),
proposed.get('distance_local'))
else:
fixed_proposed[key] = value
if command:
if command not in commands:
commands.append(command)
return fixed_proposed, commands
def default_existing(existing_value, key, value):
commands = []
if key == 'network':
for network in existing_value:
if len(network) == 2:
commands.append('no network {0} route-map {1}'.format(
network[0], network[1]))
elif len(network) == 1:
commands.append('no network {0}'.format(
network[0]))
elif key == 'inject-map':
for maps in existing_value:
if len(maps) == 2:
commands.append('no inject-map {0} exist-map {1}'.format(maps[0], maps[1]))
elif len(maps) == 3:
commands.append('no inject-map {0} exist-map {1} '
'copy-attributes'.format(maps[0], maps[1]))
elif key == 'redistribute':
for maps in existing_value:
commands.append('no redistribute {0} route-map {1}'.format(maps[0], maps[1]))
else:
commands.append('no {0} {1}'.format(key, existing_value))
return commands
def get_network_command(existing, key, value):
commands = []
existing_networks = existing.get('networks', [])
for inet in value:
if not isinstance(inet, list):
inet = [inet]
if inet not in existing_networks:
if len(inet) == 1:
command = '{0} {1}'.format(key, inet[0])
elif len(inet) == 2:
command = '{0} {1} route-map {2}'.format(key, inet[0], inet[1])
if command:
commands.append(command)
for enet in existing_networks:
if enet not in value:
if len(enet) == 1:
command = 'no {0} {1}'.format(key, enet[0])
elif len(enet) == 2:
command = 'no {0} {1} route-map {2}'.format(key, enet[0], enet[1])
if command:
commands.append(command)
return commands
def get_inject_map_command(existing, key, value):
commands = []
existing_maps = existing.get('inject_map', [])
for maps in value:
if not isinstance(maps, list):
maps = [maps]
if maps not in existing_maps:
if len(maps) == 2:
command = ('inject-map {0} exist-map {1}'.format(
maps[0], maps[1]))
elif len(maps) == 3:
command = ('inject-map {0} exist-map {1} '
'copy-attributes'.format(maps[0],
maps[1]))
if command:
commands.append(command)
for emaps in existing_maps:
if emaps not in value:
if len(emaps) == 2:
command = ('no inject-map {0} exist-map {1}'.format(
emaps[0], emaps[1]))
elif len(emaps) == 3:
command = ('no inject-map {0} exist-map {1} '
'copy-attributes'.format(emaps[0],
emaps[1]))
if command:
commands.append(command)
return commands
def get_redistribute_command(existing, key, value):
commands = []
existing_rules = existing.get('redistribute', [])
for rule in value:
if not isinstance(rule, list):
rule = [rule]
if rule not in existing_rules:
command = ('redistribute {0} route-map {1}'.format(
rule[0], rule[1]))
commands.append(command)
for erule in existing_rules:
if erule not in value:
command = ('no redistribute {0} route-map {1}'.format(
erule[0], erule[1]))
commands.append(command)
return commands
def get_table_map_command(module, existing, key, value):
commands = []
if key == 'table-map':
if value != 'default':
command = '{0} {1}'.format(key, module.params['table_map'])
if (module.params['table_map_filter'] is not None and
module.params['table_map_filter'] != 'default'):
command += ' filter'
commands.append(command)
else:
if existing.get('table_map'):
command = 'no {0} {1}'.format(key, existing.get('table_map'))
commands.append(command)
return commands
def get_default_table_map_filter(existing):
commands = []
existing_table_map_filter = existing.get('table_map_filter')
if existing_table_map_filter:
existing_table_map = existing.get('table_map')
if existing_table_map:
command = 'table-map {0}'.format(existing_table_map)
commands.append(command)
return commands
def state_present(module, existing, proposed, candidate):
fixed_proposed, commands = fix_proposed(module, proposed, existing)
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, fixed_proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if key == 'address-family':
addr_family_command = "address-family {0} {1}".format(
module.params['afi'], module.params['safi'])
if addr_family_command not in commands:
commands.append(addr_family_command)
elif key.startswith('table-map'):
table_map_commands = get_table_map_command(module, existing, key, value)
if table_map_commands:
commands.extend(table_map_commands)
elif value is True:
commands.append(key)
elif value is False:
commands.append('no {0}'.format(key))
elif value == 'default':
if key in PARAM_TO_DEFAULT_KEYMAP:
commands.append('{0} {1}'.format(key, PARAM_TO_DEFAULT_KEYMAP[key]))
elif existing_commands.get(key):
if key == 'table-map-filter':
default_tmf_command = get_default_table_map_filter(existing)
if default_tmf_command:
commands.extend(default_tmf_command)
else:
existing_value = existing_commands.get(key)
default_command = default_existing(existing_value, key, value)
if default_command:
commands.extend(default_command)
else:
if key == 'network':
network_commands = get_network_command(existing, key, value)
if network_commands:
commands.extend(network_commands)
elif key == 'inject-map':
inject_map_commands = get_inject_map_command(existing, key, value)
if inject_map_commands:
commands.extend(inject_map_commands)
elif key == 'redistribute':
redistribute_commands = get_redistribute_command(existing, key, value)
if redistribute_commands:
commands.extend(redistribute_commands)
else:
command = '{0} {1}'.format(key, value)
commands.append(command)
if commands:
parents = ["router bgp {0}".format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
addr_family_command = "address-family {0} {1}".format(module.params['afi'],
module.params['safi'])
parents.append(addr_family_command)
if addr_family_command in commands:
commands.remove(addr_family_command)
candidate.add(commands, parents=parents)
def state_absent(module, candidate):
commands = []
parents = ["router bgp {0}".format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
commands.append('no address-family {0} {1}'.format(
module.params['afi'], module.params['safi']))
candidate.add(commands, parents=parents)
def main():
argument_spec = dict(
asn=dict(required=True, type='str'),
vrf=dict(required=False, type='str', default='default'),
safi=dict(required=True, type='str', choices=['unicast', 'multicast', 'evpn']),
afi=dict(required=True, type='str', choices=['ipv4', 'ipv6', 'vpnv4', 'vpnv6', 'l2vpn']),
additional_paths_install=dict(required=False, type='bool'),
additional_paths_receive=dict(required=False, type='bool'),
additional_paths_selection=dict(required=False, type='str'),
additional_paths_send=dict(required=False, type='bool'),
advertise_l2vpn_evpn=dict(required=False, type='bool'),
client_to_client=dict(required=False, type='bool'),
dampen_igp_metric=dict(required=False, type='str'),
dampening_state=dict(required=False, type='bool'),
dampening_half_time=dict(required=False, type='str'),
dampening_max_suppress_time=dict(required=False, type='str'),
dampening_reuse_time=dict(required=False, type='str'),
dampening_routemap=dict(required=False, type='str'),
dampening_suppress_time=dict(required=False, type='str'),
default_information_originate=dict(required=False, type='bool'),
default_metric=dict(required=False, type='str'),
distance_ebgp=dict(required=False, type='str'),
distance_ibgp=dict(required=False, type='str'),
distance_local=dict(required=False, type='str'),
inject_map=dict(required=False, type='list'),
maximum_paths=dict(required=False, type='str'),
maximum_paths_ibgp=dict(required=False, type='str'),
networks=dict(required=False, type='list'),
next_hop_route_map=dict(required=False, type='str'),
redistribute=dict(required=False, type='list'),
suppress_inactive=dict(required=False, type='bool'),
table_map=dict(required=False, type='str'),
table_map_filter=dict(required=False, type='bool'),
state=dict(choices=['present', 'absent'], default='present', required=False),
)
argument_spec.update(nxos_argument_spec)
mutually_exclusive = [('dampening_state', 'dampening_routemap'),
('dampening_state', 'dampening_half_time'),
('dampening_state', 'dampening_suppress_time'),
('dampening_state', 'dampening_reuse_time'),
('dampening_state', 'dampening_max_suppress_time'),
('dampening_routemap', 'dampening_half_time'),
('dampening_routemap', 'dampening_suppress_time'),
('dampening_routemap', 'dampening_reuse_time'),
('dampening_routemap', 'dampening_max_suppress_time')]
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_together=[DAMPENING_PARAMS, ['distance_ibgp', 'distance_ebgp', 'distance_local']],
supports_check_mode=True,
)
warnings = list()
result = dict(changed=False, warnings=warnings)
state = module.params['state']
if module.params['advertise_l2vpn_evpn']:
if module.params['vrf'] == 'default':
module.fail_json(msg='It is not possible to advertise L2VPN '
'EVPN in the default VRF. Please specify '
'another one.', vrf=module.params['vrf'])
if module.params['table_map_filter'] and not module.params['table_map']:
module.fail_json(msg='table_map param is needed when using'
' table_map_filter filter.')
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args, warnings)
if existing.get('asn') and state == 'present':
if existing.get('asn') != module.params['asn']:
module.fail_json(msg='Another BGP ASN already exists.',
proposed_asn=module.params['asn'],
existing_asn=existing.get('asn'))
proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
for arg in ['networks', 'inject_map', 'redistribute']:
if proposed_args.get(arg):
if proposed_args[arg][0] == 'default':
proposed_args[arg] = 'default'
proposed = {}
for key, value in proposed_args.items():
if key not in ['asn', 'vrf']:
if str(value).lower() == 'default':
value = PARAM_TO_DEFAULT_KEYMAP.get(key, 'default')
if existing.get(key) != value:
proposed[key] = value
candidate = CustomNetworkConfig(indent=3)
if state == 'present':
state_present(module, existing, proposed, candidate)
elif state == 'absent' and existing:
state_absent(module, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate
else:
result['commands'] = []
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,488 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_bgp_neighbor
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages BGP neighbors configurations.
description:
- Manages BGP neighbors configurations on NX-OS switches.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state=absent) removes the whole BGP neighbor configuration.
- Default, where supported, restores params default value.
options:
asn:
description:
- BGP autonomous system number. Valid values are string,
Integer in ASPLAIN or ASDOT notation.
required: true
vrf:
description:
- Name of the VRF. The name 'default' is a valid VRF representing
the global bgp.
default: default
neighbor:
description:
- Neighbor Identifier. Valid values are string. Neighbors may use
IPv4 or IPv6 notation, with or without prefix length.
required: true
description:
description:
- Description of the neighbor.
bfd:
description:
- Enables/Disables BFD for a given neighbor.
- "Dependency: 'feature bfd'"
version_added: "2.9"
type: str
choices: ['enable', 'disable']
connected_check:
description:
- Configure whether or not to check for directly connected peer.
type: bool
capability_negotiation:
description:
- Configure whether or not to negotiate capability with
this neighbor.
type: bool
dynamic_capability:
description:
- Configure whether or not to enable dynamic capability.
type: bool
ebgp_multihop:
description:
- Specify multihop TTL for a remote peer. Valid values are
integers between 2 and 255, or keyword 'default' to disable
this property.
local_as:
description:
- Specify the local-as number for the eBGP neighbor.
Valid values are String or Integer in ASPLAIN or ASDOT notation,
or 'default', which means not to configure it.
log_neighbor_changes:
description:
- Specify whether or not to enable log messages for neighbor
up/down event.
choices: ['enable', 'disable', 'inherit']
low_memory_exempt:
description:
- Specify whether or not to shut down this neighbor under
memory pressure.
type: bool
maximum_peers:
description:
- Specify Maximum number of peers for this neighbor prefix
Valid values are between 1 and 1000, or 'default', which does
not impose the limit. Note that this parameter is accepted
only on neighbors with address/prefix.
pwd:
description:
- Specify the password for neighbor. Valid value is string.
pwd_type:
description:
- Specify the encryption type the password will use. Valid values
are '3des' or 'cisco_type_7' encryption or keyword 'default'.
choices: ['3des', 'cisco_type_7', 'default']
remote_as:
description:
- Specify Autonomous System Number of the neighbor.
Valid values are String or Integer in ASPLAIN or ASDOT notation,
or 'default', which means not to configure it.
remove_private_as:
description:
- Specify the config to remove private AS number from outbound
updates. Valid values are 'enable' to enable this config,
'disable' to disable this config, 'all' to remove all
private AS number, or 'replace-as', to replace the private
AS number.
choices: ['enable', 'disable', 'all', 'replace-as']
shutdown:
description:
- Configure to administratively shutdown this neighbor.
type: bool
suppress_4_byte_as:
description:
- Configure to suppress 4-byte AS Capability.
type: bool
timers_keepalive:
description:
- Specify keepalive timer value. Valid values are integers
between 0 and 3600 in terms of seconds, or 'default',
which is 60.
timers_holdtime:
description:
- Specify holdtime timer value. Valid values are integers between
0 and 3600 in terms of seconds, or 'default', which is 180.
transport_passive_only:
description:
- Specify whether or not to only allow passive connection setup.
Valid values are 'true', 'false', and 'default', which defaults
to 'false'. This property can only be configured when the
neighbor is in 'ip' address format without prefix length.
type: bool
update_source:
description:
- Specify source interface of BGP session and updates.
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# create a new neighbor
- nxos_bgp_neighbor:
asn: 65535
neighbor: 192.0.2.3
local_as: 20
remote_as: 30
bfd: enable
description: "just a description"
update_source: Ethernet1/3
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["router bgp 65535", "neighbor 192.0.2.3",
"remote-as 30", "update-source Ethernet1/3",
"description just a description", "local-as 20"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
BOOL_PARAMS = [
'capability_negotiation',
'shutdown',
'connected_check',
'dynamic_capability',
'low_memory_exempt',
'suppress_4_byte_as',
'transport_passive_only',
]
PARAM_TO_COMMAND_KEYMAP = {
'asn': 'router bgp',
'bfd': 'bfd',
'capability_negotiation': 'dont-capability-negotiate',
'connected_check': 'disable-connected-check',
'description': 'description',
'dynamic_capability': 'dynamic-capability',
'ebgp_multihop': 'ebgp-multihop',
'local_as': 'local-as',
'log_neighbor_changes': 'log-neighbor-changes',
'low_memory_exempt': 'low-memory exempt',
'maximum_peers': 'maximum-peers',
'neighbor': 'neighbor',
'pwd': 'password',
'pwd_type': 'password',
'remote_as': 'remote-as',
'remove_private_as': 'remove-private-as',
'shutdown': 'shutdown',
'suppress_4_byte_as': 'capability suppress 4-byte-as',
'timers_keepalive': 'timers',
'timers_holdtime': 'timers',
'transport_passive_only': 'transport connection-mode passive',
'update_source': 'update-source',
'vrf': 'vrf'
}
PARAM_TO_DEFAULT_KEYMAP = {
'bfd': 'disable',
'shutdown': False,
'dynamic_capability': True,
'timers_keepalive': 60,
'timers_holdtime': 180
}
def get_value(arg, config):
command = PARAM_TO_COMMAND_KEYMAP[arg]
has_command = re.search(r'^\s+{0}$'.format(command), config, re.M)
has_command_val = re.search(r'(?:\s+{0}\s*)(?P<value>.*)$'.format(command), config, re.M)
if arg == 'dynamic_capability':
has_no_command = re.search(r'\s+no\s{0}\s*$'.format(command), config, re.M)
value = True
if has_no_command:
value = False
elif arg in BOOL_PARAMS:
value = False
if has_command:
value = True
elif arg == 'log_neighbor_changes':
value = ''
if has_command:
value = 'enable'
elif has_command_val:
value = 'disable'
elif arg == 'remove_private_as':
value = 'disable'
if has_command:
value = 'enable'
elif has_command_val:
value = has_command_val.group('value')
elif arg == 'bfd':
value = 'enable' if has_command else 'disable'
else:
value = ''
if has_command_val:
value = has_command_val.group('value')
if command in ['timers', 'password']:
split_value = value.split()
value = ''
if arg in ['timers_keepalive', 'pwd_type']:
value = split_value[0]
elif arg in ['timers_holdtime', 'pwd'] and len(split_value) == 2:
value = split_value[1]
return value
def get_existing(module, args, warnings):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
asn_regex = re.compile(r'.*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*', re.S)
match_asn = asn_regex.match(str(netcfg))
if match_asn:
existing_asn = match_asn.group('existing_asn')
parents = ["router bgp {0}".format(existing_asn)]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('neighbor {0}'.format(module.params['neighbor']))
config = netcfg.get_section(parents)
if config:
for arg in args:
if arg not in ['asn', 'vrf', 'neighbor']:
existing[arg] = get_value(arg, config)
existing['asn'] = existing_asn
existing['neighbor'] = module.params['neighbor']
existing['vrf'] = module.params['vrf']
else:
warnings.append("The BGP process didn't exist but the task"
" just created it.")
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = table.get(key)
return new_dict
def state_present(module, existing, proposed, candidate):
commands = list()
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if value is True:
commands.append(key)
elif value is False:
commands.append('no {0}'.format(key))
elif value == 'default':
if existing_commands.get(key):
if key == 'password':
commands.append("no password")
else:
existing_value = existing_commands.get(key)
commands.append('no {0} {1}'.format(key, existing_value))
else:
if key == 'log-neighbor-changes':
if value == 'enable':
commands.append('{0}'.format(key))
elif value == 'disable':
commands.append('{0} {1}'.format(key, value))
elif value == 'inherit':
if existing_commands.get(key):
commands.append('no {0}'.format(key))
elif key == 'password':
pwd_type = module.params['pwd_type']
if pwd_type == '3des':
pwd_type = 3
else:
pwd_type = 7
command = '{0} {1} {2}'.format(key, pwd_type, value)
if command not in commands:
commands.append(command)
elif key == 'remove-private-as':
if value == 'enable':
command = '{0}'.format(key)
commands.append(command)
elif value == 'disable':
if existing_commands.get(key) != 'disable':
command = 'no {0}'.format(key)
commands.append(command)
else:
command = '{0} {1}'.format(key, value)
commands.append(command)
elif key == 'timers':
if (proposed['timers_keepalive'] != PARAM_TO_DEFAULT_KEYMAP.get('timers_keepalive') or
proposed['timers_holdtime'] != PARAM_TO_DEFAULT_KEYMAP.get('timers_holdtime')):
command = 'timers {0} {1}'.format(
proposed['timers_keepalive'],
proposed['timers_holdtime'])
if command not in commands:
commands.append(command)
elif key == 'bfd':
no_cmd = 'no ' if value == 'disable' else ''
commands.append(no_cmd + key)
else:
command = '{0} {1}'.format(key, value)
commands.append(command)
if commands:
parents = ['router bgp {0}'.format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('neighbor {0}'.format(module.params['neighbor']))
# make sure that local-as is the last command in the list.
local_as_command = 'local-as {0}'.format(module.params['local_as'])
if local_as_command in commands:
commands.remove(local_as_command)
commands.append(local_as_command)
candidate.add(commands, parents=parents)
def state_absent(module, existing, proposed, candidate):
commands = []
parents = ["router bgp {0}".format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
commands.append('no neighbor {0}'.format(module.params['neighbor']))
candidate.add(commands, parents=parents)
def main():
argument_spec = dict(
asn=dict(required=True, type='str'),
vrf=dict(required=False, type='str', default='default'),
neighbor=dict(required=True, type='str'),
description=dict(required=False, type='str'),
bfd=dict(required=False, type='str', choices=['enable', 'disable']),
capability_negotiation=dict(required=False, type='bool'),
connected_check=dict(required=False, type='bool'),
dynamic_capability=dict(required=False, type='bool'),
ebgp_multihop=dict(required=False, type='str'),
local_as=dict(required=False, type='str'),
log_neighbor_changes=dict(required=False, type='str', choices=['enable', 'disable', 'inherit']),
low_memory_exempt=dict(required=False, type='bool'),
maximum_peers=dict(required=False, type='str'),
pwd=dict(required=False, type='str'),
pwd_type=dict(required=False, type='str', choices=['3des', 'cisco_type_7', 'default']),
remote_as=dict(required=False, type='str'),
remove_private_as=dict(required=False, type='str', choices=['enable', 'disable', 'all', 'replace-as']),
shutdown=dict(required=False, type='bool'),
suppress_4_byte_as=dict(required=False, type='bool'),
timers_keepalive=dict(required=False, type='str'),
timers_holdtime=dict(required=False, type='str'),
transport_passive_only=dict(required=False, type='bool'),
update_source=dict(required=False, type='str'),
state=dict(choices=['present', 'absent'], default='present', required=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(
argument_spec=argument_spec,
required_together=[['timers_holdtime', 'timers_keepalive'], ['pwd', 'pwd_type']],
supports_check_mode=True,
)
warnings = list()
result = dict(changed=False, warnings=warnings)
state = module.params['state']
if module.params['pwd_type'] == 'default':
module.params['pwd_type'] = '0'
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args, warnings)
if existing.get('asn') and state == 'present':
if existing['asn'] != module.params['asn']:
module.fail_json(msg='Another BGP ASN already exists.',
proposed_asn=module.params['asn'],
existing_asn=existing.get('asn'))
proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
proposed = {}
for key, value in proposed_args.items():
if key not in ['asn', 'vrf', 'neighbor', 'pwd_type']:
if str(value).lower() == 'default':
value = PARAM_TO_DEFAULT_KEYMAP.get(key, 'default')
if key == 'bfd':
if existing.get('bfd', 'disable') != value:
proposed[key] = value
elif existing.get(key) != value:
proposed[key] = value
candidate = CustomNetworkConfig(indent=3)
if state == 'present':
state_present(module, existing, proposed, candidate)
elif state == 'absent' and existing:
state_absent(module, existing, proposed, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate
else:
result['commands'] = []
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,691 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_bgp_neighbor_af
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages BGP address-family's neighbors configuration.
description:
- Manages BGP address-family's neighbors configurations on NX-OS switches.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state=absent) removes the whole BGP address-family's
neighbor configuration.
- Default, when supported, removes properties
- In order to default maximum-prefix configuration, only
C(max_prefix_limit=default) is needed.
options:
asn:
description:
- BGP autonomous system number. Valid values are String,
Integer in ASPLAIN or ASDOT notation.
required: true
vrf:
description:
- Name of the VRF. The name 'default' is a valid VRF representing
the global bgp.
default: default
neighbor:
description:
- Neighbor Identifier. Valid values are string. Neighbors may use
IPv4 or IPv6 notation, with or without prefix length.
required: true
afi:
description:
- Address Family Identifier.
required: true
choices: ['ipv4','ipv6', 'vpnv4', 'vpnv6', 'l2vpn']
safi:
description:
- Sub Address Family Identifier.
required: true
choices: ['unicast','multicast', 'evpn']
additional_paths_receive:
description:
- Valid values are enable for basic command enablement; disable
for disabling the command at the neighbor af level
(it adds the disable keyword to the basic command); and inherit
to remove the command at this level (the command value is
inherited from a higher BGP layer).
choices: ['enable','disable', 'inherit']
additional_paths_send:
description:
- Valid values are enable for basic command enablement; disable
for disabling the command at the neighbor af level
(it adds the disable keyword to the basic command); and inherit
to remove the command at this level (the command value is
inherited from a higher BGP layer).
choices: ['enable','disable', 'inherit']
advertise_map_exist:
description:
- Conditional route advertisement. This property requires two
route maps, an advertise-map and an exist-map. Valid values are
an array specifying both the advertise-map name and the exist-map
name, or simply 'default' e.g. ['my_advertise_map',
'my_exist_map']. This command is mutually exclusive with the
advertise_map_non_exist property.
advertise_map_non_exist:
description:
- Conditional route advertisement. This property requires two
route maps, an advertise-map and an exist-map. Valid values are
an array specifying both the advertise-map name and the
non-exist-map name, or simply 'default' e.g.
['my_advertise_map', 'my_non_exist_map']. This command is mutually
exclusive with the advertise_map_exist property.
allowas_in:
description:
- Activate allowas-in property
type: bool
allowas_in_max:
description:
- Max-occurrences value for allowas_in. Valid values are
an integer value or 'default'. This is mutually exclusive with
allowas_in.
as_override:
description:
- Activate the as-override feature.
type: bool
default_originate:
description:
- Activate the default-originate feature.
type: bool
default_originate_route_map:
description:
- Route-map for the default_originate property.
Valid values are a string defining a route-map name,
or 'default'. This is mutually exclusive with
default_originate.
disable_peer_as_check:
description:
- Disable checking of peer AS-number while advertising
type: bool
version_added: 2.5
filter_list_in:
description:
- Valid values are a string defining a filter-list name,
or 'default'.
filter_list_out:
description:
- Valid values are a string defining a filter-list name,
or 'default'.
max_prefix_limit:
description:
- maximum-prefix limit value. Valid values are an integer value
or 'default'.
max_prefix_interval:
description:
- Optional restart interval. Valid values are an integer.
Requires max_prefix_limit. May not be combined with max_prefix_warning.
max_prefix_threshold:
description:
- Optional threshold percentage at which to generate a warning.
Valid values are an integer value.
Requires max_prefix_limit.
max_prefix_warning:
description:
- Optional warning-only keyword. Requires max_prefix_limit. May not be
combined with max_prefix_interval.
type: bool
next_hop_self:
description:
- Activate the next-hop-self feature.
type: bool
next_hop_third_party:
description:
- Activate the next-hop-third-party feature.
type: bool
prefix_list_in:
description:
- Valid values are a string defining a prefix-list name,
or 'default'.
prefix_list_out:
description:
- Valid values are a string defining a prefix-list name,
or 'default'.
route_map_in:
description:
- Valid values are a string defining a route-map name,
or 'default'.
route_map_out:
description:
- Valid values are a string defining a route-map name,
or 'default'.
route_reflector_client:
description:
- Router reflector client.
type: bool
send_community:
description:
- send-community attribute.
choices: ['none', 'both', 'extended', 'standard', 'default']
soft_reconfiguration_in:
description:
- Valid values are 'enable' for basic command enablement; 'always'
to add the always keyword to the basic command; and 'inherit' to
remove the command at this level (the command value is inherited
from a higher BGP layer).
choices: ['enable','always','inherit']
soo:
description:
- Site-of-origin. Valid values are a string defining a VPN
extcommunity or 'default'.
suppress_inactive:
description:
- suppress-inactive feature.
type: bool
unsuppress_map:
description:
- unsuppress-map. Valid values are a string defining a route-map
name or 'default'.
weight:
description:
- Weight value. Valid values are an integer value or 'default'.
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
- name: configure RR client
nxos_bgp_neighbor_af:
asn: 65535
neighbor: '192.0.2.3'
afi: ipv4
safi: unicast
route_reflector_client: true
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["router bgp 65535", "neighbor 192.0.2.3",
"address-family ipv4 unicast", "route-reflector-client"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
BOOL_PARAMS = [
'allowas_in',
'as_override',
'default_originate',
'disable_peer_as_check',
'next_hop_self',
'next_hop_third_party',
'route_reflector_client',
'suppress_inactive'
]
PARAM_TO_COMMAND_KEYMAP = {
'afi': 'address-family',
'asn': 'router bgp',
'neighbor': 'neighbor',
'additional_paths_receive': 'capability additional-paths receive',
'additional_paths_send': 'capability additional-paths send',
'advertise_map_exist': 'advertise-map exist-map',
'advertise_map_non_exist': 'advertise-map non-exist-map',
'allowas_in': 'allowas-in',
'allowas_in_max': 'allowas-in',
'as_override': 'as-override',
'default_originate': 'default-originate',
'default_originate_route_map': 'default-originate route-map',
'disable_peer_as_check': 'disable-peer-as-check',
'filter_list_in': 'filter-list in',
'filter_list_out': 'filter-list out',
'max_prefix_limit': 'maximum-prefix',
'max_prefix_interval': 'maximum-prefix interval',
'max_prefix_threshold': 'maximum-prefix threshold',
'max_prefix_warning': 'maximum-prefix warning',
'next_hop_self': 'next-hop-self',
'next_hop_third_party': 'next-hop-third-party',
'prefix_list_in': 'prefix-list in',
'prefix_list_out': 'prefix-list out',
'route_map_in': 'route-map in',
'route_map_out': 'route-map out',
'route_reflector_client': 'route-reflector-client',
'safi': 'address-family',
'send_community': 'send-community',
'soft_reconfiguration_in': 'soft-reconfiguration inbound',
'soo': 'soo',
'suppress_inactive': 'suppress-inactive',
'unsuppress_map': 'unsuppress-map',
'weight': 'weight',
'vrf': 'vrf'
}
def get_value(arg, config, module):
custom = [
'additional_paths_send',
'additional_paths_receive',
'max_prefix_limit',
'max_prefix_interval',
'max_prefix_threshold',
'max_prefix_warning',
'send_community',
'soft_reconfiguration_in'
]
command = PARAM_TO_COMMAND_KEYMAP[arg]
has_command = re.search(r'^\s+{0}\s*'.format(command), config, re.M)
has_command_val = re.search(r'(?:{0}\s)(?P<value>.*)$'.format(command), config, re.M)
value = ''
if arg in custom:
value = get_custom_value(arg, config, module)
elif arg == 'next_hop_third_party':
has_no_command = re.search(r'^\s+no\s+{0}\s*$'.format(command), config, re.M)
value = False
if not has_no_command:
value = True
elif arg in BOOL_PARAMS:
value = False
if has_command:
value = True
elif command.startswith('advertise-map'):
value = []
has_adv_map = re.search(r'{0}\s(?P<value1>.*)\s{1}\s(?P<value2>.*)$'.format(*command.split()), config, re.M)
if has_adv_map:
value = list(has_adv_map.groups())
elif command.split()[0] in ['filter-list', 'prefix-list', 'route-map']:
has_cmd_direction_val = re.search(r'{0}\s(?P<value>.*)\s{1}$'.format(*command.split()), config, re.M)
if has_cmd_direction_val:
value = has_cmd_direction_val.group('value')
elif has_command_val:
value = has_command_val.group('value')
return value
def get_custom_value(arg, config, module):
command = PARAM_TO_COMMAND_KEYMAP.get(arg)
splitted_config = config.splitlines()
value = ''
if arg.startswith('additional_paths'):
value = 'inherit'
for line in splitted_config:
if command in line:
if 'disable' in line:
value = 'disable'
else:
value = 'enable'
elif arg.startswith('max_prefix'):
for line in splitted_config:
if 'maximum-prefix' in line:
splitted_line = line.split()
if arg == 'max_prefix_limit':
value = splitted_line[1]
elif arg == 'max_prefix_interval' and 'restart' in line:
value = splitted_line[-1]
elif arg == 'max_prefix_threshold' and len(splitted_line) > 2:
try:
int(splitted_line[2])
value = splitted_line[2]
except ValueError:
value = ''
elif arg == 'max_prefix_warning':
value = 'warning-only' in line
elif arg == 'soft_reconfiguration_in':
value = 'inherit'
for line in splitted_config:
if command in line:
if 'always' in line:
value = 'always'
else:
value = 'enable'
elif arg == 'send_community':
value = 'none'
for line in splitted_config:
if command in line:
if 'extended' in line:
if value == 'standard':
value = 'both'
else:
value = 'extended'
elif 'both' in line:
value = 'both'
else:
value = 'standard'
return value
def get_existing(module, args, warnings):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
asn_regex = re.compile(r'.*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*', re.S)
match_asn = asn_regex.match(str(netcfg))
if match_asn:
existing_asn = match_asn.group('existing_asn')
parents = ["router bgp {0}".format(existing_asn)]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('neighbor {0}'.format(module.params['neighbor']))
parents.append('address-family {0} {1}'.format(module.params['afi'], module.params['safi']))
config = netcfg.get_section(parents)
if config:
for arg in args:
if arg not in ['asn', 'vrf', 'neighbor', 'afi', 'safi']:
existing[arg] = get_value(arg, config, module)
existing['asn'] = existing_asn
existing['neighbor'] = module.params['neighbor']
existing['vrf'] = module.params['vrf']
existing['afi'] = module.params['afi']
existing['safi'] = module.params['safi']
else:
warnings.append("The BGP process didn't exist but the task just created it.")
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = table.get(key)
return new_dict
def get_default_command(key, value, existing_commands):
command = ''
if existing_commands.get(key):
existing_value = existing_commands.get(key)
if value == 'inherit':
if existing_value != 'inherit':
command = 'no {0}'.format(key)
else:
if key == 'advertise-map exist-map':
command = 'no advertise-map {0} exist-map {1}'.format(
existing_value[0], existing_value[1])
elif key == 'advertise-map non-exist-map':
command = 'no advertise-map {0} non-exist-map {1}'.format(
existing_value[0], existing_value[1])
elif key == 'filter-list in':
command = 'no filter-list {0} in'.format(existing_value)
elif key == 'filter-list out':
command = 'no filter-list {0} out'.format(existing_value)
elif key == 'prefix-list in':
command = 'no prefix-list {0} in'.format(existing_value)
elif key == 'prefix-list out':
command = 'no prefix-list {0} out'.format(existing_value)
elif key == 'route-map in':
command = 'no route-map {0} in'.format(existing_value)
elif key == 'route-map out':
command = 'no route-map {0} out'.format(existing_value)
elif key.startswith('maximum-prefix'):
command = 'no maximum-prefix'
elif key == 'allowas-in max':
command = ['no allowas-in {0}'.format(existing_value)]
command.append('allowas-in')
else:
command = 'no {0} {1}'.format(key, existing_value)
else:
if key.replace(' ', '_').replace('-', '_') in BOOL_PARAMS:
command = 'no {0}'.format(key)
return command
def fix_proposed(module, existing, proposed):
allowas_in = proposed.get('allowas_in')
allowas_in_max = proposed.get('allowas_in_max')
if allowas_in_max and not allowas_in:
proposed.pop('allowas_in_max')
elif allowas_in and allowas_in_max:
proposed.pop('allowas_in')
if existing.get('send_community') == 'none' and proposed.get('send_community') == 'default':
proposed.pop('send_community')
return proposed
def state_present(module, existing, proposed, candidate):
commands = list()
proposed = fix_proposed(module, existing, proposed)
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if value in ['inherit', 'default']:
command = get_default_command(key, value, existing_commands)
if isinstance(command, str):
if command and command not in commands:
commands.append(command)
elif isinstance(command, list):
for cmd in command:
if cmd not in commands:
commands.append(cmd)
elif key.startswith('maximum-prefix'):
if module.params['max_prefix_limit'] != 'default':
command = 'maximum-prefix {0}'.format(module.params['max_prefix_limit'])
if module.params['max_prefix_threshold']:
command += ' {0}'.format(module.params['max_prefix_threshold'])
if module.params['max_prefix_interval']:
command += ' restart {0}'.format(module.params['max_prefix_interval'])
elif module.params['max_prefix_warning']:
command += ' warning-only'
commands.append(command)
elif value is True:
commands.append(key)
elif value is False:
commands.append('no {0}'.format(key))
elif key == 'address-family':
commands.append("address-family {0} {1}".format(module.params['afi'], module.params['safi']))
elif key.startswith('capability additional-paths'):
command = key
if value == 'disable':
command += ' disable'
commands.append(command)
elif key.startswith('advertise-map'):
direction = key.split()[1]
commands.append('advertise-map {1} {0} {2}'.format(direction, *value))
elif key.split()[0] in ['filter-list', 'prefix-list', 'route-map']:
commands.append('{1} {0} {2}'.format(value, *key.split()))
elif key == 'soft-reconfiguration inbound':
command = ''
if value == 'enable':
command = key
elif value == 'always':
command = '{0} {1}'.format(key, value)
commands.append(command)
elif key == 'send-community':
command = key
if value in ['standard', 'extended']:
commands.append('no ' + key + ' both')
command += ' {0}'.format(value)
commands.append(command)
else:
command = '{0} {1}'.format(key, value)
commands.append(command)
if commands:
parents = ['router bgp {0}'.format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('neighbor {0}'.format(module.params['neighbor']))
af_command = 'address-family {0} {1}'.format(
module.params['afi'], module.params['safi'])
parents.append(af_command)
if af_command in commands:
commands.remove(af_command)
candidate.add(commands, parents=parents)
def state_absent(module, existing, candidate):
commands = []
parents = ["router bgp {0}".format(module.params['asn'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('neighbor {0}'.format(module.params['neighbor']))
commands.append('no address-family {0} {1}'.format(
module.params['afi'], module.params['safi']))
candidate.add(commands, parents=parents)
def main():
argument_spec = dict(
asn=dict(required=True, type='str'),
vrf=dict(required=False, type='str', default='default'),
neighbor=dict(required=True, type='str'),
afi=dict(required=True, type='str'),
safi=dict(required=True, type='str'),
additional_paths_receive=dict(required=False, type='str', choices=['enable', 'disable', 'inherit']),
additional_paths_send=dict(required=False, type='str', choices=['enable', 'disable', 'inherit']),
advertise_map_exist=dict(required=False, type='list'),
advertise_map_non_exist=dict(required=False, type='list'),
allowas_in=dict(required=False, type='bool'),
allowas_in_max=dict(required=False, type='str'),
as_override=dict(required=False, type='bool'),
default_originate=dict(required=False, type='bool'),
default_originate_route_map=dict(required=False, type='str'),
disable_peer_as_check=dict(required=False, type='bool'),
filter_list_in=dict(required=False, type='str'),
filter_list_out=dict(required=False, type='str'),
max_prefix_limit=dict(required=False, type='str'),
max_prefix_interval=dict(required=False, type='str'),
max_prefix_threshold=dict(required=False, type='str'),
max_prefix_warning=dict(required=False, type='bool'),
next_hop_self=dict(required=False, type='bool'),
next_hop_third_party=dict(required=False, type='bool'),
prefix_list_in=dict(required=False, type='str'),
prefix_list_out=dict(required=False, type='str'),
route_map_in=dict(required=False, type='str'),
route_map_out=dict(required=False, type='str'),
route_reflector_client=dict(required=False, type='bool'),
send_community=dict(required=False, choices=['none', 'both', 'extended', 'standard', 'default']),
soft_reconfiguration_in=dict(required=False, type='str', choices=['enable', 'always', 'inherit']),
soo=dict(required=False, type='str'),
suppress_inactive=dict(required=False, type='bool'),
unsuppress_map=dict(required=False, type='str'),
weight=dict(required=False, type='str'),
state=dict(choices=['present', 'absent'], default='present', required=False),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[['advertise_map_exist', 'advertise_map_non_exist'],
['max_prefix_interval', 'max_prefix_warning'],
['default_originate', 'default_originate_route_map'],
['allowas_in', 'allowas_in_max']],
supports_check_mode=True,
)
warnings = list()
result = dict(changed=False, warnings=warnings)
state = module.params['state']
for key in ['max_prefix_interval', 'max_prefix_warning', 'max_prefix_threshold']:
if module.params[key] and not module.params['max_prefix_limit']:
module.fail_json(
msg='max_prefix_limit is required when using %s' % key
)
if module.params['vrf'] == 'default' and module.params['soo']:
module.fail_json(msg='SOO is only allowed in non-default VRF')
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args, warnings)
if existing.get('asn') and state == 'present':
if existing.get('asn') != module.params['asn']:
module.fail_json(msg='Another BGP ASN already exists.',
proposed_asn=module.params['asn'],
existing_asn=existing.get('asn'))
for param in ['advertise_map_exist', 'advertise_map_non_exist']:
if module.params[param] == ['default']:
module.params[param] = 'default'
proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
proposed = {}
for key, value in proposed_args.items():
if key not in ['asn', 'vrf', 'neighbor']:
if not isinstance(value, list):
if str(value).lower() == 'true':
value = True
elif str(value).lower() == 'false':
value = False
elif str(value).lower() == 'default':
if key in BOOL_PARAMS:
value = False
else:
value = 'default'
elif key == 'send_community' and str(value).lower() == 'none':
value = 'default'
if existing.get(key) != value:
proposed[key] = value
candidate = CustomNetworkConfig(indent=3)
if state == 'present':
state_present(module, existing, proposed, candidate)
elif state == 'absent' and existing:
state_absent(module, existing, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate
else:
result['commands'] = []
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,231 +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: nxos_command
extends_documentation_fragment: nxos
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Run arbitrary command on Cisco NXOS devices
description:
- Sends an arbitrary command to an NXOS 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.
options:
commands:
description:
- The commands to send to the remote NXOS device. 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 retires as expired.
- The I(commands) argument also accepts an alternative form
that allows for complex values that specify the command
to run and the output format to return. This can be done
on a command by command basis. The complex argument supports
the keywords C(command) and C(output) where C(command) is the
command to run and C(output) is one of 'text' or 'json'.
required: true
wait_for:
description:
- Specifies what to evaluate from the output of the command
and what conditionals to apply. This argument will cause
the task to wait for a particular conditional to be true
before moving forward. If the conditional is not true
by the configured 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 I(wait_for) must be satisfied. If
the value is set to C(any) then only one of the values must be
satisfied.
default: 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)
conditionals.
default: 10
interval:
description:
- Configures the interval in seconds to wait between retries
of the command. If the command does not pass the specified
conditional, the interval indicates how to long to wait before
trying the command again.
default: 1
"""
EXAMPLES = """
---
- name: run show version on remote devices
nxos_command:
commands: show version
- name: run show version and check to see if output contains Cisco
nxos_command:
commands: show version
wait_for: result[0] contains Cisco
- name: run multiple commands on remote nodes
nxos_command:
commands:
- show version
- show interfaces
- name: run multiple commands and evaluate the output
nxos_command:
commands:
- show version
- show interfaces
wait_for:
- result[0] contains Cisco
- result[1] contains loopback0
- name: run commands and specify the output format
nxos_command:
commands:
- command: show version
output: json
- name: run commands that require answering a prompt
nxos_command:
commands:
- configure terminal
- command: 'no feature npv'
prompt: 'Do you want to continue'
answer: 'y'
"""
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, FailedConditionalError
from ansible.module_utils.network.common.utils import transform_commands, to_lines
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, run_commands
def parse_commands(module, warnings):
commands = transform_commands(module)
if module.check_mode:
for item in list(commands):
if not item['command'].startswith('show'):
warnings.append(
'Only show commands are supported when using check mode, not '
'executing %s' % item['command']
)
commands.remove(item)
return commands
def to_cli(obj):
cmd = obj['command']
if obj.get('output') == 'json':
cmd += ' | json'
return cmd
def main():
"""entry point for module execution
"""
argument_spec = dict(
# { command: <str>, output: <str>, prompt: <str>, response: <str> }
commands=dict(type='list', required=True),
wait_for=dict(type='list', aliases=['waitfor']),
match=dict(default='all', choices=['any', 'all']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
argument_spec.update(nxos_argument_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):
try:
if item(responses):
if match == 'any':
conditionals = list()
break
conditionals.remove(item)
except FailedConditionalError as exc:
module.fail_json(msg=to_text(exc))
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,533 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_config
extends_documentation_fragment: nxos
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Manage Cisco NXOS configuration sections
description:
- Cisco NXOS configurations use a simple block indent file syntax
for segmenting configuration into sections. This module provides
an implementation for working with NXOS configuration sections in
a deterministic way. This module works with either CLI or NXAPI
transports.
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:
- The I(src) argument provides a path to the configuration file
to load into the remote system. The path can either be a full
system path to the configuration file if the value starts with /
or relative to the root of the implemented role or playbook.
This argument is mutually exclusive with the I(lines) and
I(parents) arguments.
version_added: "2.2"
replace_src:
description:
- The I(replace_src) argument provides path to the configuration file
to load into the remote system. This argument is used to replace the
entire config with a flat-file. This is used with argument I(replace)
with value I(config). This is mutually exclusive with the I(lines) and
I(src) arguments. This argument is supported on Nexus 9K device.
Use I(nxos_file_copy) module to copy the flat file to remote device and
then use the path with this argument.
version_added: "2.5"
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. replace I(config) is supported only on Nexus 9K device.
default: line
choices: ['line', 'block', 'config']
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"
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.
aliases: ['config']
version_added: "2.4"
defaults:
description:
- The I(defaults) argument will influence how the running-config
is collected from the device. When the value is set to true,
the command used to collect the running-config is append with
the all keyword. When the value is set to false, the command
is issued without the all keyword
type: bool
default: 'no'
version_added: "2.2"
save_when:
description:
- When changes are made to the device running-configuration, the
changes are not copied to non-volatile storage by default. Using
this argument will change that before. If the argument is set to
I(always), then the running-config will always be copied to the
startup-config and the I(modified) flag will always be set to
True. If the argument is set to I(modified), then the running-config
will only be copied to the startup-config if it has changed since
the last save to startup-config. If the argument is set to
I(never), the running-config will never be copied to the
startup-config. If the argument is set to I(changed), then the running-config
will only be copied to the startup-config if the task has made a change.
I(changed) was added in Ansible 2.6.
default: never
choices: ['always', 'never', 'modified', 'changed']
version_added: "2.4"
diff_against:
description:
- When using the C(ansible-playbook --diff) command line argument
the module can generate diffs against different sources.
- When this option is configure as I(startup), the module will return
the diff of the running-config against the startup-config.
- When this option is configured as I(intended), the module will
return the diff of the running-config against the configuration
provided in the C(intended_config) argument.
- When this option is configured as I(running), the module will
return the before and after diff of the running-config with respect
to any changes made to the device configuration.
default: startup
choices: ['startup', 'intended', 'running']
version_added: "2.4"
diff_ignore_lines:
description:
- Use this argument to specify one or more lines that should be
ignored during the diff. This is used for lines in the configuration
that are automatically updated by the system. This argument takes
a list of regular expressions or exact line matches.
version_added: "2.4"
intended_config:
description:
- The C(intended_config) provides the master configuration that
the node should conform to and is used to check the final
running-config against. This argument will not modify any settings
on the remote device and is strictly used to check the compliance
of the current device's configuration against. When specifying this
argument, the task should also modify the C(diff_against) value and
set it to I(intended).
version_added: "2.4"
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(True), if C(backup) is set
to I(false) 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
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"
notes:
- 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).
"""
EXAMPLES = """
---
- name: configure top level configuration and save it
nxos_config:
lines: hostname {{ inventory_hostname }}
save_when: modified
- name: diff the running-config against a provided config
nxos_config:
diff_against: intended
intended_config: "{{ lookup('file', 'master.cfg') }}"
- nxos_config:
lines:
- 10 permit ip 192.0.2.1/32 any log
- 20 permit ip 192.0.2.2/32 any log
- 30 permit ip 192.0.2.3/32 any log
- 40 permit ip 192.0.2.4/32 any log
- 50 permit ip 192.0.2.5/32 any log
parents: ip access-list test
before: no ip access-list test
match: exact
- nxos_config:
lines:
- 10 permit ip 192.0.2.1/32 any log
- 20 permit ip 192.0.2.2/32 any log
- 30 permit ip 192.0.2.3/32 any log
- 40 permit ip 192.0.2.4/32 any log
parents: ip access-list test
before: no ip access-list test
replace: block
- name: replace config with flat file
nxos_config:
replace_src: config.txt
replace: config
- name: for idempotency, use full-form commands
nxos_config:
lines:
# - shut
- shutdown
# parents: int eth1/1
parents: interface Ethernet1/1
- name: configurable backup path
nxos_config:
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: always
type: list
sample: ['hostname foo', 'vlan 1', 'name default']
updates:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['hostname foo', 'vlan 1', 'name default']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: str
sample: /playbooks/ansible/backup/nxos_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: nxos_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/nxos_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"
"""
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.config import NetworkConfig, dumps
from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands, get_connection
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.network.common.utils import to_list
def get_running_config(module, config=None, flags=None):
contents = module.params['running_config']
if not contents:
if config:
contents = config
else:
contents = get_config(module, flags=flags)
return contents
def get_candidate(module):
candidate = ''
if module.params['src']:
if module.params['replace'] != 'config':
candidate = module.params['src']
if module.params['replace'] == 'config':
candidate = 'config replace {0}'.format(module.params['replace_src'])
elif module.params['lines']:
candidate_obj = NetworkConfig(indent=2)
parents = module.params['parents'] or list()
candidate_obj.add(module.params['lines'], parents=parents)
candidate = dumps(candidate_obj, 'raw')
return candidate
def execute_show_commands(module, commands, output='text'):
cmds = []
for command in to_list(commands):
cmd = {'command': command,
'output': output,
}
cmds.append(cmd)
body = run_commands(module, cmds)
return body
def save_config(module, result):
result['changed'] = True
if not module.check_mode:
cmd = {'command': 'copy running-config startup-config', 'output': 'text'}
run_commands(module, [cmd])
else:
module.warn('Skipping command `copy running-config startup-config` '
'due to check_mode. Configuration not copied to '
'non-volatile storage')
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'),
replace_src=dict(),
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']),
running_config=dict(aliases=['config']),
intended_config=dict(),
defaults=dict(type='bool', default=False),
backup=dict(type='bool', default=False),
backup_options=dict(type='dict', options=backup_spec),
save_when=dict(choices=['always', 'never', 'modified', 'changed'], default='never'),
diff_against=dict(choices=['running', 'startup', 'intended']),
diff_ignore_lines=dict(type='list'),
)
argument_spec.update(nxos_argument_spec)
mutually_exclusive = [('lines', 'src', 'replace_src'),
('parents', 'src')]
required_if = [('match', 'strict', ['lines']),
('match', 'exact', ['lines']),
('replace', 'block', ['lines']),
('replace', 'config', ['replace_src']),
('diff_against', 'intended', ['intended_config'])]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True)
warnings = list()
result = {'changed': False, 'warnings': warnings}
config = None
diff_ignore_lines = module.params['diff_ignore_lines']
path = module.params['parents']
connection = get_connection(module)
contents = None
flags = ['all'] if module.params['defaults'] else []
replace_src = module.params['replace_src']
if replace_src:
if module.params['replace'] != 'config':
module.fail_json(msg='replace: config is required with replace_src')
if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'):
contents = get_config(module, flags=flags)
config = NetworkConfig(indent=2, contents=contents)
if module.params['backup']:
result['__backup__'] = contents
if any((module.params['src'], module.params['lines'], replace_src)):
match = module.params['match']
replace = module.params['replace']
commit = not module.check_mode
candidate = get_candidate(module)
running = get_running_config(module, contents, flags=flags)
if replace_src:
commands = candidate.split('\n')
result['commands'] = result['updates'] = commands
if commit:
load_config(module, commands, replace=replace_src)
result['changed'] = True
else:
try:
response = connection.get_diff(candidate=candidate, running=running, diff_match=match, diff_ignore_lines=diff_ignore_lines, path=path,
diff_replace=replace)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
config_diff = response['config_diff']
if config_diff:
commands = config_diff.split('\n')
if module.params['before']:
commands[:0] = module.params['before']
if module.params['after']:
commands.extend(module.params['after'])
result['commands'] = commands
result['updates'] = commands
if commit:
load_config(module, commands, replace=replace_src)
result['changed'] = True
running_config = module.params['running_config']
startup_config = None
if module.params['save_when'] == 'always':
save_config(module, result)
elif module.params['save_when'] == 'modified':
output = execute_show_commands(module, ['show running-config', 'show startup-config'])
running_config = NetworkConfig(indent=2, contents=output[0], ignore_lines=diff_ignore_lines)
startup_config = NetworkConfig(indent=2, contents=output[1], ignore_lines=diff_ignore_lines)
if running_config.sha1 != startup_config.sha1:
save_config(module, result)
elif module.params['save_when'] == 'changed' and result['changed']:
save_config(module, result)
if module._diff:
if not running_config:
output = execute_show_commands(module, 'show running-config')
contents = output[0]
else:
contents = running_config
# recreate the object in order to process diff_ignore_lines
running_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines)
if module.params['diff_against'] == 'running':
if module.check_mode:
module.warn("unable to perform diff against running-config due to check mode")
contents = None
else:
contents = config.config_text
elif module.params['diff_against'] == 'startup':
if not startup_config:
output = execute_show_commands(module, 'show startup-config')
contents = output[0]
else:
contents = startup_config.config_text
elif module.params['diff_against'] == 'intended':
contents = module.params['intended_config']
if contents is not None:
base_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines)
if running_config.sha1 != base_config.sha1:
if module.params['diff_against'] == 'intended':
before = running_config
after = base_config
elif module.params['diff_against'] in ('startup', 'running'):
before = base_config
after = running_config
result.update({
'changed': True,
'diff': {'before': str(before), 'after': str(after)}
})
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,101 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_evpn_global
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Handles the EVPN control plane for VXLAN.
description:
- Handles the EVPN control plane for VXLAN.
author: Gabriele Gerbino (@GGabriele)
notes:
- This module is not supported on Nexus 3000 series of switches.
options:
nv_overlay_evpn:
description:
- EVPN control plane.
required: true
type: bool
'''
EXAMPLES = '''
- nxos_evpn_global:
nv_overlay_evpn: true
'''
RETURN = '''
commands:
description: The set of commands to be sent to the remote device
returned: always
type: list
sample: ['nv overlay evpn']
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec
def main():
argument_spec = dict(
nv_overlay_evpn=dict(required=True, type='bool'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
result = {'changed': False}
warnings = list()
if warnings:
result['warnings'] = warnings
config = get_config(module)
commands = list()
info = get_capabilities(module).get('device_info', {})
os_platform = info.get('network_os_platform', '')
if '3K' in os_platform:
module.fail_json(msg='This module is not supported on Nexus 3000 series')
if module.params['nv_overlay_evpn'] is True:
if 'nv overlay evpn' not in config:
commands.append('nv overlay evpn')
elif 'nv overlay evpn' in config:
commands.append('no nv overlay evpn')
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
result['commands'] = commands
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,287 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_evpn_vni
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages Cisco EVPN VXLAN Network Identifier (VNI).
description:
- Manages Cisco Ethernet Virtual Private Network (EVPN) VXLAN Network
Identifier (VNI) configurations of a Nexus device.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- default, where supported, restores params default value.
- RD override is not permitted. You should set it to the default values
first and then reconfigure it.
- C(route_target_both), C(route_target_import) and
C(route_target_export valid) values are a list of extended communities,
(i.e. ['1.2.3.4:5', '33:55']) or the keywords 'auto' or 'default'.
- The C(route_target_both) property is discouraged due to the inconsistent
behavior of the property across Nexus platforms and image versions.
For this reason it is recommended to use explicit C(route_target_export)
and C(route_target_import) properties instead of C(route_target_both).
- RD valid values are a string in one of the route-distinguisher formats,
the keyword 'auto', or the keyword 'default'.
options:
vni:
description:
- The EVPN VXLAN Network Identifier.
required: true
route_distinguisher:
description:
- The VPN Route Distinguisher (RD). The RD is combined with
the IPv4 or IPv6 prefix learned by the PE router to create a
globally unique address.
required: true
route_target_both:
description:
- Enables/Disables route-target settings for both import and
export target communities using a single property.
route_target_import:
description:
- Sets the route-target 'import' extended communities.
route_target_export:
description:
- Sets the route-target 'export' extended communities.
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
- name: vni configuration
nxos_evpn_vni:
vni: 6000
route_distinguisher: "60:10"
route_target_import:
- "5000:10"
- "4100:100"
route_target_export: auto
route_target_both: default
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["evpn", "vni 6000 l2", "route-target import 5001:10"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
PARAM_TO_COMMAND_KEYMAP = {
'vni': 'vni',
'route_distinguisher': 'rd',
'route_target_both': 'route-target both',
'route_target_import': 'route-target import',
'route_target_export': 'route-target export'
}
def get_value(arg, config, module):
command = PARAM_TO_COMMAND_KEYMAP.get(arg)
command_re = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(command), re.M)
value = ''
if command in config:
value = command_re.search(config).group('value')
return value
def get_route_target_value(arg, config, module):
splitted_config = config.splitlines()
value_list = []
command = PARAM_TO_COMMAND_KEYMAP.get(arg)
command_re = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(command), re.M)
for line in splitted_config:
value = ''
if command in line.strip():
value = command_re.search(line).group('value')
value_list.append(value)
return value_list
def get_existing(module, args):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
parents = ['evpn', 'vni {0} l2'.format(module.params['vni'])]
config = netcfg.get_section(parents)
if config:
for arg in args:
if arg != 'vni':
if arg == 'route_distinguisher':
existing[arg] = get_value(arg, config, module)
else:
existing[arg] = get_route_target_value(arg, config, module)
existing_fix = dict((k, v) for k, v in existing.items() if v)
if not existing_fix:
existing = existing_fix
existing['vni'] = module.params['vni']
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = table.get(key)
return new_dict
def fix_proposed(proposed_commands):
new_proposed = {}
for key, value in proposed_commands.items():
if key == 'route-target both':
new_proposed['route-target export'] = value
new_proposed['route-target import'] = value
else:
new_proposed[key] = value
return new_proposed
def state_present(module, existing, proposed):
commands = list()
parents = list()
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
if proposed_commands.get('route-target both'):
proposed_commands = fix_proposed(proposed_commands)
for key, value in proposed_commands.items():
if key.startswith('route-target'):
if value == ['default']:
existing_value = existing_commands.get(key)
if existing_value:
for target in existing_value:
commands.append('no {0} {1}'.format(key, target))
elif not isinstance(value, list):
value = [value]
for target in value:
if target == 'default':
continue
if existing:
if target not in existing.get(key.replace('-', '_').replace(' ', '_')):
commands.append('{0} {1}'.format(key, target))
else:
commands.append('{0} {1}'.format(key, target))
if existing.get(key.replace('-', '_').replace(' ', '_')):
for exi in existing.get(key.replace('-', '_').replace(' ', '_')):
if exi not in value:
commands.append('no {0} {1}'.format(key, exi))
elif value == 'default':
existing_value = existing_commands.get(key)
if existing_value:
commands.append('no {0} {1}'.format(key, existing_value))
else:
command = '{0} {1}'.format(key, value)
commands.append(command)
if commands:
parents = ['evpn', 'vni {0} l2'.format(module.params['vni'])]
return commands, parents
def state_absent(module, existing, proposed):
commands = ['no vni {0} l2'.format(module.params['vni'])]
parents = ['evpn']
return commands, parents
def main():
argument_spec = dict(
vni=dict(required=True, type='str'),
route_distinguisher=dict(required=False, type='str'),
route_target_both=dict(required=False, type='list'),
route_target_import=dict(required=False, type='list'),
route_target_export=dict(required=False, type='list'),
state=dict(choices=['present', 'absent'], default='present', required=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
results = dict(changed=False, warnings=warnings)
state = module.params['state']
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args)
proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
commands = []
parents = []
proposed = {}
for key, value in proposed_args.items():
if key != 'vni':
if value == 'true':
value = True
elif value == 'false':
value = False
if existing.get(key) != value:
proposed[key] = value
if state == 'present':
commands, parents = state_present(module, existing, proposed)
elif state == 'absent' and existing:
commands, parents = state_absent(module, existing, proposed)
if commands:
candidate = CustomNetworkConfig(indent=3)
candidate.add(commands, parents=parents)
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
results['changed'] = True
results['commands'] = candidate
else:
results['commands'] = []
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,243 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_facts
extends_documentation_fragment: nxos
version_added: "2.1"
short_description: Gets facts about NX-OS switches
description:
- Collects facts from Cisco Nexus devices running the NX-OS operating
system. Fact collection is supported over both Cli and Nxapi
transports. This module prepends all of the base network fact keys
with C(ansible_net_<fact>). The facts module will always collect a
base set of facts from the device and can enable or disable
collection of additional facts.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
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, legacy, 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'
version_added: "2.2"
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, vlans 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', 'bfd_interfaces', 'lag_interfaces', 'telemetry',
'vlans', 'lacp', 'lacp_interfaces', 'interfaces', 'l3_interfaces',
'l2_interfaces', 'lldp_global'.
required: false
version_added: "2.9"
"""
EXAMPLES = """
- name: Gather all legacy facts
nxos_facts:
gather_subset: all
- name: Gather only the config and default facts
nxos_facts:
gather_subset:
- config
- name: Do not gather hardware facts
nxos_facts:
gather_subset:
- "!hardware"
- name: Gather legacy and resource facts
nxos_facts:
gather_subset: all
gather_network_resources: all
- name: Gather only the interfaces resource facts and no legacy facts
nxos_facts:
gather_subset:
- '!all'
- '!min'
gather_network_resources:
- interfaces
- name: Gather interfaces resource and minimal legacy facts
nxos_facts:
gather_subset: min
gather_network_resources: interfaces
"""
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device
returned: always
type: list
ansible_net_gather_network_resources:
description: The list of fact for network resource subsets collected from the device
returned: when the resource is configured
type: list
# default
ansible_net_model:
description: The model name returned from the device
returned: always
type: str
ansible_net_serialnum:
description: The serial number of the remote device
returned: always
type: str
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_license_hostid:
description: The License host id of the device
returned: always
type: str
ansible_net_python_version:
description: The Python version Ansible controller is using
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 and CDP neighbors from the device. If both,
CDP and LLDP neighbor data is present on one port, CDP is preferred.
returned: when interfaces is configured
type: dict
# legacy (pre Ansible 2.2)
fan_info:
description: A hash of facts about fans in the remote device
returned: when legacy is configured
type: dict
hostname:
description: The configured hostname of the remote device
returned: when legacy is configured
type: dict
interfaces_list:
description: The list of interface names on the remote device
returned: when legacy is configured
type: dict
kickstart:
description: The software version used to boot the system
returned: when legacy is configured
type: str
module:
description: A hash of facts about the modules in a remote device
returned: when legacy is configured
type: dict
platform:
description: The hardware platform reported by the remote device
returned: when legacy is configured
type: str
power_supply_info:
description: A hash of facts about the power supplies in the remote device
returned: when legacy is configured
type: str
vlan_list:
description: The list of VLAN IDs configured on the remote device
returned: when legacy is configured
type: list
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.nxos.facts.facts import Facts
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
def main():
"""
Main entry point for module execution
:returns: ansible_facts
"""
argument_spec = FactsArgs.argument_spec
argument_spec.update(nxos_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,257 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_feature
extends_documentation_fragment: nxos
version_added: "2.1"
short_description: Manage features in NX-OS switches.
description:
- Offers ability to enable and disable features in NX-OS.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
options:
feature:
description:
- Name of feature.
required: true
state:
description:
- Desired state of the feature.
required: false
default: 'enabled'
choices: ['enabled','disabled']
'''
EXAMPLES = '''
- name: Ensure lacp is enabled
nxos_feature:
feature: lacp
state: enabled
- name: Ensure ospf is disabled
nxos_feature:
feature: ospf
state: disabled
- name: Ensure vpc is enabled
nxos_feature:
feature: vpc
state: enabled
'''
RETURN = '''
commands:
description: The set of commands to be sent to the remote device
returned: always
type: list
sample: ['nv overlay evpn']
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec
def get_available_features(feature, module):
available_features = {}
feature_regex = r'(?P<feature>\S+)\s+\d+\s+(?P<state>.*)'
command = {'command': 'show feature', 'output': 'text'}
try:
body = run_commands(module, [command])[0]
split_body = body.splitlines()
except (KeyError, IndexError):
return {}
for line in split_body:
try:
match_feature = re.match(feature_regex, line, re.DOTALL)
feature_group = match_feature.groupdict()
feature = feature_group['feature']
state = feature_group['state']
except AttributeError:
feature = ''
state = ''
if feature and state:
if 'enabled' in state:
state = 'enabled'
if feature not in available_features:
available_features[feature] = state
else:
if available_features[feature] == 'disabled' and state == 'enabled':
available_features[feature] = state
return available_features
def get_commands(proposed, existing, state, module):
feature = validate_feature(module, mode='config')
commands = []
feature_check = proposed == existing
if not feature_check:
if state == 'enabled':
command = 'feature {0}'.format(feature)
commands.append(command)
elif state == 'disabled':
command = "no feature {0}".format(feature)
commands.append(command)
return commands
def validate_feature(module, mode='show'):
'''Some features may need to be mapped due to inconsistency
between how they appear from "show feature" output and
how they are configured'''
feature = module.params['feature']
try:
info = get_capabilities(module)
device_info = info.get('device_info', {})
os_version = device_info.get('network_os_version', '')
except ConnectionError:
os_version = ''
if '8.1' in os_version:
feature_to_be_mapped = {
'show': {
'nv overlay': 'nve',
'vn-segment-vlan-based': 'vnseg_vlan',
'hsrp': 'hsrp_engine',
'fabric multicast': 'fabric_mcast',
'scp-server': 'scpServer',
'sftp-server': 'sftpServer',
'sla responder': 'sla_responder',
'sla sender': 'sla_sender',
'ssh': 'sshServer',
'tacacs+': 'tacacs',
'telnet': 'telnetServer',
'ethernet-link-oam': 'elo'
},
'config': {
'nve': 'nv overlay',
'vnseg_vlan': 'vn-segment-vlan-based',
'hsrp_engine': 'hsrp',
'fabric_mcast': 'fabric multicast',
'scpServer': 'scp-server',
'sftpServer': 'sftp-server',
'sla_sender': 'sla sender',
'sla_responder': 'sla responder',
'sshServer': 'ssh',
'tacacs': 'tacacs+',
'telnetServer': 'telnet',
'elo': 'ethernet-link-oam'
}
}
else:
feature_to_be_mapped = {
'show': {
'nv overlay': 'nve',
'vn-segment-vlan-based': 'vnseg_vlan',
'hsrp': 'hsrp_engine',
'fabric multicast': 'fabric_mcast',
'scp-server': 'scpServer',
'sftp-server': 'sftpServer',
'sla responder': 'sla_responder',
'sla sender': 'sla_sender',
'ssh': 'sshServer',
'tacacs+': 'tacacs',
'telnet': 'telnetServer',
'ethernet-link-oam': 'elo',
'port-security': 'eth_port_sec'
},
'config': {
'nve': 'nv overlay',
'vnseg_vlan': 'vn-segment-vlan-based',
'hsrp_engine': 'hsrp',
'fabric_mcast': 'fabric multicast',
'scpServer': 'scp-server',
'sftpServer': 'sftp-server',
'sla_sender': 'sla sender',
'sla_responder': 'sla responder',
'sshServer': 'ssh',
'tacacs': 'tacacs+',
'telnetServer': 'telnet',
'elo': 'ethernet-link-oam',
'eth_port_sec': 'port-security'
}
}
if feature in feature_to_be_mapped[mode]:
feature = feature_to_be_mapped[mode][feature]
return feature
def main():
argument_spec = dict(
feature=dict(type='str', required=True),
state=dict(choices=['enabled', 'disabled'], default='enabled')
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
results = dict(changed=False, warnings=warnings)
feature = validate_feature(module)
state = module.params['state'].lower()
available_features = get_available_features(feature, module)
if feature not in available_features:
module.fail_json(
msg='Invalid feature name.',
features_currently_supported=available_features,
invalid_feature=feature)
else:
existstate = available_features[feature]
existing = dict(state=existstate)
proposed = dict(state=state)
results['changed'] = False
cmds = get_commands(proposed, existing, state, module)
if cmds:
# On N35 A8 images, some features return a yes/no prompt
# on enablement or disablement. Bypass using terminal dont-ask
cmds.insert(0, 'terminal dont-ask')
if not module.check_mode:
load_config(module, cmds)
results['changed'] = True
results['commands'] = cmds
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,190 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_file_copy
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Copy a file to a remote NXOS device.
description:
- This module supports two different workflows for copying a file
to flash (or bootflash) on NXOS devices. Files can either be (1) pushed
from the Ansible controller to the device or (2) pulled from a remote SCP
file server to the device. File copies are initiated from the NXOS
device to the remote SCP server. This module only supports the
use of connection C(network_cli) or C(Cli) transport with connection C(local).
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
- Rewritten as a plugin by (@mikewiebe)
notes:
- Tested against NXOS 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3),
7.0(3)I6(1), 7.0(3)I7(3), 6.0(2)A8(8), 7.0(3)F3(4), 7.3(0)D1(1),
8.3(0), 9.2, 9.3
- When pushing files (file_pull is False) to the NXOS device,
feature scp-server must be enabled.
- When pulling files (file_pull is True) to the NXOS device,
feature scp-server is not required.
- When pulling files (file_pull is True) to the NXOS device,
no transfer will take place if the file is already present.
- Check mode will tell you if the file would be copied.
requirements:
- paramiko (required when file_pull is False)
- SCPClient (required when file_pull is False)
- pexpect (required when file_pull is True)
options:
local_file:
description:
- When (file_pull is False) this is the path to the local file on the Ansible controller.
The local directory must exist.
- When (file_pull is True) this is the target file name on the NXOS device.
remote_file:
description:
- When (file_pull is False) this is the remote file path on the NXOS device.
If omitted, the name of the local file will be used.
The remote directory must exist.
- When (file_pull is True) this is the full path to the file on the remote SCP
server to be copied to the NXOS device.
file_system:
description:
- The remote file system on the nxos device. If omitted,
devices that support a I(file_system) parameter will use
their default values.
default: "bootflash:"
connect_ssh_port:
description:
- SSH server port used for file transfer.
default: 22
version_added: "2.5"
file_pull:
description:
- When (False) file is copied from the Ansible controller to the NXOS device.
- When (True) file is copied from a remote SCP server to the NXOS device.
In this mode, the file copy is initiated from the NXOS device.
- If the file is already present on the device it will be overwritten and
therefore the operation is NOT idempotent.
type: bool
default: False
version_added: "2.7"
file_pull_compact:
description:
- When file_pull is True, this is used to compact nxos image files.
This option can only be used with nxos image files.
- When (file_pull is False), this is not used.
type: bool
default: False
version_added: "2.9"
file_pull_kstack:
description:
- When file_pull is True, this can be used to speed up file copies when
the nxos running image supports the use-kstack option.
- When (file_pull is False), this is not used.
type: bool
default: False
version_added: "2.9"
local_file_directory:
description:
- When (file_pull is True) file is copied from a remote SCP server to the NXOS device,
and written to this directory on the NXOS device. If the directory does not exist, it
will be created under the file_system. This is an optional parameter.
- When (file_pull is False), this is not used.
version_added: "2.7"
file_pull_timeout:
description:
- Use this parameter to set timeout in seconds, when transferring
large files or when the network is slow.
- When (file_pull is False), this is not used.
default: 300
version_added: "2.7"
remote_scp_server:
description:
- The remote scp server address when file_pull is True.
This is required if file_pull is True.
- When (file_pull is False), this is not used.
version_added: "2.7"
remote_scp_server_user:
description:
- The remote scp server username when file_pull is True.
This is required if file_pull is True.
- When (file_pull is False), this is not used.
version_added: "2.7"
remote_scp_server_password:
description:
- The remote scp server password when file_pull is True.
This is required if file_pull is True.
- When (file_pull is False), this is not used.
version_added: "2.7"
vrf:
description:
- The VRF used to pull the file. Useful when no vrf management is defined
default: "management"
version_added: "2.9"
'''
EXAMPLES = '''
# File copy from ansible controller to nxos device
- name: "copy from server to device"
nxos_file_copy:
local_file: "./test_file.txt"
remote_file: "test_file.txt"
# Initiate file copy from the nxos device to transfer file from an SCP server back to the nxos device
- name: "initiate file copy from device"
nxos_file_copy:
file_pull: True
local_file: "xyz"
local_file_directory: "dir1/dir2/dir3"
remote_file: "/mydir/abc"
remote_scp_server: "192.168.0.1"
remote_scp_server_user: "myUser"
remote_scp_server_password: "myPassword"
vrf: "management"
'''
RETURN = '''
transfer_status:
description: Whether a file was transferred to the nxos device.
returned: success
type: str
sample: 'Sent'
local_file:
description: The path of the local file.
returned: success
type: str
sample: '/path/to/local/file'
remote_file:
description: The path of the remote file.
returned: success
type: str
sample: '/path/to/remote/file'
remote_scp_server:
description: The name of the scp server when file_pull is True.
returned: success
type: str
sample: 'fileserver.example.com'
changed:
description: Indicates whether or not the file was copied.
returned: success
type: bool
sample: true
'''

@ -1,309 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_gir
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Trigger a graceful removal or insertion (GIR) of the switch.
description:
- Trigger a graceful removal or insertion (GIR) of the switch.
- GIR processing may take more than 2 minutes. Timeout settings are automatically extended to 200s when user timeout settings are insufficient.
author:
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state) has effect only in combination with
C(system_mode_maintenance_timeout) or
C(system_mode_maintenance_on_reload_reset_reason).
- Using C(system_mode_maintenance) and
C(system_mode_maintenance_dont_generate_profile) would make the module
fail, but the system mode will be triggered anyway.
options:
system_mode_maintenance:
description:
- When C(system_mode_maintenance=true) it puts all enabled
protocols in maintenance mode (using the isolate command).
When C(system_mode_maintenance=false) it puts all enabled
protocols in normal mode (using the no isolate command).
type: bool
system_mode_maintenance_dont_generate_profile:
description:
- When C(system_mode_maintenance_dont_generate_profile=true) it
prevents the dynamic searching of enabled protocols and executes
commands configured in a maintenance-mode profile.
Use this option if you want the system to use a maintenance-mode
profile that you have created.
When C(system_mode_maintenance_dont_generate_profile=false) it
prevents the dynamic searching of enabled protocols and executes
commands configured in a normal-mode profile. Use this option if
you want the system to use a normal-mode profile that
you have created.
type: bool
system_mode_maintenance_timeout:
description:
- Keeps the switch in maintenance mode for a specified
number of minutes. Range is 5-65535.
system_mode_maintenance_shutdown:
description:
- Shuts down all protocols, vPC domains, and interfaces except
the management interface (using the shutdown command).
This option is disruptive while C(system_mode_maintenance)
(which uses the isolate command) is not.
type: bool
system_mode_maintenance_on_reload_reset_reason:
description:
- Boots the switch into maintenance mode automatically in the
event of a specified system crash. Note that not all reset
reasons are applicable for all platforms. Also if reset
reason is set to match_any, it is not idempotent as it turns
on all reset reasons. If reset reason is match_any and state
is absent, it turns off all the reset reasons.
choices: ['hw_error','svc_failure','kern_failure','wdog_timeout',
'fatal_error','lc_failure','match_any','manual_reload',
'any_other', 'maintenance']
state:
description:
- Specify desired state of the resource.
required: true
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# Trigger system maintenance mode
- nxos_gir:
system_mode_maintenance: true
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
# Trigger system normal mode
- nxos_gir:
system_mode_maintenance: false
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
# Configure on-reload reset-reason for maintenance mode
- nxos_gir:
system_mode_maintenance_on_reload_reset_reason: manual_reload
state: present
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
# Add on-reload reset-reason for maintenance mode
- nxos_gir:
system_mode_maintenance_on_reload_reset_reason: hw_error
state: present
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
# Remove on-reload reset-reason for maintenance mode
- nxos_gir:
system_mode_maintenance_on_reload_reset_reason: manual_reload
state: absent
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
# Set timeout for maintenance mode
- nxos_gir:
system_mode_maintenance_timeout: 30
state: present
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
# Remove timeout for maintenance mode
- nxos_gir:
system_mode_maintenance_timeout: 30
state: absent
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
'''
RETURN = '''
final_system_mode:
description: describe the last system mode
returned: verbose mode
type: str
sample: normal
updates:
description: commands sent to the device
returned: verbose mode
type: list
sample: ["terminal dont-ask", "system mode maintenance timeout 10"]
changed:
description: check to see if a change was made on the device
returned: always
type: bool
sample: true
'''
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def get_system_mode(module):
command = {'command': 'show system mode', 'output': 'text'}
body = run_commands(module, [command])[0]
if body and 'normal' in body.lower():
mode = 'normal'
else:
mode = 'maintenance'
return mode
def get_maintenance_timeout(module):
command = {'command': 'show maintenance timeout', 'output': 'text'}
body = run_commands(module, [command])[0]
timeout = body.split()[4]
return timeout
def get_reset_reasons(module):
command = {'command': 'show maintenance on-reload reset-reasons', 'output': 'text'}
body = run_commands(module, [command])[0]
return body
def get_commands(module, state, mode):
commands = list()
if module.params['system_mode_maintenance'] is True and mode == 'normal':
commands.append('system mode maintenance')
elif (module.params['system_mode_maintenance'] is False and
mode == 'maintenance'):
commands.append('no system mode maintenance')
elif (module.params[
'system_mode_maintenance_dont_generate_profile'] is True and
mode == 'normal'):
commands.append('system mode maintenance dont-generate-profile')
elif (module.params[
'system_mode_maintenance_dont_generate_profile'] is False and
mode == 'maintenance'):
commands.append('no system mode maintenance dont-generate-profile')
elif module.params['system_mode_maintenance_timeout']:
timeout = get_maintenance_timeout(module)
if (state == 'present' and
timeout != module.params['system_mode_maintenance_timeout']):
commands.append('system mode maintenance timeout {0}'.format(
module.params['system_mode_maintenance_timeout']))
elif (state == 'absent' and
timeout == module.params['system_mode_maintenance_timeout']):
commands.append('no system mode maintenance timeout {0}'.format(
module.params['system_mode_maintenance_timeout']))
elif (module.params['system_mode_maintenance_shutdown'] and
mode == 'normal'):
commands.append('system mode maintenance shutdown')
elif (module.params[
'system_mode_maintenance_shutdown'] is False and
mode == 'maintenance'):
commands.append('no system mode maintenance')
elif module.params['system_mode_maintenance_on_reload_reset_reason']:
reset_reasons = get_reset_reasons(module)
if (state == 'present' and
module.params['system_mode_maintenance_on_reload_reset_reason'].lower() not in reset_reasons.lower()):
commands.append('system mode maintenance on-reload '
'reset-reason {0}'.format(
module.params[
'system_mode_maintenance_on_reload_reset_reason']))
elif (state == 'absent' and
module.params[
'system_mode_maintenance_on_reload_reset_reason'].lower() in
reset_reasons.lower()):
commands.append('no system mode maintenance on-reload '
'reset-reason {0}'.format(
module.params[
'system_mode_maintenance_on_reload_reset_reason']))
if commands:
commands.insert(0, 'terminal dont-ask')
return commands
def main():
argument_spec = dict(
system_mode_maintenance=dict(required=False, type='bool'),
system_mode_maintenance_dont_generate_profile=dict(required=False,
type='bool'),
system_mode_maintenance_timeout=dict(required=False, type='str'),
system_mode_maintenance_shutdown=dict(required=False, type='bool'),
system_mode_maintenance_on_reload_reset_reason=dict(required=False,
choices=['hw_error', 'svc_failure', 'kern_failure',
'wdog_timeout', 'fatal_error', 'lc_failure',
'match_any', 'manual_reload', 'any_other',
'maintenance']),
state=dict(choices=['absent', 'present', 'default'],
default='present', required=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[[
'system_mode_maintenance',
'system_mode_maintenance_dont_generate_profile',
'system_mode_maintenance_timeout',
'system_mode_maintenance_shutdown',
'system_mode_maintenance_on_reload_reset_reason'
]],
required_one_of=[[
'system_mode_maintenance',
'system_mode_maintenance_dont_generate_profile',
'system_mode_maintenance_timeout',
'system_mode_maintenance_shutdown',
'system_mode_maintenance_on_reload_reset_reason'
]],
supports_check_mode=True)
warnings = list()
state = module.params['state']
mode = get_system_mode(module)
commands = get_commands(module, state, mode)
changed = False
if commands:
if module.check_mode:
module.exit_json(changed=True, commands=commands)
else:
load_config(module, commands)
changed = True
result = {}
result['changed'] = changed
if module._verbosity > 0:
final_system_mode = get_system_mode(module)
result['final_system_mode'] = final_system_mode
result['updates'] = commands
result['warnings'] = warnings
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,204 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_gir_profile_management
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Create a maintenance-mode or normal-mode profile for GIR.
description:
- Manage a maintenance-mode or normal-mode profile with configuration
commands that can be applied during graceful removal
or graceful insertion.
author:
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- C(state=absent) removes the whole profile.
options:
commands:
description:
- List of commands to be included into the profile.
mode:
description:
- Configure the profile as Maintenance or Normal mode.
required: true
choices: ['maintenance', 'normal']
state:
description:
- Specify desired state of the resource.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# Create a maintenance-mode profile
- nxos_gir_profile_management:
mode: maintenance
commands:
- router eigrp 11
- isolate
# Remove the maintenance-mode profile
- nxos_gir_profile_management:
mode: maintenance
state: absent
'''
RETURN = '''
proposed:
description: list of commands passed into module.
returned: verbose mode
type: list
sample: ["router eigrp 11", "isolate"]
existing:
description: list of existing profile commands.
returned: verbose mode
type: list
sample: ["router bgp 65535","isolate","router eigrp 10","isolate",
"diagnostic bootup level complete"]
end_state:
description: list of profile entries after module execution.
returned: verbose mode
type: list
sample: ["router bgp 65535","isolate","router eigrp 10","isolate",
"diagnostic bootup level complete","router eigrp 11", "isolate"]
updates:
description: commands sent to the device
returned: always
type: list
sample: ["configure maintenance profile maintenance-mode",
"router eigrp 11","isolate"]
changed:
description: check to see if a change was made on the device
returned: always
type: bool
sample: true
'''
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
def get_existing(module):
existing = []
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
if module.params['mode'] == 'maintenance':
parents = ['configure maintenance profile maintenance-mode']
else:
parents = ['configure maintenance profile normal-mode']
config = netcfg.get_section(parents)
if config:
existing = config.splitlines()
existing = [cmd.strip() for cmd in existing]
existing.pop(0)
return existing
def state_present(module, existing, commands):
cmds = list()
if existing == commands:
# Idempotent case
return cmds
cmds.extend(commands)
if module.params['mode'] == 'maintenance':
cmds.insert(0, 'configure maintenance profile maintenance-mode')
else:
cmds.insert(0, 'configure maintenance profile normal-mode')
return cmds
def state_absent(module, existing, commands):
if module.params['mode'] == 'maintenance':
cmds = ['no configure maintenance profile maintenance-mode']
else:
cmds = ['no configure maintenance profile normal-mode']
return cmds
def invoke(name, *args, **kwargs):
func = globals().get(name)
if func:
return func(*args, **kwargs)
def main():
argument_spec = dict(
commands=dict(required=False, type='list'),
mode=dict(required=True, choices=['maintenance', 'normal']),
state=dict(choices=['absent', 'present'], default='present')
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
state = module.params['state']
commands = module.params['commands'] or []
if state == 'absent' and commands:
module.fail_json(msg='when state is absent, no command can be used.')
existing = invoke('get_existing', module)
end_state = existing
changed = False
result = {}
cmds = []
if state == 'present' or (state == 'absent' and existing):
cmds = invoke('state_%s' % state, module, existing, commands)
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
if cmds:
load_config(module, cmds)
changed = True
end_state = invoke('get_existing', module)
result['changed'] = changed
if module._verbosity > 0:
end_state = invoke('get_existing', module)
result['end_state'] = end_state
result['existing'] = existing
result['proposed'] = commands
result['updates'] = cmds
result['warnings'] = warnings
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,474 +0,0 @@
#!/usr/bin/python
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = r'''
---
module: nxos_hsrp
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages HSRP configuration on NX-OS switches.
description:
- Manages HSRP configuration on NX-OS switches.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- HSRP feature needs to be enabled first on the system.
- SVIs must exist before using this module.
- Interface must be a L3 port before using this module.
- HSRP cannot be configured on loopback interfaces.
- MD5 authentication is only possible with HSRPv2 while it is ignored if
HSRPv1 is used instead, while it will not raise any error. Here we allow
MD5 authentication only with HSRPv2 in order to enforce better practice.
options:
group:
description:
- HSRP group number.
required: true
interface:
description:
- Full name of interface that is being managed for HSRP.
required: true
version:
description:
- HSRP version.
default: 1
choices: ['1','2']
priority:
description:
- HSRP priority or keyword 'default'.
preempt:
description:
- Enable/Disable preempt.
choices: ['enabled', 'disabled']
vip:
description:
- HSRP virtual IP address or keyword 'default'
auth_string:
description:
- Authentication string. If this needs to be hidden(for md5 type), the string
should be 7 followed by the key string. Otherwise, it can be 0 followed by
key string or just key string (for backward compatibility). For text type,
this should be just be a key string. if this is 'default', authentication
is removed.
auth_type:
description:
- Authentication type.
choices: ['text','md5']
state:
description:
- Specify desired state of the resource.
choices: ['present','absent']
default: 'present'
'''
EXAMPLES = r'''
- name: Ensure HSRP is configured with following params on a SVI
nxos_hsrp:
group: 10
vip: 10.1.1.1
priority: 150
interface: vlan10
preempt: enabled
- name: Ensure HSRP is configured with following params on a SVI
with clear text authentication
nxos_hsrp:
group: 10
vip: 10.1.1.1
priority: 150
interface: vlan10
preempt: enabled
auth_type: text
auth_string: CISCO
- name: Ensure HSRP is configured with md5 authentication and clear
authentication string
nxos_hsrp:
group: 10
vip: 10.1.1.1
priority: 150
interface: vlan10
preempt: enabled
auth_type: md5
auth_string: "0 1234"
- name: Ensure HSRP is configured with md5 authentication and hidden
authentication string
nxos_hsrp:
group: 10
vip: 10.1.1.1
priority: 150
interface: vlan10
preempt: enabled
auth_type: md5
auth_string: "7 1234"
- name: Remove HSRP config for given interface, group, and VIP
nxos_hsrp:
group: 10
interface: vlan10
vip: 10.1.1.1
state: absent
'''
RETURN = r'''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["interface vlan10", "hsrp version 2", "hsrp 30", "ip 10.30.1.1"]
'''
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec
from ansible.module_utils.network.nxos.nxos import get_interface_type
from ansible.module_utils.basic import AnsibleModule
PARAM_TO_DEFAULT_KEYMAP = {
'vip': None,
'priority': '100',
'auth_type': 'text',
'auth_string': 'cisco',
}
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
value = table.get(key)
if value:
new_dict[new_key] = str(value)
else:
new_dict[new_key] = value
return new_dict
def get_interface_mode(interface, intf_type, module):
command = 'show interface {0} | json'.format(interface)
interface = {}
mode = 'unknown'
try:
body = run_commands(module, [command])[0]
except IndexError:
return None
if intf_type in ['ethernet', 'portchannel']:
interface_table = body['TABLE_interface']['ROW_interface']
mode = str(interface_table.get('eth_mode', 'layer3'))
if mode == 'access' or mode == 'trunk':
mode = 'layer2'
elif intf_type == 'svi':
mode = 'layer3'
return mode
def get_hsrp_group(group, interface, module):
command = 'show hsrp group {0} all | json'.format(group)
hsrp = {}
hsrp_key = {
'sh_if_index': 'interface',
'sh_group_num': 'group',
'sh_group_version': 'version',
'sh_cfg_prio': 'priority',
'sh_preempt': 'preempt',
'sh_vip': 'vip',
'sh_authentication_type': 'auth_type',
'sh_keystring_attr': 'auth_enc',
'sh_authentication_data': 'auth_string'
}
try:
body = run_commands(module, [command])[0]
hsrp_table = body['TABLE_grp_detail']['ROW_grp_detail']
if 'sh_keystring_attr' not in hsrp_table:
del hsrp_key['sh_keystring_attr']
if 'unknown enum:' in str(hsrp_table):
hsrp_table = get_hsrp_group_unknown_enum(module, command, hsrp_table)
except (AttributeError, IndexError, TypeError, KeyError):
return {}
if isinstance(hsrp_table, dict):
hsrp_table = [hsrp_table]
for hsrp_group in hsrp_table:
parsed_hsrp = apply_key_map(hsrp_key, hsrp_group)
parsed_hsrp['interface'] = parsed_hsrp['interface'].lower()
if parsed_hsrp['version'] == 'v1':
parsed_hsrp['version'] = '1'
elif parsed_hsrp['version'] == 'v2':
parsed_hsrp['version'] = '2'
if parsed_hsrp['auth_type'] == 'md5':
if parsed_hsrp['auth_enc'] == 'hidden':
parsed_hsrp['auth_enc'] = '7'
else:
parsed_hsrp['auth_enc'] = '0'
if parsed_hsrp['interface'] == interface:
return parsed_hsrp
return hsrp
def get_hsrp_group_unknown_enum(module, command, hsrp_table):
'''Some older NXOS images fail to set the attr values when using structured output and
instead set the values to <unknown enum>. This fallback method is a workaround that
uses an unstructured (text) request to query the device a second time.
'sh_preempt' is currently the only attr affected. Add checks for other attrs as needed.
'''
if 'unknown enum:' in hsrp_table['sh_preempt']:
cmd = {'output': 'text', 'command': command.split('|')[0]}
out = run_commands(module, cmd)[0]
hsrp_table['sh_preempt'] = 'enabled' if ('may preempt' in out) else 'disabled'
return hsrp_table
def get_commands_remove_hsrp(group, interface):
commands = ['interface {0}'.format(interface), 'no hsrp {0}'.format(group)]
return commands
def get_commands_config_hsrp(delta, interface, args, existing):
commands = []
config_args = {
'group': 'hsrp {group}',
'priority': '{priority}',
'preempt': '{preempt}',
'vip': '{vip}'
}
preempt = delta.get('preempt', None)
group = delta.get('group', None)
vip = delta.get('vip', None)
priority = delta.get('priority', None)
if preempt:
if preempt == 'enabled':
delta['preempt'] = 'preempt'
elif preempt == 'disabled':
delta['preempt'] = 'no preempt'
if priority:
if priority == 'default':
if existing and existing.get('priority') != PARAM_TO_DEFAULT_KEYMAP.get('priority'):
delta['priority'] = 'no priority'
else:
del(delta['priority'])
else:
delta['priority'] = 'priority {0}'.format(delta['priority'])
if vip:
if vip == 'default':
if existing and existing.get('vip') != PARAM_TO_DEFAULT_KEYMAP.get('vip'):
delta['vip'] = 'no ip'
else:
del(delta['vip'])
else:
delta['vip'] = 'ip {0}'.format(delta['vip'])
for key in delta:
command = config_args.get(key, 'DNE').format(**delta)
if command and command != 'DNE':
if key == 'group':
commands.insert(0, command)
else:
commands.append(command)
command = None
auth_type = delta.get('auth_type', None)
auth_string = delta.get('auth_string', None)
auth_enc = delta.get('auth_enc', None)
if auth_type or auth_string:
if not auth_type:
auth_type = args['auth_type']
elif not auth_string:
auth_string = args['auth_string']
if auth_string != 'default':
if auth_type == 'md5':
command = 'authentication md5 key-string {0} {1}'.format(auth_enc, auth_string)
commands.append(command)
elif auth_type == 'text':
command = 'authentication text {0}'.format(auth_string)
commands.append(command)
else:
if existing and existing.get('auth_string') != PARAM_TO_DEFAULT_KEYMAP.get('auth_string'):
commands.append('no authentication')
if commands and not group:
commands.insert(0, 'hsrp {0}'.format(args['group']))
version = delta.get('version', None)
if version:
if version == '2':
command = 'hsrp version 2'
elif version == '1':
command = 'hsrp version 1'
commands.insert(0, command)
commands.insert(0, 'interface {0}'.format(interface))
if commands:
if not commands[0].startswith('interface'):
commands.insert(0, 'interface {0}'.format(interface))
return commands
def is_default(interface, module):
command = 'show run interface {0}'.format(interface)
try:
body = run_commands(module, [command], check_rc=False)[0]
if 'invalid' in body.lower():
return 'DNE'
else:
raw_list = body.split('\n')
if raw_list[-1].startswith('interface'):
return True
else:
return False
except (KeyError):
return 'DNE'
def validate_config(body, vip, module):
new_body = ''.join(body)
if "invalid ip address" in new_body.lower():
module.fail_json(msg="Invalid VIP. Possible duplicate IP address.",
vip=vip)
def main():
argument_spec = dict(
group=dict(required=True, type='str'),
interface=dict(required=True),
version=dict(choices=['1', '2'], default='1', required=False),
priority=dict(type='str', required=False),
preempt=dict(type='str', choices=['disabled', 'enabled'], required=False),
vip=dict(type='str', required=False),
auth_type=dict(choices=['text', 'md5'], required=False),
auth_string=dict(type='str', required=False),
state=dict(choices=['absent', 'present'], required=False, default='present')
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
results = dict(changed=False, warnings=warnings)
interface = module.params['interface'].lower()
group = module.params['group']
version = module.params['version']
state = module.params['state']
priority = module.params['priority']
preempt = module.params['preempt']
vip = module.params['vip']
auth_type = module.params['auth_type']
auth_full_string = module.params['auth_string']
auth_enc = '0'
auth_string = None
if auth_full_string:
kstr = auth_full_string.split()
if len(kstr) == 2:
auth_enc = kstr[0]
auth_string = kstr[1]
elif len(kstr) == 1:
auth_string = kstr[0]
else:
module.fail_json(msg='Invalid auth_string')
if auth_enc != '0' and auth_enc != '7':
module.fail_json(msg='Invalid auth_string, only 0 or 7 allowed')
device_info = get_capabilities(module)
network_api = device_info.get('network_api', 'nxapi')
intf_type = get_interface_type(interface)
if (intf_type != 'ethernet' and network_api == 'cliconf'):
if is_default(interface, module) == 'DNE':
module.fail_json(msg='That interface does not exist yet. Create '
'it first.', interface=interface)
if intf_type == 'loopback':
module.fail_json(msg="Loopback interfaces don't support HSRP.",
interface=interface)
mode = get_interface_mode(interface, intf_type, module)
if mode == 'layer2':
module.fail_json(msg='That interface is a layer2 port.\nMake it '
'a layer 3 port first.', interface=interface)
if auth_type or auth_string:
if not (auth_type and auth_string):
module.fail_json(msg='When using auth parameters, you need BOTH '
'auth_type AND auth_string.')
args = dict(group=group, version=version, priority=priority,
preempt=preempt, vip=vip, auth_type=auth_type,
auth_string=auth_string, auth_enc=auth_enc)
proposed = dict((k, v) for k, v in args.items() if v is not None)
existing = get_hsrp_group(group, interface, module)
# This will enforce better practice with md5 and hsrp version.
if proposed.get('auth_type', None) == 'md5':
if proposed['version'] == '1':
module.fail_json(msg="It's recommended to use HSRP v2 "
"when auth_type=md5")
elif not proposed.get('auth_type', None) and existing:
if (proposed['version'] == '1' and
existing['auth_type'] == 'md5') and state == 'present':
module.fail_json(msg="Existing auth_type is md5. It's recommended "
"to use HSRP v2 when using md5")
commands = []
if state == 'present':
delta = dict(
set(proposed.items()).difference(existing.items()))
if delta:
command = get_commands_config_hsrp(delta, interface, args, existing)
commands.extend(command)
elif state == 'absent':
if existing:
command = get_commands_remove_hsrp(group, interface)
commands.extend(command)
if commands:
if module.check_mode:
module.exit_json(**results)
else:
load_config(module, commands)
# validate IP
if network_api == 'cliconf' and state == 'present':
commands.insert(0, 'config t')
body = run_commands(module, commands)
validate_config(body, vip, module)
results['changed'] = True
if 'configure' in commands:
commands.pop(0)
results['commands'] = commands
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,163 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Cisco and/or its affiliates.
# 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 nxos_hsrp_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: nxos_hsrp_interfaces
version_added: "2.10"
short_description: 'Manages HSRP attributes of NXOS interfaces.'
description: 'Manages Hot Standby Router Protocol (HSRP) interface attributes.'
author: Chris Van Heuveln (@chrisvanheuveln)
notes:
options:
config:
description: The provided configuration
type: list
elements: dict
suboptions:
name:
type: str
description: The name of the interface.
bfd:
type: str
description:
- Enable/Disable HSRP Bidirectional Forwarding Detection (BFD) on the interface.
choices:
- enable
- disable
state:
description:
- The state the configuration should be left in
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
"""
EXAMPLES = """
# Using deleted
- name: Configure hsrp attributes on interfaces
nxos_hsrp_interfaces:
config:
- name: Ethernet1/1
- name: Ethernet1/2
operation: deleted
# Using merged
- name: Configure hsrp attributes on interfaces
nxos_hsrp_interfaces:
config:
- name: Ethernet1/1
bfd: enable
- name: Ethernet1/2
bfd: disable
operation: merged
# Using overridden
- name: Configure hsrp attributes on interfaces
nxos_hsrp_interfaces:
config:
- name: Ethernet1/1
bfd: enable
- name: Ethernet1/2
bfd: disable
operation: overridden
# Using replaced
- name: Configure hsrp attributes on interfaces
nxos_hsrp_interfaces:
config:
- name: Ethernet1/1
bfd: enable
- name: Ethernet1/2
bfd: disable
operation: replaced
"""
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 Ethernet1/1', 'hsrp bfd']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import Hsrp_interfacesArgs
from ansible.module_utils.network.nxos.config.hsrp_interfaces.hsrp_interfaces import Hsrp_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Hsrp_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Hsrp_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,155 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_igmp
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages IGMP global configuration.
description:
- Manages IGMP global configuration configuration settings.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- When C(state=default), all supported params will be reset to a
default state.
- If restart is set to true with other params set, the restart will happen
last, i.e. after the configuration takes place.
options:
flush_routes:
description:
- Removes routes when the IGMP process is restarted. By default,
routes are not flushed.
type: bool
enforce_rtr_alert:
description:
- Enables or disables the enforce router alert option check for
IGMPv2 and IGMPv3 packets.
type: bool
restart:
description:
- Restarts the igmp process (using an exec config command).
type: bool
state:
description:
- Manages desired state of the resource.
default: present
choices: ['present', 'default']
'''
EXAMPLES = '''
- name: Default igmp global params (all params except restart)
nxos_igmp:
state: default
- name: Ensure the following igmp global config exists on the device
nxos_igmp:
flush_routes: true
enforce_rtr_alert: true
- name: Restart the igmp process
nxos_igmp:
restart: true
'''
RETURN = '''
updates:
description: commands sent to the device
returned: always
type: list
sample: ["ip igmp flush-routes"]
'''
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def get_current(module):
output = run_commands(module, {'command': 'show running-config', 'output': 'text'})
return {
'flush_routes': 'ip igmp flush-routes' in output[0],
'enforce_rtr_alert': 'ip igmp enforce-router-alert' in output[0]
}
def get_desired(module):
return {
'flush_routes': module.params['flush_routes'],
'enforce_rtr_alert': module.params['enforce_rtr_alert']
}
def main():
argument_spec = dict(
flush_routes=dict(type='bool'),
enforce_rtr_alert=dict(type='bool'),
restart=dict(type='bool', default=False),
state=dict(choices=['present', 'default'], default='present')
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
current = get_current(module)
desired = get_desired(module)
state = module.params['state']
commands = list()
if state == 'default':
if current['flush_routes']:
commands.append('no ip igmp flush-routes')
if current['enforce_rtr_alert']:
commands.append('no ip igmp enforce-router-alert')
elif state == 'present':
ldict = {'flush_routes': 'flush-routes', 'enforce_rtr_alert': 'enforce-router-alert'}
for arg in ['flush_routes', 'enforce_rtr_alert']:
if desired[arg] and not current[arg]:
commands.append('ip igmp {0}'.format(ldict.get(arg)))
elif current[arg] and not desired[arg]:
commands.append('no ip igmp {0}'.format(ldict.get(arg)))
result = {'changed': False, 'updates': commands, 'warnings': warnings}
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
if module.params['restart']:
cmd = {'command': 'restart igmp', 'output': 'text'}
run_commands(module, cmd)
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,629 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_igmp_interface
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages IGMP interface configuration.
description:
- Manages IGMP interface configuration settings.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- When C(state=default), supported params will be reset to a default state.
These include C(version), C(startup_query_interval),
C(startup_query_count), C(robustness), C(querier_timeout), C(query_mrt),
C(query_interval), C(last_member_qrt), C(last_member_query_count),
C(group_timeout), C(report_llg), and C(immediate_leave).
- When C(state=absent), all configs for C(oif_ps), and
C(oif_routemap) will be removed.
- PIM must be enabled to use this module.
- This module is for Layer 3 interfaces.
- Route-map check not performed (same as CLI) check when configuring
route-map with 'static-oif'
- If restart is set to true with other params set, the restart will happen
last, i.e. after the configuration takes place. However, 'restart' itself
is not idempotent as it is an action and not configuration.
options:
interface:
description:
- The full interface name for IGMP configuration.
e.g. I(Ethernet1/2).
required: true
version:
description:
- IGMP version. It can be 2 or 3 or keyword 'default'.
choices: ['2', '3', 'default']
startup_query_interval:
description:
- Query interval used when the IGMP process starts up.
The range is from 1 to 18000 or keyword 'default'.
The default is 31.
startup_query_count:
description:
- Query count used when the IGMP process starts up.
The range is from 1 to 10 or keyword 'default'.
The default is 2.
robustness:
description:
- Sets the robustness variable. Values can range from 1 to 7 or
keyword 'default'. The default is 2.
querier_timeout:
description:
- Sets the querier timeout that the software uses when deciding
to take over as the querier. Values can range from 1 to 65535
seconds or keyword 'default'. The default is 255 seconds.
query_mrt:
description:
- Sets the response time advertised in IGMP queries.
Values can range from 1 to 25 seconds or keyword 'default'.
The default is 10 seconds.
query_interval:
description:
- Sets the frequency at which the software sends IGMP host query
messages. Values can range from 1 to 18000 seconds or keyword
'default'. The default is 125 seconds.
last_member_qrt:
description:
- Sets the query interval waited after sending membership reports
before the software deletes the group state. Values can range
from 1 to 25 seconds or keyword 'default'. The default is 1 second.
last_member_query_count:
description:
- Sets the number of times that the software sends an IGMP query
in response to a host leave message.
Values can range from 1 to 5 or keyword 'default'. The default is 2.
group_timeout:
description:
- Sets the group membership timeout for IGMPv2.
Values can range from 3 to 65,535 seconds or keyword 'default'.
The default is 260 seconds.
report_llg:
description:
- Configures report-link-local-groups.
Enables sending reports for groups in 224.0.0.0/24.
Reports are always sent for nonlink local groups.
By default, reports are not sent for link local groups.
type: bool
immediate_leave:
description:
- Enables the device to remove the group entry from the multicast
routing table immediately upon receiving a leave message for
the group. Use this command to minimize the leave latency of
IGMPv2 group memberships on a given IGMP interface because the
device does not send group-specific queries.
The default is disabled.
type: bool
oif_routemap:
description:
- Configure a routemap for static outgoing interface (OIF) or
keyword 'default'.
oif_ps:
description:
- Configure prefixes and sources for static outgoing interface (OIF). This
is a list of dict where each dict has source and prefix defined or just
prefix if source is not needed. The specified values will be configured
on the device and if any previous prefix/sources exist, they will be removed.
Keyword 'default' is also accepted which removes all existing prefix/sources.
version_added: 2.6
restart:
description:
- Restart IGMP. This is NOT idempotent as this is action only.
type: bool
default: False
state:
description:
- Manages desired state of the resource.
default: present
choices: ['present', 'absent', 'default']
'''
EXAMPLES = '''
- nxos_igmp_interface:
interface: ethernet1/32
startup_query_interval: 30
oif_ps:
- { 'prefix': '238.2.2.6' }
- { 'source': '192.168.0.1', 'prefix': '238.2.2.5'}
state: present
'''
RETURN = '''
proposed:
description: k/v pairs of parameters passed into module
returned: always
type: dict
sample: {"startup_query_count": "30",
"oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]}
existing:
description: k/v pairs of existing igmp_interface configuration
returned: always
type: dict
sample: {"startup_query_count": "2", "oif_ps": []}
end_state:
description: k/v pairs of igmp interface configuration after module execution
returned: always
type: dict
sample: {"startup_query_count": "30",
"oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]}
updates:
description: commands sent to the device
returned: always
type: list
sample: ["interface Ethernet1/32", "ip igmp startup-query-count 30",
"ip igmp static-oif 238.2.2.6", "ip igmp static-oif 238.2.2.5 source 192.168.0.1"]
changed:
description: check to see if a change was made on the device
returned: always
type: bool
sample: true
'''
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.network.nxos.nxos import get_interface_type
from ansible.module_utils.basic import AnsibleModule
import re
def execute_show_command(command, module, command_type='cli_show'):
if command_type == 'cli_show_ascii':
cmds = [{
'command': command,
'output': 'text',
}]
else:
cmds = [{
'command': command,
'output': 'json',
}]
return run_commands(module, cmds)
def get_interface_mode(interface, intf_type, module):
command = 'show interface {0}'.format(interface)
interface = {}
mode = 'unknown'
if intf_type in ['ethernet', 'portchannel']:
body = execute_show_command(command, module)[0]
interface_table = body['TABLE_interface']['ROW_interface']
mode = str(interface_table.get('eth_mode', 'layer3'))
if mode == 'access' or mode == 'trunk':
mode = 'layer2'
elif intf_type == 'loopback' or intf_type == 'svi':
mode = 'layer3'
return mode
def apply_key_map(key_map, table):
new_dict = {}
for key, value in table.items():
new_key = key_map.get(key)
if new_key:
value = table.get(key)
if value:
new_dict[new_key] = value
else:
new_dict[new_key] = value
return new_dict
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def get_igmp_interface(module, interface):
command = 'show ip igmp interface {0}'.format(interface)
igmp = {}
key_map = {
'IGMPVersion': 'version',
'ConfiguredStartupQueryInterval': 'startup_query_interval',
'StartupQueryCount': 'startup_query_count',
'RobustnessVariable': 'robustness',
'ConfiguredQuerierTimeout': 'querier_timeout',
'ConfiguredMaxResponseTime': 'query_mrt',
'ConfiguredQueryInterval': 'query_interval',
'LastMemberMTR': 'last_member_qrt',
'LastMemberQueryCount': 'last_member_query_count',
'ConfiguredGroupTimeout': 'group_timeout'
}
body = execute_show_command(command, module)[0]
if body:
if 'not running' in body:
return igmp
resource = body['TABLE_vrf']['ROW_vrf']['TABLE_if']['ROW_if']
igmp = apply_key_map(key_map, resource)
report_llg = str(resource['ReportingForLinkLocal']).lower()
if report_llg == 'true':
igmp['report_llg'] = True
elif report_llg == 'false':
igmp['report_llg'] = False
immediate_leave = str(resource['ImmediateLeave']).lower() # returns en or dis
if re.search(r'^en|^true|^enabled', immediate_leave):
igmp['immediate_leave'] = True
elif re.search(r'^dis|^false|^disabled', immediate_leave):
igmp['immediate_leave'] = False
# the next block of code is used to retrieve anything with:
# ip igmp static-oif *** i.e.. could be route-map ROUTEMAP
# or PREFIX source <ip>, etc.
command = 'show run interface {0} | inc oif'.format(interface)
body = execute_show_command(
command, module, command_type='cli_show_ascii')[0]
staticoif = []
if body:
split_body = body.split('\n')
route_map_regex = (r'.*ip igmp static-oif route-map\s+'
r'(?P<route_map>\S+).*')
prefix_source_regex = (r'.*ip igmp static-oif\s+(?P<prefix>'
r'((\d+.){3}\d+))(\ssource\s'
r'(?P<source>\S+))?.*')
for line in split_body:
temp = {}
try:
match_route_map = re.match(route_map_regex, line, re.DOTALL)
route_map = match_route_map.groupdict()['route_map']
except AttributeError:
route_map = ''
try:
match_prefix_source = re.match(
prefix_source_regex, line, re.DOTALL)
prefix_source_group = match_prefix_source.groupdict()
prefix = prefix_source_group['prefix']
source = prefix_source_group['source']
except AttributeError:
prefix = ''
source = ''
if route_map:
temp['route_map'] = route_map
if prefix:
temp['prefix'] = prefix
if source:
temp['source'] = source
if temp:
staticoif.append(temp)
igmp['oif_routemap'] = None
igmp['oif_prefix_source'] = []
if staticoif:
if len(staticoif) == 1 and staticoif[0].get('route_map'):
igmp['oif_routemap'] = staticoif[0]['route_map']
else:
igmp['oif_prefix_source'] = staticoif
return igmp
def config_igmp_interface(delta, existing, existing_oif_prefix_source):
CMDS = {
'version': 'ip igmp version {0}',
'startup_query_interval': 'ip igmp startup-query-interval {0}',
'startup_query_count': 'ip igmp startup-query-count {0}',
'robustness': 'ip igmp robustness-variable {0}',
'querier_timeout': 'ip igmp querier-timeout {0}',
'query_mrt': 'ip igmp query-max-response-time {0}',
'query_interval': 'ip igmp query-interval {0}',
'last_member_qrt': 'ip igmp last-member-query-response-time {0}',
'last_member_query_count': 'ip igmp last-member-query-count {0}',
'group_timeout': 'ip igmp group-timeout {0}',
'report_llg': 'ip igmp report-link-local-groups',
'immediate_leave': 'ip igmp immediate-leave',
'oif_prefix_source': 'ip igmp static-oif {0} source {1} ',
'oif_routemap': 'ip igmp static-oif route-map {0}',
'oif_prefix': 'ip igmp static-oif {0}',
}
commands = []
command = None
def_vals = get_igmp_interface_defaults()
for key, value in delta.items():
if key == 'oif_ps' and value != 'default':
for each in value:
if each in existing_oif_prefix_source:
existing_oif_prefix_source.remove(each)
else:
# add new prefix/sources
pf = each['prefix']
src = ''
if 'source' in each.keys():
src = each['source']
if src:
commands.append(CMDS.get('oif_prefix_source').format(pf, src))
else:
commands.append(CMDS.get('oif_prefix').format(pf))
if existing_oif_prefix_source:
for each in existing_oif_prefix_source:
# remove stale prefix/sources
pf = each['prefix']
src = ''
if 'source' in each.keys():
src = each['source']
if src:
commands.append('no ' + CMDS.get('oif_prefix_source').format(pf, src))
else:
commands.append('no ' + CMDS.get('oif_prefix').format(pf))
elif key == 'oif_routemap':
if value == 'default':
if existing.get(key):
command = 'no ' + CMDS.get(key).format(existing.get(key))
else:
command = CMDS.get(key).format(value)
elif value:
if value == 'default':
if def_vals.get(key) != existing.get(key):
command = CMDS.get(key).format(def_vals.get(key))
else:
command = CMDS.get(key).format(value)
elif not value:
command = 'no {0}'.format(CMDS.get(key).format(value))
if command:
if command not in commands:
commands.append(command)
command = None
return commands
def get_igmp_interface_defaults():
version = '2'
startup_query_interval = '31'
startup_query_count = '2'
robustness = '2'
querier_timeout = '255'
query_mrt = '10'
query_interval = '125'
last_member_qrt = '1'
last_member_query_count = '2'
group_timeout = '260'
report_llg = False
immediate_leave = False
args = dict(version=version, startup_query_interval=startup_query_interval,
startup_query_count=startup_query_count, robustness=robustness,
querier_timeout=querier_timeout, query_mrt=query_mrt,
query_interval=query_interval, last_member_qrt=last_member_qrt,
last_member_query_count=last_member_query_count,
group_timeout=group_timeout, report_llg=report_llg,
immediate_leave=immediate_leave)
default = dict((param, value) for (param, value) in args.items()
if value is not None)
return default
def config_default_igmp_interface(existing, delta):
commands = []
proposed = get_igmp_interface_defaults()
delta = dict(set(proposed.items()).difference(existing.items()))
if delta:
command = config_igmp_interface(delta, existing, existing_oif_prefix_source=None)
if command:
for each in command:
commands.append(each)
return commands
def config_remove_oif(existing, existing_oif_prefix_source):
commands = []
command = None
if existing.get('oif_routemap'):
commands.append('no ip igmp static-oif route-map {0}'.format(existing.get('oif_routemap')))
elif existing_oif_prefix_source:
for each in existing_oif_prefix_source:
if each.get('prefix') and each.get('source'):
command = 'no ip igmp static-oif {0} source {1} '.format(
each.get('prefix'), each.get('source')
)
elif each.get('prefix'):
command = 'no ip igmp static-oif {0}'.format(
each.get('prefix')
)
if command:
commands.append(command)
command = None
return commands
def main():
argument_spec = dict(
interface=dict(required=True, type='str'),
version=dict(required=False, type='str'),
startup_query_interval=dict(required=False, type='str'),
startup_query_count=dict(required=False, type='str'),
robustness=dict(required=False, type='str'),
querier_timeout=dict(required=False, type='str'),
query_mrt=dict(required=False, type='str'),
query_interval=dict(required=False, type='str'),
last_member_qrt=dict(required=False, type='str'),
last_member_query_count=dict(required=False, type='str'),
group_timeout=dict(required=False, type='str'),
report_llg=dict(type='bool'),
immediate_leave=dict(type='bool'),
oif_routemap=dict(required=False, type='str'),
oif_ps=dict(required=False, type='raw'),
restart=dict(type='bool', default=False),
state=dict(choices=['present', 'absent', 'default'],
default='present')
)
argument_spec.update(nxos_argument_spec)
mutually_exclusive = [('oif_ps', 'oif_routemap')]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
state = module.params['state']
interface = module.params['interface']
oif_routemap = module.params['oif_routemap']
oif_ps = module.params['oif_ps']
intf_type = get_interface_type(interface)
if get_interface_mode(interface, intf_type, module) == 'layer2':
module.fail_json(msg='this module only works on Layer 3 interfaces')
existing = get_igmp_interface(module, interface)
existing_copy = existing.copy()
end_state = existing_copy
if not existing.get('version'):
module.fail_json(msg='pim needs to be enabled on the interface')
existing_oif_prefix_source = existing.get('oif_prefix_source')
# not json serializable
existing.pop('oif_prefix_source')
if oif_routemap and existing_oif_prefix_source:
module.fail_json(msg='Delete static-oif configurations on this '
'interface if you want to use a routemap')
if oif_ps and existing.get('oif_routemap'):
module.fail_json(msg='Delete static-oif route-map configuration '
'on this interface if you want to config '
'static entries')
args = [
'version',
'startup_query_interval',
'startup_query_count',
'robustness',
'querier_timeout',
'query_mrt',
'query_interval',
'last_member_qrt',
'last_member_query_count',
'group_timeout',
'report_llg',
'immediate_leave',
'oif_routemap',
]
changed = False
commands = []
proposed = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
CANNOT_ABSENT = ['version', 'startup_query_interval',
'startup_query_count', 'robustness', 'querier_timeout',
'query_mrt', 'query_interval', 'last_member_qrt',
'last_member_query_count', 'group_timeout', 'report_llg',
'immediate_leave']
if state == 'absent':
for each in CANNOT_ABSENT:
if each in proposed:
module.fail_json(msg='only params: '
'oif_ps, oif_routemap can be used when '
'state=absent')
# delta check for all params except oif_ps
delta = dict(set(proposed.items()).difference(existing.items()))
if oif_ps:
if oif_ps == 'default':
delta['oif_ps'] = []
else:
delta['oif_ps'] = oif_ps
if state == 'present':
if delta:
command = config_igmp_interface(delta, existing, existing_oif_prefix_source)
if command:
commands.append(command)
elif state == 'default':
command = config_default_igmp_interface(existing, delta)
if command:
commands.append(command)
elif state == 'absent':
command = None
if existing.get('oif_routemap') or existing_oif_prefix_source:
command = config_remove_oif(existing, existing_oif_prefix_source)
if command:
commands.append(command)
command = config_default_igmp_interface(existing, delta)
if command:
commands.append(command)
cmds = []
results = {}
if commands:
commands.insert(0, ['interface {0}'.format(interface)])
cmds = flatten_list(commands)
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
load_config(module, cmds)
changed = True
end_state = get_igmp_interface(module, interface)
if 'configure' in cmds:
cmds.pop(0)
if module.params['restart']:
cmd = {'command': 'restart igmp', 'output': 'text'}
run_commands(module, cmd)
results['proposed'] = proposed
results['existing'] = existing_copy
results['updates'] = cmds
results['changed'] = changed
results['warnings'] = warnings
results['end_state'] = end_state
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,305 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_igmp_snooping
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages IGMP snooping global configuration.
description:
- Manages IGMP snooping global configuration.
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- When C(state=default), params will be reset to a default state.
- C(group_timeout) also accepts I(never) as an input.
options:
snooping:
description:
- Enables/disables IGMP snooping on the switch.
type: bool
group_timeout:
description:
- Group membership timeout value for all VLANs on the device.
Accepted values are integer in range 1-10080, I(never) and
I(default).
link_local_grp_supp:
description:
- Global link-local groups suppression.
type: bool
report_supp:
description:
- Global IGMPv1/IGMPv2 Report Suppression.
type: bool
v3_report_supp:
description:
- Global IGMPv3 Report Suppression and Proxy Reporting.
type: bool
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','default']
'''
EXAMPLES = '''
# ensure igmp snooping params supported in this module are in there default state
- nxos_igmp_snooping:
state: default
# ensure following igmp snooping params are in the desired state
- nxos_igmp_snooping:
group_timeout: never
snooping: true
link_local_grp_supp: false
optimize_mcast_flood: false
report_supp: true
v3_report_supp: true
'''
RETURN = '''
commands:
description: command sent to the device
returned: always
type: list
sample: ["ip igmp snooping link-local-groups-suppression",
"ip igmp snooping group-timeout 50",
"no ip igmp snooping report-suppression",
"no ip igmp snooping v3-report-suppression",
"no ip igmp snooping"]
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def execute_show_command(command, module, output='text'):
command = {
'command': command,
'output': output,
}
return run_commands(module, [command])
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def get_group_timeout(config):
match = re.search(r' Group timeout configured: (\S+)', config, re.M)
if match:
value = match.group(1)
else:
value = ''
return value
def get_igmp_snooping(module):
command = 'show ip igmp snooping'
existing = {}
try:
body = execute_show_command(command, module, output='json')[0]
except IndexError:
body = []
if body:
snooping = str(body.get('enabled')).lower()
if snooping == 'true' or snooping == 'enabled':
existing['snooping'] = True
else:
existing['snooping'] = False
report_supp = str(body.get('grepsup')).lower()
if report_supp == 'true' or report_supp == 'enabled':
existing['report_supp'] = True
else:
existing['report_supp'] = False
link_local_grp_supp = str(body.get('glinklocalgrpsup')).lower()
if link_local_grp_supp == 'true' or link_local_grp_supp == 'enabled':
existing['link_local_grp_supp'] = True
else:
existing['link_local_grp_supp'] = False
v3_report_supp = str(body.get('gv3repsup')).lower()
if v3_report_supp == 'true' or v3_report_supp == 'enabled':
existing['v3_report_supp'] = True
else:
existing['v3_report_supp'] = False
command = 'show ip igmp snooping'
body = execute_show_command(command, module)[0]
if body:
existing['group_timeout'] = get_group_timeout(body)
return existing
def config_igmp_snooping(delta, existing, default=False):
CMDS = {
'snooping': 'ip igmp snooping',
'group_timeout': 'ip igmp snooping group-timeout {}',
'link_local_grp_supp': 'ip igmp snooping link-local-groups-suppression',
'v3_report_supp': 'ip igmp snooping v3-report-suppression',
'report_supp': 'ip igmp snooping report-suppression'
}
commands = []
command = None
gt_command = None
for key, value in delta.items():
if value:
if default and key == 'group_timeout':
if existing.get(key):
gt_command = 'no ' + CMDS.get(key).format(existing.get(key))
elif value == 'default' and key == 'group_timeout':
if existing.get(key):
command = 'no ' + CMDS.get(key).format(existing.get(key))
else:
command = CMDS.get(key).format(value)
else:
command = 'no ' + CMDS.get(key).format(value)
if command:
commands.append(command)
command = None
if gt_command:
# ensure that group-timeout command is configured last
commands.append(gt_command)
return commands
def get_igmp_snooping_defaults():
group_timeout = 'dummy'
report_supp = True
link_local_grp_supp = True
v3_report_supp = False
snooping = True
args = dict(snooping=snooping, link_local_grp_supp=link_local_grp_supp,
report_supp=report_supp, v3_report_supp=v3_report_supp,
group_timeout=group_timeout)
default = dict((param, value) for (param, value) in args.items()
if value is not None)
return default
def igmp_snooping_gt_dependency(command, existing, module):
# group-timeout will fail if igmp snooping is disabled
gt = [i for i in command if i.startswith('ip igmp snooping group-timeout')]
if gt:
if 'no ip igmp snooping' in command or (existing['snooping'] is False and 'ip igmp snooping' not in command):
msg = "group-timeout cannot be enabled or changed when ip igmp snooping is disabled"
module.fail_json(msg=msg)
else:
# ensure that group-timeout command is configured last
command.remove(gt[0])
command.append(gt[0])
def main():
argument_spec = dict(
snooping=dict(required=False, type='bool'),
group_timeout=dict(required=False, type='str'),
link_local_grp_supp=dict(required=False, type='bool'),
report_supp=dict(required=False, type='bool'),
v3_report_supp=dict(required=False, type='bool'),
state=dict(choices=['present', 'default'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
results = {'changed': False, 'commands': [], 'warnings': warnings}
snooping = module.params['snooping']
link_local_grp_supp = module.params['link_local_grp_supp']
report_supp = module.params['report_supp']
v3_report_supp = module.params['v3_report_supp']
group_timeout = module.params['group_timeout']
state = module.params['state']
args = dict(snooping=snooping, link_local_grp_supp=link_local_grp_supp,
report_supp=report_supp, v3_report_supp=v3_report_supp,
group_timeout=group_timeout)
proposed = dict((param, value) for (param, value) in args.items()
if value is not None)
existing = get_igmp_snooping(module)
commands = []
if state == 'present':
delta = dict(
set(proposed.items()).difference(existing.items())
)
if delta:
command = config_igmp_snooping(delta, existing)
if command:
if group_timeout:
igmp_snooping_gt_dependency(command, existing, module)
commands.append(command)
elif state == 'default':
proposed = get_igmp_snooping_defaults()
delta = dict(
set(proposed.items()).difference(existing.items())
)
if delta:
command = config_igmp_snooping(delta, existing, default=True)
if command:
commands.append(command)
cmds = flatten_list(commands)
if cmds:
results['changed'] = True
if not module.check_mode:
load_config(module, cmds)
if 'configure' in cmds:
cmds.pop(0)
results['commands'] = cmds
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,596 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_install_os
extends_documentation_fragment: nxos
short_description: Set boot options like boot, kickstart image and issu.
description:
- Install an operating system by setting the boot options like boot
image and kickstart image and optionally select to install using
ISSU (In Server Software Upgrade).
notes:
- Tested against the following platforms and images
- N9k 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(1), 7.0(3)F2(2), 7.0(3)F3(2)
- N3k 6.0(2)A8(6), 6.0(2)A8(8), 7.0(3)I6(1), 7.0(3)I7(1)
- N7k 7.3(0)D1(1), 8.0(1), 8.1(1), 8.2(1)
- This module requires both the ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and
ANSIBLE_PERSISTENT_COMMAND_TIMEOUT timers to be set to 600 seconds or higher.
The module will exit if the timers are not set properly.
- When using connection local, ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and
ANSIBLE_PERSISTENT_COMMAND_TIMEOUT can only be set using ENV variables or
the ansible.cfg file.
- Do not include full file paths, just the name of the file(s) stored on
the top level flash directory.
- This module attempts to install the software immediately,
which may trigger a reboot.
- In check mode, the module will indicate if an upgrade is needed and
whether or not the upgrade is disruptive or non-disruptive(ISSU).
author:
- Jason Edelman (@jedelman8)
- Gabriele Gerbibo (@GGabriele)
version_added: 2.2
options:
system_image_file:
description:
- Name of the system (or combined) image file on flash.
required: true
kickstart_image_file:
description:
- Name of the kickstart image file on flash.
(Not required on all Nexus platforms)
issu:
version_added: "2.5"
description:
- Upgrade using In Service Software Upgrade (ISSU).
(Supported on N5k, N7k, N9k platforms)
- Selecting 'required' or 'yes' means that upgrades will only
proceed if the switch is capable of ISSU.
- Selecting 'desired' means that upgrades will use ISSU if possible
but will fall back to disruptive upgrade if needed.
- Selecting 'no' means do not use ISSU. Forced disruptive.
choices: ['required','desired', 'yes', 'no']
default: 'no'
'''
EXAMPLES = '''
- name: Install OS on N9k
check_mode: no
nxos_install_os:
system_image_file: nxos.7.0.3.I6.1.bin
issu: desired
- name: Wait for device to come back up with new image
wait_for:
port: 22
state: started
timeout: 500
delay: 60
host: "{{ inventory_hostname }}"
- name: Check installed OS for newly installed version
nxos_command:
commands: ['show version | json']
provider: "{{ connection }}"
register: output
- assert:
that:
- output['stdout'][0]['kickstart_ver_str'] == '7.0(3)I6(1)'
'''
RETURN = '''
install_state:
description: Boot and install information.
returned: always
type: dict
sample: {
"install_state": [
"Compatibility check is done:",
"Module bootable Impact Install-type Reason",
"------ -------- -------------- ------------ ------",
" 1 yes non-disruptive reset ",
"Images will be upgraded according to following table:",
"Module Image Running-Version(pri:alt) New-Version Upg-Required",
"------ ---------- ---------------------------------------- -------------------- ------------",
" 1 nxos 7.0(3)I6(1) 7.0(3)I7(1) yes",
" 1 bios v4.4.0(07/12/2017) v4.4.0(07/12/2017) no"
],
}
'''
import re
from time import sleep
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
# Output options are 'text' or 'json'
def execute_show_command(module, command, output='text'):
cmds = [{
'command': command,
'output': output,
}]
return run_commands(module, cmds)
def get_platform(module):
"""Determine platform type"""
data = execute_show_command(module, 'show inventory', 'json')
pid = data[0]['TABLE_inv']['ROW_inv'][0]['productid']
if re.search(r'N3K', pid):
type = 'N3K'
elif re.search(r'N5K', pid):
type = 'N5K'
elif re.search(r'N6K', pid):
type = 'N6K'
elif re.search(r'N7K', pid):
type = 'N7K'
elif re.search(r'N9K', pid):
type = 'N9K'
else:
type = 'unknown'
return type
def parse_show_install(data):
"""Helper method to parse the output of the 'show install all impact' or
'install all' commands.
Sample Output:
Installer will perform impact only check. Please wait.
Verifying image bootflash:/nxos.7.0.3.F2.2.bin for boot variable "nxos".
[####################] 100% -- SUCCESS
Verifying image type.
[####################] 100% -- SUCCESS
Preparing "bios" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
[####################] 100% -- SUCCESS
Preparing "nxos" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
[####################] 100% -- SUCCESS
Performing module support checks.
[####################] 100% -- SUCCESS
Notifying services about system upgrade.
[####################] 100% -- SUCCESS
Compatibility check is done:
Module bootable Impact Install-type Reason
------ -------- -------------- ------------ ------
8 yes disruptive reset Incompatible image for ISSU
21 yes disruptive reset Incompatible image for ISSU
Images will be upgraded according to following table:
Module Image Running-Version(pri:alt) New-Version Upg-Required
------ ---------- ---------------------------------------- ------------
8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
8 bios v01.17 v01.17 no
21 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
21 bios v01.70 v01.70 no
"""
if len(data) > 0:
data = massage_install_data(data)
ud = {'raw': data}
ud['processed'] = []
ud['disruptive'] = False
ud['upgrade_needed'] = False
ud['error'] = False
ud['invalid_command'] = False
ud['install_in_progress'] = False
ud['server_error'] = False
ud['upgrade_succeeded'] = False
ud['use_impact_data'] = False
# Check for server errors
if isinstance(data, int):
if data == -1:
ud['server_error'] = True
elif data >= 500:
ud['server_error'] = True
elif data == -32603:
ud['server_error'] = True
elif data == 1:
ud['server_error'] = True
return ud
else:
ud['list_data'] = data.split('\n')
for x in ud['list_data']:
# Check for errors and exit if found.
if re.search(r'Pre-upgrade check failed', x):
ud['error'] = True
break
if re.search(r'[I|i]nvalid command', x):
ud['invalid_command'] = True
ud['error'] = True
break
if re.search(r'No install all data found', x):
ud['error'] = True
break
# Check for potentially transient conditions
if re.search(r'Another install procedure may\s*be in progress', x):
ud['install_in_progress'] = True
break
if re.search(r'Backend processing error', x):
ud['server_error'] = True
break
if re.search(r'timed out', x):
ud['server_error'] = True
break
if re.search(r'^(-1|5\d\d)$', x):
ud['server_error'] = True
break
# Check for messages indicating a successful upgrade.
if re.search(r'Finishing the upgrade', x):
ud['upgrade_succeeded'] = True
break
if re.search(r'Install has been successful', x):
ud['upgrade_succeeded'] = True
break
if re.search(r'Switching over onto standby', x):
ud['upgrade_succeeded'] = True
break
# We get these messages when the upgrade is non-disruptive and
# we loose connection with the switchover but far enough along that
# we can be confident the upgrade succeeded.
if re.search(r'timeout .*trying to send command: install', x):
ud['upgrade_succeeded'] = True
ud['use_impact_data'] = True
break
if re.search(r'[C|c]onnection failure: timed out', x):
ud['upgrade_succeeded'] = True
ud['use_impact_data'] = True
break
# Begin normal parsing.
if re.search(r'----|Module|Images will|Compatibility', x):
ud['processed'].append(x)
continue
# Check to see if upgrade will be disruptive or non-disruptive and
# build dictionary of individual modules and their status.
# Sample Line:
#
# Module bootable Impact Install-type Reason
# ------ -------- ---------- ------------ ------
# 8 yes disruptive reset Incompatible image
rd = r'(\d+)\s+(\S+)\s+(disruptive|non-disruptive)\s+(\S+)'
mo = re.search(rd, x)
if mo:
ud['processed'].append(x)
key = 'm%s' % mo.group(1)
field = 'disruptive'
if mo.group(3) == 'non-disruptive':
ud[key] = {field: False}
else:
ud[field] = True
ud[key] = {field: True}
field = 'bootable'
if mo.group(2) == 'yes':
ud[key].update({field: True})
else:
ud[key].update({field: False})
continue
# Check to see if switch needs an upgrade and build a dictionary
# of individual modules and their individual upgrade status.
# Sample Line:
#
# Module Image Running-Version(pri:alt) New-Version Upg-Required
# ------ ----- ---------------------------------------- ------------
# 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
mo = re.search(r'(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(yes|no)', x)
if mo:
ud['processed'].append(x)
key = 'm%s_%s' % (mo.group(1), mo.group(2))
field = 'upgrade_needed'
if mo.group(5) == 'yes':
ud[field] = True
ud[key] = {field: True}
else:
ud[key] = {field: False}
continue
return ud
def massage_install_data(data):
# Transport cli returns a list containing one result item.
# Transport nxapi returns a list containing two items. The second item
# contains the data we are interested in.
default_error_msg = 'No install all data found'
if len(data) == 1:
result_data = data[0]
elif len(data) == 2:
result_data = data[1]
else:
result_data = default_error_msg
# Further processing may be needed for result_data
if len(data) == 2 and isinstance(data[1], dict):
if 'clierror' in data[1].keys():
result_data = data[1]['clierror']
elif 'code' in data[1].keys() and data[1]['code'] == '500':
# We encountered a backend processing error for nxapi
result_data = data[1]['msg']
else:
result_data = default_error_msg
return result_data
def build_install_cmd_set(issu, image, kick, type, force=True):
commands = ['terminal dont-ask']
# Different NX-OS platforms behave differently for
# disruptive and non-disruptive upgrade paths.
#
# 1) Combined kickstart/system image:
# * Use option 'non-disruptive' for issu.
# * Omit option 'non-disruptive' for disruptive upgrades.
# 2) Separate kickstart + system images.
# * Omit hidden 'force' option for issu.
# * Use hidden 'force' option for disruptive upgrades.
# * Note: Not supported on all platforms
if re.search(r'required|desired|yes', issu):
if kick is None:
issu_cmd = 'non-disruptive'
else:
issu_cmd = ''
else:
if kick is None:
issu_cmd = ''
else:
issu_cmd = 'force' if force else ''
if type == 'impact':
rootcmd = 'show install all impact'
# The force option is not available for the impact command.
if kick:
issu_cmd = ''
else:
rootcmd = 'install all'
if kick is None:
commands.append(
'%s nxos %s %s' % (rootcmd, image, issu_cmd))
else:
commands.append(
'%s %s system %s kickstart %s' % (rootcmd, issu_cmd, image, kick))
return commands
def parse_show_version(data):
version_data = {'raw': data[0].split('\n')}
version_data['version'] = ''
version_data['error'] = False
for x in version_data['raw']:
mo = re.search(r'(kickstart|system|NXOS):\s+version\s+(\S+)', x)
if mo:
version_data['version'] = mo.group(2)
continue
if version_data['version'] == '':
version_data['error'] = True
return version_data
def check_mode_legacy(module, issu, image, kick=None):
"""Some platforms/images/transports don't support the 'install all impact'
command so we need to use a different method."""
current = execute_show_command(module, 'show version', 'json')[0]
# Call parse_show_data on empty string to create the default upgrade
# data structure dictionary
data = parse_show_install('')
upgrade_msg = 'No upgrade required'
# Process System Image
data['error'] = False
tsver = 'show version image bootflash:%s' % image
data['upgrade_cmd'] = [tsver]
target_image = parse_show_version(execute_show_command(module, tsver))
if target_image['error']:
data['error'] = True
data['raw'] = target_image['raw']
if current['kickstart_ver_str'] != target_image['version'] and not data['error']:
data['upgrade_needed'] = True
data['disruptive'] = True
upgrade_msg = 'Switch upgraded: system: %s' % tsver
# Process Kickstart Image
if kick is not None and not data['error']:
tkver = 'show version image bootflash:%s' % kick
data['upgrade_cmd'].append(tsver)
target_kick = parse_show_version(execute_show_command(module, tkver))
if target_kick['error']:
data['error'] = True
data['raw'] = target_kick['raw']
if current['kickstart_ver_str'] != target_kick['version'] and not data['error']:
data['upgrade_needed'] = True
data['disruptive'] = True
upgrade_msg = upgrade_msg + ' kickstart: %s' % tkver
data['list_data'] = data['raw']
data['processed'] = upgrade_msg
return data
def check_mode_nextgen(module, issu, image, kick=None):
"""Use the 'install all impact' command for check_mode"""
opts = {'ignore_timeout': True}
commands = build_install_cmd_set(issu, image, kick, 'impact')
data = parse_show_install(load_config(module, commands, True, opts))
# If an error is encountered when issu is 'desired' then try again
# but set issu to 'no'
if data['error'] and issu == 'desired':
issu = 'no'
commands = build_install_cmd_set(issu, image, kick, 'impact')
# The system may be busy from the previous call to check_mode so loop
# until it's done.
data = check_install_in_progress(module, commands, opts)
if data['server_error']:
data['error'] = True
data['upgrade_cmd'] = commands
return data
def check_install_in_progress(module, commands, opts):
for attempt in range(20):
data = parse_show_install(load_config(module, commands, True, opts))
if data['install_in_progress']:
sleep(1)
continue
break
return data
def check_mode(module, issu, image, kick=None):
"""Check switch upgrade impact using 'show install all impact' command"""
data = check_mode_nextgen(module, issu, image, kick)
if data['server_error']:
# We encountered an unrecoverable error in the attempt to get upgrade
# impact data from the 'show install all impact' command.
# Fallback to legacy method.
data = check_mode_legacy(module, issu, image, kick)
if data['invalid_command']:
# If we are upgrading from a device running a separate kickstart and
# system image the impact command will fail.
# Fallback to legacy method.
data = check_mode_legacy(module, issu, image, kick)
return data
def do_install_all(module, issu, image, kick=None):
"""Perform the switch upgrade using the 'install all' command"""
impact_data = check_mode(module, issu, image, kick)
if module.check_mode:
# Check mode set in the playbook so just return the impact data.
msg = '*** SWITCH WAS NOT UPGRADED: IMPACT DATA ONLY ***'
impact_data['processed'].append(msg)
return impact_data
if impact_data['error']:
# Check mode discovered an error so return with this info.
return impact_data
elif not impact_data['upgrade_needed']:
# The switch is already upgraded. Nothing more to do.
return impact_data
else:
# If we get here, check_mode returned no errors and the switch
# needs to be upgraded.
if impact_data['disruptive']:
# Check mode indicated that ISSU is not possible so issue the
# upgrade command without the non-disruptive flag unless the
# playbook specified issu: yes/required.
if issu == 'yes':
msg = 'ISSU/ISSD requested but impact data indicates ISSU/ISSD is not possible'
module.fail_json(msg=msg, raw_data=impact_data['list_data'])
else:
issu = 'no'
commands = build_install_cmd_set(issu, image, kick, 'install')
opts = {'ignore_timeout': True}
# The system may be busy from the call to check_mode so loop until
# it's done.
upgrade = check_install_in_progress(module, commands, opts)
if upgrade['invalid_command'] and 'force' in commands[1]:
# Not all platforms support the 'force' keyword. Check for this
# condition and re-try without the 'force' keyword if needed.
commands = build_install_cmd_set(issu, image, kick, 'install', False)
upgrade = check_install_in_progress(module, commands, opts)
upgrade['upgrade_cmd'] = commands
# Special case: If we encounter a server error at this stage
# it means the command was sent and the upgrade was started but
# we will need to use the impact data instead of the current install
# data.
if upgrade['server_error']:
upgrade['upgrade_succeeded'] = True
upgrade['use_impact_data'] = True
if upgrade['use_impact_data']:
if upgrade['upgrade_succeeded']:
upgrade = impact_data
upgrade['upgrade_succeeded'] = True
else:
upgrade = impact_data
upgrade['upgrade_succeeded'] = False
if not upgrade['upgrade_succeeded']:
upgrade['error'] = True
return upgrade
def main():
argument_spec = dict(
system_image_file=dict(required=True),
kickstart_image_file=dict(required=False),
issu=dict(choices=['required', 'desired', 'no', 'yes'], default='no'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
# Get system_image_file(sif), kickstart_image_file(kif) and
# issu settings from module params.
sif = module.params['system_image_file']
kif = module.params['kickstart_image_file']
issu = module.params['issu']
if re.search(r'(yes|required)', issu):
issu = 'yes'
if kif == 'null' or kif == '':
kif = None
install_result = do_install_all(module, issu, sif, kick=kif)
if install_result['error']:
cmd = install_result['upgrade_cmd']
msg = 'Failed to upgrade device using command: %s' % cmd
module.fail_json(msg=msg, raw_data=install_result['list_data'])
state = install_result['processed']
changed = install_result['upgrade_needed']
module.exit_json(changed=changed, install_state=state, warnings=warnings)
if __name__ == '__main__':
main()

@ -1,512 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_interface_ospf
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages configuration of an OSPF interface instance.
description:
- Manages configuration of an OSPF interface instance.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Default, where supported, restores params default value.
- To remove an existing authentication configuration you should use
C(message_digest_key_id=default) plus all other options matching their
existing values.
- Loopback interfaces only support ospf network type 'point-to-point'.
- C(state=absent) removes the whole OSPF interface configuration.
options:
interface:
description:
- Name of this cisco_interface resource. Valid value is a string.
required: true
ospf:
description:
- Name of the ospf instance.
required: true
area:
description:
- Ospf area associated with this cisco_interface_ospf instance.
Valid values are a string, formatted as an IP address
(i.e. "0.0.0.0") or as an integer.
required: true
bfd:
description:
- Enables bfd at interface level. This overrides the bfd variable set at the ospf router level.
- Valid values are 'enable', 'disable' or 'default'.
- "Dependency: 'feature bfd'"
version_added: "2.9"
type: str
choices: ['enable', 'disable', 'default']
cost:
description:
- The cost associated with this cisco_interface_ospf instance.
hello_interval:
description:
- Time between sending successive hello packets.
Valid values are an integer or the keyword 'default'.
dead_interval:
description:
- Time interval an ospf neighbor waits for a hello
packet before tearing down adjacencies. Valid values are an
integer or the keyword 'default'.
passive_interface:
description:
- Enable or disable passive-interface state on this interface.
true - (enable) Prevent OSPF from establishing an adjacency or
sending routing updates on this interface.
false - (disable) Override global 'passive-interface default' for this interface.
type: bool
network:
description:
- Specifies interface ospf network type. Valid values are 'point-to-point' or 'broadcast'.
choices: ['point-to-point', 'broadcast']
version_added: "2.8"
message_digest:
description:
- Enables or disables the usage of message digest authentication.
type: bool
message_digest_key_id:
description:
- Md5 authentication key-id associated with the ospf instance.
If this is present, message_digest_encryption_type,
message_digest_algorithm_type and message_digest_password are
mandatory. Valid value is an integer and 'default'.
message_digest_algorithm_type:
description:
- Algorithm used for authentication among neighboring routers
within an area. Valid values are 'md5' and 'default'.
choices: ['md5', 'default']
message_digest_encryption_type:
description:
- Specifies the scheme used for encrypting message_digest_password.
Valid values are '3des' or 'cisco_type_7' encryption or 'default'.
choices: ['cisco_type_7','3des', 'default']
message_digest_password:
description:
- Specifies the message_digest password. Valid value is a string.
state:
description:
- Determines whether the config should be present or not
on the device.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
- nxos_interface_ospf:
interface: ethernet1/32
ospf: 1
area: 1
bfd: disable
cost: default
- nxos_interface_ospf:
interface: loopback0
ospf: prod
area: 0.0.0.0
bfd: enable
network: point-to-point
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["interface Ethernet1/32", "ip router ospf 1 area 0.0.0.1", "ip ospf bfd disable"]
'''
import re
import struct
import socket
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
BOOL_PARAMS = [
'passive_interface',
'message_digest'
]
PARAM_TO_COMMAND_KEYMAP = {
'interface': '',
'cost': 'ip ospf cost',
'ospf': 'ip router ospf',
'area': 'ip router ospf',
'bfd': 'ip ospf bfd',
'hello_interval': 'ip ospf hello-interval',
'dead_interval': 'ip ospf dead-interval',
'passive_interface': 'ip ospf passive-interface',
'message_digest': 'ip ospf authentication message-digest',
'message_digest_key_id': 'ip ospf message-digest-key',
'message_digest_algorithm_type': 'ip ospf message-digest-key',
'message_digest_encryption_type': 'ip ospf message-digest-key',
'message_digest_password': 'ip ospf message-digest-key',
'network': 'ip ospf network',
}
def get_value(arg, config, module):
command = PARAM_TO_COMMAND_KEYMAP[arg]
has_command = re.search(r'\s+{0}\s*$'.format(command), config, re.M)
has_command_val = re.search(r'(?:{0}\s)(?P<value>.*)$'.format(command), config, re.M)
if command == 'ip router ospf':
value = ''
if has_command_val:
value_list = has_command_val.group('value').split()
if arg == 'ospf':
value = value_list[0]
elif arg == 'area':
value = value_list[2]
value = normalize_area(value, module)
elif command == 'ip ospf message-digest-key':
value = ''
if has_command_val:
value_list = has_command_val.group('value').split()
if arg == 'message_digest_key_id':
value = value_list[0]
elif arg == 'message_digest_algorithm_type':
value = value_list[1]
elif arg == 'message_digest_encryption_type':
value = value_list[2]
if value == '3':
value = '3des'
elif value == '7':
value = 'cisco_type_7'
elif arg == 'message_digest_password':
value = value_list[3]
elif arg == 'passive_interface':
has_no_command = re.search(r'\s+no\s+{0}\s*$'.format(command), config, re.M)
if has_no_command:
value = False
elif has_command:
value = True
else:
value = None
elif arg == 'bfd':
m = re.search(r'\s*ip ospf bfd(?P<disable> disable)?', config)
if m:
value = 'disable' if m.group('disable') else 'enable'
else:
value = 'default'
elif arg in BOOL_PARAMS:
value = bool(has_command)
else:
value = ''
if has_command_val:
value = has_command_val.group('value')
return value
def get_existing(module, args):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
if module.params['interface'].startswith('loopback') or module.params['interface'].startswith('port-channel'):
parents = ['interface {0}'.format(module.params['interface'])]
else:
parents = ['interface {0}'.format(module.params['interface'].capitalize())]
config = netcfg.get_section(parents)
if 'ospf' in config:
for arg in args:
if arg not in ['interface']:
existing[arg] = get_value(arg, config, module)
existing['interface'] = module.params['interface']
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key, value in table.items():
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = value
return new_dict
def get_default_commands(existing, proposed, existing_commands, key, module):
commands = list()
existing_value = existing_commands.get(key)
if key.startswith('ip ospf message-digest-key'):
check = False
for param in ['message_digest_encryption_type',
'message_digest_algorithm_type',
'message_digest_password']:
if existing[param] == proposed[param]:
check = True
if check:
if existing['message_digest_encryption_type'] == '3des':
encryption_type = '3'
elif existing['message_digest_encryption_type'] == 'cisco_type_7':
encryption_type = '7'
command = 'no {0} {1} {2} {3} {4}'.format(
key,
existing['message_digest_key_id'],
existing['message_digest_algorithm_type'],
encryption_type,
existing['message_digest_password'])
commands.append(command)
elif 'ip ospf bfd' in key:
commands.append('no {0}'.format(key))
elif 'passive-interface' in key:
commands.append('default ip ospf passive-interface')
else:
commands.append('no {0} {1}'.format(key, existing_value))
return commands
def get_custom_command(existing_cmd, proposed, key, module):
commands = list()
if key == 'ip router ospf':
command = '{0} {1} area {2}'.format(key, proposed['ospf'],
proposed['area'])
if command not in existing_cmd:
commands.append(command)
if key == 'ip ospf network':
command = '{0} {1}'.format(key, proposed['network'])
if command not in existing_cmd:
commands.append(command)
elif key.startswith('ip ospf message-digest-key'):
if (proposed['message_digest_key_id'] != 'default' and
'options' not in key):
if proposed['message_digest_encryption_type'] == '3des':
encryption_type = '3'
elif proposed['message_digest_encryption_type'] == 'cisco_type_7':
encryption_type = '7'
command = '{0} {1} {2} {3} {4}'.format(
key,
proposed['message_digest_key_id'],
proposed['message_digest_algorithm_type'],
encryption_type,
proposed['message_digest_password'])
commands.append(command)
return commands
def state_present(module, existing, proposed, candidate):
commands = list()
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if existing_commands.get(key):
if key == 'ip router ospf':
if proposed['area'] == existing['area']:
continue
if existing_commands[key] == proposed_commands[key]:
continue
if key == 'ip ospf passive-interface' and module.params.get('interface').upper().startswith('LO'):
module.fail_json(msg='loopback interface does not support passive_interface')
if key == 'ip ospf network' and value == 'broadcast' and module.params.get('interface').upper().startswith('LO'):
module.fail_json(msg='loopback interface does not support ospf network type broadcast')
if key == 'ip ospf bfd':
cmd = key
if 'disable' in value:
cmd += ' disable'
elif 'default' in value and existing.get('bfd') is not None:
cmd = 'no ' + cmd
commands.append(cmd)
continue
if value is True:
commands.append(key)
elif value is False:
commands.append('no {0}'.format(key))
elif value == 'default':
if existing_commands.get(key):
commands.extend(get_default_commands(existing, proposed,
existing_commands, key,
module))
else:
if (key == 'ip router ospf' or
key.startswith('ip ospf message-digest-key')):
commands.extend(get_custom_command(commands, proposed,
key, module))
else:
command = '{0} {1}'.format(key, value.lower())
commands.append(command)
if commands:
parents = ['interface {0}'.format(module.params['interface'].capitalize())]
candidate.add(commands, parents=parents)
def state_absent(module, existing, proposed, candidate):
commands = []
parents = ['interface {0}'.format(module.params['interface'].capitalize())]
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in existing_commands.items():
if 'ip ospf bfd' in key:
if 'default' not in value:
# cli is present when enabled or disabled; this removes either case
commands.append('no ip ospf bfd')
continue
if 'ip ospf passive-interface' in key and value is not None:
# cli is present for both enabled or disabled; 'no' will not remove
commands.append('default ip ospf passive-interface')
continue
if value:
if key.startswith('ip ospf message-digest-key'):
if 'options' not in key:
if existing['message_digest_encryption_type'] == '3des':
encryption_type = '3'
elif existing['message_digest_encryption_type'] == 'cisco_type_7':
encryption_type = '7'
command = 'no {0} {1} {2} {3} {4}'.format(
key,
existing['message_digest_key_id'],
existing['message_digest_algorithm_type'],
encryption_type,
existing['message_digest_password'])
commands.append(command)
elif key in ['ip ospf authentication message-digest', 'ip ospf network']:
if value:
commands.append('no {0}'.format(key))
elif key == 'ip router ospf':
command = 'no {0} {1} area {2}'.format(key, proposed['ospf'], proposed['area'])
if command not in commands:
commands.append(command)
else:
existing_value = existing_commands.get(key)
commands.append('no {0} {1}'.format(key, existing_value))
candidate.add(commands, parents=parents)
def normalize_area(area, module):
try:
area = int(area)
area = socket.inet_ntoa(struct.pack('!L', area))
except ValueError:
splitted_area = area.split('.')
if len(splitted_area) != 4:
module.fail_json(msg='Incorrect Area ID format', area=area)
return area
def main():
argument_spec = dict(
interface=dict(required=True, type='str'),
ospf=dict(required=True, type='str'),
area=dict(required=True, type='str'),
bfd=dict(choices=['enable', 'disable', 'default'], required=False, type='str'),
cost=dict(required=False, type='str'),
hello_interval=dict(required=False, type='str'),
dead_interval=dict(required=False, type='str'),
passive_interface=dict(required=False, type='bool'),
network=dict(required=False, type='str', choices=['broadcast', 'point-to-point']),
message_digest=dict(required=False, type='bool'),
message_digest_key_id=dict(required=False, type='str'),
message_digest_algorithm_type=dict(required=False, type='str', choices=['md5', 'default']),
message_digest_encryption_type=dict(required=False, type='str', choices=['cisco_type_7', '3des', 'default']),
message_digest_password=dict(required=False, type='str', no_log=True),
state=dict(choices=['present', 'absent'], default='present', required=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
required_together=[['message_digest_key_id',
'message_digest_algorithm_type',
'message_digest_encryption_type',
'message_digest_password']],
supports_check_mode=True)
# Normalize interface input data.
#
# * For port-channel and loopback interfaces expection is all lower case names.
# * All other interfaces the expectation is an uppercase leading character
# followed by lower case characters.
#
if re.match(r'(port-channel|loopback)', module.params['interface'], re.I):
module.params['interface'] = module.params['interface'].lower()
else:
module.params['interface'] = module.params['interface'].capitalize()
warnings = list()
result = {'changed': False, 'commands': [], 'warnings': warnings}
for param in ['message_digest_encryption_type',
'message_digest_algorithm_type',
'message_digest_password']:
if module.params[param] == 'default' and module.params['message_digest_key_id'] != 'default':
module.exit_json(msg='Use message_digest_key_id=default to remove an existing authentication configuration')
state = module.params['state']
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args)
proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
proposed = {}
for key, value in proposed_args.items():
if key != 'interface':
if str(value).lower() == 'true':
value = True
elif str(value).lower() == 'false':
value = False
elif str(value).lower() == 'default':
value = 'default'
elif key == 'bfd':
value = str(value).lower()
if existing.get(key) or (not existing.get(key) and value):
proposed[key] = value
elif 'passive_interface' in key and existing.get(key) is None and value is False:
proposed[key] = value
proposed['area'] = normalize_area(proposed['area'], module)
if 'hello_interval' in proposed and proposed['hello_interval'] == '10':
proposed['hello_interval'] = 'default'
candidate = CustomNetworkConfig(indent=3)
if state == 'present':
state_present(module, existing, proposed, candidate)
elif state == 'absent' and existing.get('ospf') == proposed['ospf'] and existing.get('area') == proposed['area']:
state_absent(module, existing, proposed, candidate)
if candidate:
candidate = candidate.items_text()
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,280 +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 nxos_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_interfaces
version_added: 2.9
short_description: 'Manages interface attributes of NX-OS Interfaces'
description: This module manages the interface attributes of NX-OS interfaces.
author: Trishna Guha (@trishnaguha)
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL
options:
config:
description: A dictionary of interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of interface, e.g. Ethernet1/1, port-channel10.
type: str
required: true
description:
description:
- Interface description.
type: str
enabled:
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:
- Interface link speed. Applicable for Ethernet interfaces only.
type: str
mode:
description:
- Manage Layer2 or Layer3 state of the interface.
Applicable for Ethernet and port channel interfaces only.
choices: ['layer2','layer3']
type: str
mtu:
description:
- MTU for a specific interface. Must be an even number between 576 and 9216.
Applicable for Ethernet interfaces only.
type: str
duplex:
description:
- Interface link status. Applicable for Ethernet interfaces only.
type: str
choices: ['full', 'half', 'auto']
ip_forward:
description:
- Enable or disable IP forward feature on SVIs.
Set the value to C(true) to enable or C(false) to disable.
type: bool
fabric_forwarding_anycast_gateway:
description:
- Associate SVI with anycast gateway under VLAN configuration mode.
Applicable for SVI interfaces only.
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:
# -------------
#
# interface Ethernet1/1
# description testing
# mtu 1800
- name: Merge provided configuration with device configuration
nxos_interfaces:
config:
- name: Ethernet1/1
description: 'Configured by Ansible'
enabled: True
- name: Ethernet1/2
description: 'Configured by Ansible Network'
enabled: False
state: merged
# After state:
# ------------
#
# interface Ethernet1/1
# description Configured by Ansible
# no shutdown
# mtu 1800
# interface Ethernet2
# description Configured by Ansible Network
# shutdown
# Using replaced
# Before state:
# -------------
#
# interface Ethernet1/1
# description Interface 1/1
# interface Ethernet1/2
- name: Replaces device configuration of listed interfaces with provided configuration
nxos_interfaces:
config:
- name: Ethernet1/1
description: 'Configured by Ansible'
enabled: True
mtu: 2000
- name: Ethernet1/2
description: 'Configured by Ansible Network'
enabled: False
mode: layer2
state: replaced
# After state:
# ------------
#
# interface Ethernet1/1
# description Configured by Ansible
# no shutdown
# mtu 1500
# interface Ethernet2/2
# description Configured by Ansible Network
# shutdown
# switchport
# Using overridden
# Before state:
# -------------
#
# interface Ethernet1/1
# description Interface Ethernet1/1
# interface Ethernet1/2
# interface mgmt0
# description Management interface
# ip address dhcp
- name: Override device configuration of all interfaces with provided configuration
nxos_interfaces:
config:
- name: Ethernet1/1
enabled: True
- name: Ethernet1/2
description: 'Configured by Ansible Network'
enabled: False
state: overridden
# After state:
# ------------
#
# interface Ethernet1/1
# interface Ethernet1/2
# description Configured by Ansible Network
# shutdown
# interface mgmt0
# ip address dhcp
# Using deleted
# Before state:
# -------------
#
# interface Ethernet1/1
# description Interface Ethernet1/1
# interface Ethernet1/2
# interface mgmt0
# description Management interface
# ip address dhcp
- name: Delete or return interface parameters to default settings
nxos_interfaces:
config:
- name: Ethernet1/1
state: deleted
# After state:
# ------------
#
# interface Ethernet1/1
# interface Ethernet1/2
# interface mgmt0
# description Management interface
# ip address dhcp
"""
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 Ethernet1/1', 'mtu 1800']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.interfaces.interfaces import InterfacesArgs
from ansible.module_utils.network.nxos.config.interfaces.interfaces import Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
supports_check_mode=True)
result = Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,284 +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 nxos_l2_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_l2_interfaces
version_added: 2.9
short_description: Manages Layer-2 Interfaces attributes of NX-OS Interfaces
description: This module manages Layer-2 interfaces attributes of NX-OS Interfaces.
author: Trishna Guha (@trishnaguha)
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL
options:
config:
description: A dictionary of Layer-2 interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of interface, i.e. Ethernet1/1.
type: str
required: true
access:
description:
- Switchport mode access command to configure the interface as a
Layer-2 access.
type: dict
suboptions:
vlan:
description:
- Configure given VLAN in access port. It's used as the access
VLAN ID.
type: int
trunk:
description:
- Switchport mode trunk command to configure the interface as a
Layer-2 trunk.
type: dict
suboptions:
native_vlan:
description:
- Native VLAN to be configured in trunk port. It is used as the
trunk native VLAN ID.
type: int
allowed_vlans:
description:
- List of allowed VLANs in a given trunk port. These are the only
VLANs that will be configured on the trunk.
type: str
mode:
description:
- Mode in which interface needs to be configured.
- Access mode is not shown in interface facts, so idempotency will not be
maintained for switchport mode access and every time the output will come
as changed=True.
version_added: '2.10'
type: str
choices: ['access', 'trunk']
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
#
# interface Ethernet1/1
# switchport access vlan 20
# interface Ethernet1/2
# switchport trunk native vlan 20
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
- name: Merge provided configuration with device configuration.
nxos_l2_interfaces:
config:
- name: Ethernet1/1
trunk:
native_vlan: 10
allowed_vlans: 2,4,15
- name: Ethernet1/2
access:
vlan: 30
state: merged
# After state:
# ------------
#
# interface Ethernet1/1
# switchport trunk native vlan 10
# switchport trunk allowed vlans 2,4,15
# interface Ethernet1/2
# switchport access vlan 30
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
# Using replaced
# Before state:
# -------------
#
# interface Ethernet1/1
# switchport access vlan 20
# interface Ethernet1/2
# switchport trunk native vlan 20
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
- name: Replace device configuration of specified L2 interfaces with provided configuration.
nxos_l2_interfaces:
config:
- name: Ethernet1/1
trunk:
native_vlan: 20
trunk_vlans: 5-10, 15
state: replaced
# After state:
# ------------
#
# interface Ethernet1/1
# switchport trunk native vlan 20
# switchport trunk allowed vlan 5-10,15
# interface Ethernet1/2
# switchport trunk native vlan 20
# switchport mode trunk
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
# Using overridden
# Before state:
# -------------
#
# interface Ethernet1/1
# switchport access vlan 20
# interface Ethernet1/2
# switchport trunk native vlan 20
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
- name: Override device configuration of all L2 interfaces on device with provided configuration.
nxos_l2_interfaces:
config:
- name: Ethernet1/2
access:
vlan: 30
state: overridden
# After state:
# ------------
#
# interface Ethernet1/1
# interface Ethernet1/2
# switchport access vlan 30
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
# Using deleted
# Before state:
# -------------
#
# interface Ethernet1/1
# switchport access vlan 20
# interface Ethernet1/2
# switchport trunk native vlan 20
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
- name: Delete L2 attributes of given interfaces (Note This won't delete the interface itself).
nxos_l2_interfaces:
config:
- name: Ethernet1/1
- name: Ethernet1/2
state: deleted
# After state:
# ------------
#
# interface Ethernet1/1
# interface Ethernet1/2
# interface mgmt0
# ip address dhcp
# ipv6 address auto-config
"""
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: ['command 1', 'command 2', 'command 3']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import L2_interfacesArgs
from ansible.module_utils.network.nxos.config.l2_interfaces.l2_interfaces import L2_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=L2_interfacesArgs.argument_spec,
supports_check_mode=True)
result = L2_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,272 +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 nxos_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: nxos_l3_interfaces
version_added: 2.9
short_description: Manages Layer-3 Interfaces attributes of NX-OS Interfaces
description: This module manages Layer-3 interfaces attributes of NX-OS Interfaces.
author: Trishna Guha (@trishnaguha)
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL
options:
config:
description: A dictionary of Layer-3 interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of L3 interface, i.e. Ethernet1/1.
type: str
required: true
dot1q:
description:
- Configures IEEE 802.1Q VLAN encapsulation on a subinterface.
type: int
version_added: 2.10
ipv4:
description:
- IPv4 address and attributes of the L3 interface.
type: list
elements: dict
suboptions:
address:
description:
- IPV4 address of the L3 interface.
type: str
tag:
description:
- URIB route tag value for local/direct routes.
type: int
secondary:
description:
- A boolean attribute to manage addition of secondary IP address.
type: bool
default: False
ipv6:
description:
- IPv6 address and attributes of the L3 interface.
type: list
elements: dict
suboptions:
address:
description:
- IPV6 address of the L3 interface.
type: str
tag:
description:
- URIB route tag value for local/direct routes.
type: int
redirects:
description:
- Enables/disables ip redirects
type: bool
version_added: 2.10
unreachables:
description:
- Enables/disables ip redirects
type: bool
version_added: 2.10
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
#
# interface Ethernet1/6
- name: Merge provided configuration with device configuration.
nxos_l3_interfaces:
config:
- name: Ethernet1/6
ipv4:
- address: 192.168.1.1/24
tag: 5
- address: 10.1.1.1/24
secondary: True
tag: 10
ipv6:
- address: fd5d:12c9:2201:2::1/64
tag: 6
- name: Ethernet1/7.42
dot1q: 42
redirects: False
unreachables: False
state: merged
# After state:
# ------------
#
# interface Ethernet1/6
# ip address 192.168.22.1/24 tag 5
# ip address 10.1.1.1/24 secondary tag 10
# interface Ethernet1/6
# ipv6 address fd5d:12c9:2201:2::1/64 tag 6
# interface Ethernet1/7.42
# encapsulation dot1q 42
# no ip redirects
# no ip unreachables
# Using replaced
# Before state:
# -------------
#
# interface Ethernet1/6
# ip address 192.168.22.1/24
# ipv6 address "fd5d:12c9:2201:1::1/64"
- name: Replace device configuration of specified L3 interfaces with provided configuration.
nxos_l3_interfaces:
config:
- name: Ethernet1/6
ipv4: 192.168.22.3/24
state: replaced
# After state:
# ------------
#
# interface Ethernet1/6
# ip address 192.168.22.3/24
# Using overridden
# Before state:
# -------------
#
# interface Ethernet1/2
# ip address 192.168.22.1/24
# interface Ethernet1/6
# ipv6 address "fd5d:12c9:2201:1::1/64"
- name: Override device configuration of all L3 interfaces on device with provided configuration.
nxos_l3_interfaces:
config:
- name: Ethernet1/2
ipv4: 192.168.22.3/4
state: overridden
# After state:
# ------------
#
# interface Ethernet1/2
# ipv4 address 192.168.22.3/24
# interface Ethernet1/6
# Using deleted
# Before state:
# -------------
#
# interface Ethernet1/6
# ip address 192.168.22.1/24
# interface Ethernet1/2
# ipv6 address "fd5d:12c9:2201:1::1/64"
- name: Delete L3 attributes of given interfaces (This won't delete the interface itself).
nxos_l3_interfaces:
config:
- name: Ethernet1/6
- name: Ethernet1/2
state: deleted
# After state:
# ------------
#
# interface Ethernet1/6
# interface Ethernet1/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 Ethernet1/2', 'ip address 192.168.0.1/2']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs
from ansible.module_utils.network.nxos.config.l3_interfaces.l3_interfaces import L3_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=L3_interfacesArgs.argument_spec,
supports_check_mode=True)
result = L3_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,188 +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 nxos_lacp
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: nxos_lacp
version_added: 2.9
short_description: Manage Global Link Aggregation Control Protocol (LACP) on Cisco NX-OS devices.
description: This module manages Global Link Aggregation Control Protocol (LACP) on NX-OS devices.
author: Trishna Guha (@trishnaguha)
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL.
- Feature lacp should be enabled for this module.
options:
config:
description: LACP global options.
type: dict
suboptions:
system:
description:
- LACP system options
type: dict
suboptions:
priority:
description:
- The system priority to use in LACP negotiations.
type: int
mac:
description:
- MAC address to be used for the LACP Protocol exchanges
type: dict
suboptions:
address:
description:
- MAC-address (FORMAT :xxxx.xxxx.xxxx).
type: str
role:
description:
- The role for the Switch.
type: str
choices: ['primary', 'secondary']
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
#
- name: Merge provided configuration with device configuration.
nxos_lacp:
config:
system:
priority: 10
mac:
address: 00c1.4c00.bd15
state: merged
# After state:
# ------------
#
# lacp system-priority 10
# lacp system-mac 00c1.4c00.bd15
# Using replaced
# Before state:
# -------------
#
# lacp system-priority 10
- name: Replace device global lacp configuration with the given configuration.
nxos_lacp:
config:
system:
mac:
address: 00c1.4c00.bd15
state: replaced
# After state:
# ------------
#
# lacp system-mac 00c1.4c00.bd15
# Using deleted
# Before state:
# -------------
#
# lacp system-priority 10
- name: Delete global LACP configurations.
nxos_lacp:
state: deleted
# After state:
# ------------
#
"""
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 15', 'lacp system-mac 00c1.4c00.bd15 role primary']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.lacp.lacp import LacpArgs
from ansible.module_utils.network.nxos.config.lacp.lacp import Lacp
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=LacpArgs.argument_spec,
supports_check_mode=True)
result = Lacp(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,258 +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 nxos_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: nxos_lacp_interfaces
version_added: 2.9
short_description: Manage Link Aggregation Control Protocol (LACP) attributes of interfaces on Cisco NX-OS devices.
description: This module manages Link Aggregation Control Protocol (LACP) attributes of NX-OS Interfaces.
author: Trishna Guha (@trishnaguha)
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL
options:
config:
description: A dictionary of LACP interfaces options.
type: list
elements: dict
suboptions:
name:
description:
- Name of the interface.
required: true
type: str
port_priority:
description:
- LACP port priority for the interface. Range 1-65535.
Applicable only for Ethernet.
type: int
rate:
description:
- Rate at which PDUs are sent by LACP. Applicable only for Ethernet.
At fast rate LACP is transmitted once every 1 second.
At normal rate LACP is transmitted every 30 seconds after the link is bundled.
type: str
choices: ['fast', 'normal']
links:
description:
- This dict contains configurable options related to max and min port-channel links.
Applicable only for Port-channel.
type: dict
suboptions:
max:
description:
- Port-channel max bundle.
type: int
min:
description:
- Port-channel min links.
type: int
mode:
description:
- LACP mode. Applicable only for Port-channel.
type: str
choices: ['delay']
suspend_individual:
description:
- port-channel lacp state. Disabling this will cause lacp to put the
port to individual state and not suspend the port in case it does not get
LACP BPDU from the peer ports in the port-channel.
type: bool
convergence:
description:
- This dict contains configurable options related to convergence.
Applicable only for Port-channel.
type: dict
suboptions:
graceful:
description:
- port-channel lacp graceful convergence. Disable this only with lacp ports
connected to Non-Nexus peer. Disabling this with Nexus peer can lead
to port suspension.
type: bool
vpc:
description:
- Enable lacp convergence for vPC port channels.
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:
# -------------
#
- name: Merge provided configuration with device configuration.
nxos_lacp_interfaces:
config:
- name: Ethernet1/3
port_priority: 5
rate: fast
state: merged
# After state:
# ------------
#
# interface Ethernet1/3
# lacp port-priority 5
# lacp rate fast
# Using replaced
# Before state:
# -------------
#
# interface Ethernet1/3
# lacp port-priority 5
# interface port-channel11
# lacp mode delay
- name: Replace device lacp interfaces configuration with the given configuration.
nxos_lacp_interfaces:
config:
- name: port-channel11
links:
min: 4
state: replaced
# After state:
# ------------
#
# interface Ethernet1/3
# lacp port-priority 5
# interface port-channel11
# lacp min-links 4
# Using overridden
# Before state:
# -------------
#
# interface Ethernet1/3
# lacp port-priority 5
# interface port-channel11
# lacp mode delay
- name: Override device configuration of all LACP interfaces attributes of given interfaces on device with provided configuration.
nxos_lacp_interfaces:
config:
- name: port-channel11
links:
min: 4
state: overridden
# After state:
# ------------
#
# interface port-channel11
# lacp min-links 4
# Using deleted
# Before state:
# -------------
#
# interface Ethernet1/3
# lacp port-priority 5
# interface port-channel11
# lacp mode delay
- name: Delete LACP interfaces configurations.
nxos_lacp_interfaces:
state: deleted
# After state:
# ------------
#
"""
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 port-channel10', 'lacp min-links 5', 'lacp mode delay']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.lacp_interfaces.lacp_interfaces import Lacp_interfacesArgs
from ansible.module_utils.network.nxos.config.lacp_interfaces.lacp_interfaces import Lacp_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Lacp_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lacp_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,231 +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 nxos_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: nxos_lag_interfaces
version_added: 2.9
short_description: Manages link aggregation groups of NX-OS Interfaces
description: This module manages attributes of link aggregation groups of NX-OS Interfaces.
author: Trishna Guha (@trishnaguha)
options:
config:
description: A list of link aggregation group configurations.
type: list
suboptions:
name:
description:
- Name of the link aggregation group (LAG).
type: str
required: true
members:
description:
- The list of interfaces that are part of the group.
type: list
suboptions:
member:
description:
- The interface name.
type: str
mode:
description:
- Link aggregation group (LAG).
type: str
choices:
- active
- on
- passive
force:
description:
- When true it forces link aggregation group members to match what
is declared in the members param. This can be used to remove members.
type: bool
state:
description:
- The state of the configuration after module completion.
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL.
- This module works with connection C(network_cli).
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
#
# interface Ethernet1/4
- name: Merge provided configuration with device configuration.
nxos_lag_interfaces:
config:
- name: port-channel99
members:
- member: Ethernet1/4
state: merged
# After state:
# ------------
#
# interface Ethernet1/4
# channel-group 99
# Using replaced
# Before state:
# -------------
#
# interface Ethernet1/4
# channel-group 99 mode active
- name: Replace device configuration of specified LAG attributes of given interfaces with provided configuration.
nxos_lag_interfaces:
config:
- name: port-channel10
members:
- member: Ethernet1/4
state: replaced
# After state:
# ------------
#
# interface Ethernet1/4
# channel-group 10
# Using overridden
# Before state:
# -------------
#
# interface Ethernet1/4
# channel-group 10
# interface Ethernet1/2
# channel-group 99 mode passive
- name: Override device configuration of all LAG attributes of given interfaces on device with provided configuration.
nxos_lag_interfaces:
config:
- name: port-channel20
members:
- member: Ethernet1/6
force: True
state: overridden
# After state:
# ------------
# interface Ethernet1/2
# interface Ethernet1/4
# interface Ethernet1/6
# channel-group 20 force
# Using deleted
# Before state:
# -------------
#
# interface Ethernet1/4
# channel-group 99 mode active
- name: Delete LAG attributes of given interface (This won't delete the port-channel itself).
nxos_lag_interfaces:
config:
- port-channel: port-channel99
state: deleted
- name: Delete LAG attributes of all the interfaces
nxos_lag_interfaces:
state: deleted
# After state:
# ------------
#
# interface Ethernet1/4
# no channel-group 99
"""
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: ['command 1', 'command 2', 'command 3']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
from ansible.module_utils.network.nxos.config.lag_interfaces.lag_interfaces import Lag_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lag_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,114 +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: nxos_lldp
version_added: "2.5"
author: "Ganesh Nalawade (@ganeshrn)"
short_description: Manage LLDP configuration on Cisco NXOS network devices.
description:
- This module provides declarative management of LLDP service
on Cisco NXOS network devices.
notes:
- Tested against NXOSv 7.0(3)I5(1).
options:
state:
description:
- State of the LLDP configuration. If value is I(present) lldp will be enabled
else if it is I(absent) it will be disabled.
default: present
choices: ['present', 'absent']
extends_documentation_fragment: nxos
"""
EXAMPLES = """
- name: Enable LLDP service
nxos_lldp:
state: present
- name: Disable LLDP service
nxos_lldp:
state: absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always, except for the platforms that use Netconf transport to manage the device.
type: list
sample:
- feature lldp
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
def has_lldp(module):
output = get_config(module, ['| section lldp'])
is_lldp_enable = False
if output and "feature lldp" in output:
is_lldp_enable = True
return is_lldp_enable
def main():
""" main entry point for module execution
"""
argument_spec = dict(
state=dict(default='present',
choices=['present', 'absent',
'enabled', 'disabled'])
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
HAS_LLDP = has_lldp(module)
commands = []
if module.params['state'] == 'absent' and HAS_LLDP:
commands.append('no feature lldp')
elif module.params['state'] == 'present' and not HAS_LLDP:
commands.append('feature lldp')
result['commands'] = commands
if commands:
# On N35 A8 images, some features return a yes/no prompt
# on enablement or disablement. Bypass using terminal dont-ask
commands.insert(0, 'terminal dont-ask')
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,250 +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 nxos_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: nxos_lldp_global
version_added: 2.9
short_description: Configure and manage Link Layer Discovery Protocol(LLDP) attributes on NX-OS platforms.
description: This module configures and manages the Link Layer Discovery Protocol(LLDP) attributes on NX-OS platforms.
author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
notes:
- Tested against NxOS 7.3.(0)D1(1) on VIRL
- The LLDP feature needs to be enabled before using this module
options:
config:
description:
- A list of link layer discovery configurations
type: dict
suboptions:
holdtime:
description:
- Amount of time the receiving device should hold the information (in seconds)
type: int
port_id:
description:
- This attribute defines if the interface names should be advertised in the long(0) or short(1) form.
type: int
choices: [0, 1]
reinit:
description:
- Amount of time to delay the initialization of LLDP on any interface (in seconds)
type: int
timer:
description:
- Frequency at which LLDP updates need to be transmitted (in seconds)
type: int
tlv_select:
description:
- This attribute can be used to specify the TLVs that need to be sent and received in the LLDP packets. By default, all TLVs are advertised
type: dict
suboptions:
dcbxp:
description:
- Used to specify the Data Center Bridging Exchange Protocol TLV
type: bool
management_address:
description:
- Used to specify the management address in TLV messages
type: dict
suboptions:
v4:
description: Management address with TLV v4
type: bool
v6:
description: Management address with TLV v6
type: bool
port:
description:
- Used to manage port based attributes in TLV messages
type: dict
suboptions:
description:
description:
- Used to specify the port description TLV
type: bool
vlan:
description:
- Used to specify the port VLAN ID TLV
type: bool
power_management:
description:
- Used to specify IEEE 802.3 DTE Power via MDI TLV
type: bool
system:
description:
- Used to manage system based attributes in TLV messages
type: dict
suboptions:
capabilities:
description:
- Used to specify the system capabilities TLV
type: bool
description:
description:
- Used to specify the system description TLV
type: bool
name:
description:
- Used to specify the 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:
# -------------
#
# user(config)# show running-config | include lldp
# feature lldp
- name: Merge provided configuration with device configuration
nxos_lldp_global:
config:
timer: 35
holdtime: 100
state: merged
# After state:
# ------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp timer 35
# lldp holdtime 100
# Using replaced
# Before state:
# -------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp holdtime 100
# lldp reinit 5
# lldp timer 35
- name: Replace device configuration of specific LLDP attributes with provided configuration
nxos_lldp_global:
config:
timer: 40
tlv_select:
system:
description: true
name: false
management_address:
v4: true
state: replaced
# After state:
# ------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp timer 40
# no lldp tlv-select system-name
# Using deleted
# Before state:
# -------------
#
# user(config)# show running-config | include lldp
# feature lldp
# lldp holdtime 5
# lldp reinit 3
- name: Delete LLDP configuration (this will by default remove all lldp configuration)
nxos_lldp_global:
state: deleted
# After state:
# ------------
#
# user(config)# show running-config | include lldp
# feature 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 holdtime 125', 'lldp reinit 4', 'no lldp tlv-select system-name']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.lldp_global.lldp_global import Lldp_globalArgs
from ansible.module_utils.network.nxos.config.lldp_global.lldp_global import Lldp_global
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec,
supports_check_mode=True)
result = Lldp_global(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,250 +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 nxos_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: nxos_lldp_interfaces
version_added: "2.10"
short_description: Manages interfaces' configuration for Link Layer Discovery Protocol (LLDP) on NX-OS platforms.
description: This module manages interfaces' configuration for Link Layer Discovery Protocol (LLDP) on NX-OS platforms.
author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
notes:
- Tested against NXOS 7.3.(0)D1(1) on VIRL
- The LLDP feature needs to be enabled before using this module
options:
running_config:
description:
- Used to parse given commands into structured format, only in parsed state
type: str
config:
description:
- A list of link layer discovery configurations for interfaces.
type: list
elements: dict
suboptions:
name:
description:
- Name of the interface
required: true
type: str
receive:
description:
- Used to enable or disable the reception of LLDP packets on that interface. By default, this is enabled after LLDP is enabled globally.
type: bool
transmit:
description:
- Used to enable or disable the transmission of LLDP packets on that interface. By default, this is enabled after LLDP is enabled globally.
type: bool
tlv_set:
description:
- Used to configure TLV parameters on the interface
type: dict
suboptions:
management_address:
description:
- Used to mention the IPv4 or IPv6 management address for the interface
type: str
vlan:
description:
- Used to mention the VLAN for the interface
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:
# -------------
#
- name : Merge provided configuration with device configuration
nxos_lldp_interfaces:
config:
- name : Ethernet1/4
receive: false
transmit: true
tlv_set:
management_address: 192.168.122.64
vlan: 12
state: merged
# After state:
# -------------
#
# interface Ethernet1/4
# no lldp receive
# lldp tlv-set management-address 192.168.122.64
# lldp tlv-set vlan 12
# Using replaced
# Before state:
# ------------
#
# interface Ethernet1/4
# no lldp receive
# lldp tlv-set management-address 192.168.122.64
# interface Ethernet1/5
# no lldp transmit
# lldp tlv-set vlan 10
- name: Replace LLDP configuration on interfaces with given configuration
nxos_lldp_interfaces:
config:
- name: Ethernet1/4
transmit: no
tlv_set:
vlan: 2
state: replaced
# After state:
# -----------
#
# interface Ethernet1/4
# no lldp transmit
# lldp tlv_set vlan 2
# interface Ethernet1/5
# no lldp transmit
# lldp tlv-set vlan 10
# Using overridden
# Before state:
# ------------
#
# interface Ethernet1/4
# no lldp receive
# lldp tlv-set management-address 192.168.122.64
# interface Ethernet1/5
# no lldp transmit
# lldp tlv-set vlan 10
- name: Override LLDP configuration on all interfaces with given configuration
nxos_lldp_interfaces:
config:
- name: Ethernet1/7
receive: no
tlv_set:
vlan: 12
state: overridden
# After state:
# -----------
#
# interface Ethernet1/7
# no lldp receive
# lldp tlv_set vlan 12
# Using deleted
# Before state:
# ------------
#
# interface Ethernet1/4
# lldp tlv-set management vlan 24
# no lldp transmit
# interface mgmt0
# no lldp receive
- name: Delete LLDP interfaces configuration
nxos_lldp_interfaces:
state: deleted
# After state:
# ------------
#
"""
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 Ethernet1/2', 'lldp receive', 'lldp tlv-set vlan 12']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.argspec.lldp_interfaces.lldp_interfaces import Lldp_interfacesArgs
from ansible.module_utils.network.nxos.config.lldp_interfaces.lldp_interfaces import Lldp_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(argument_spec=Lldp_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Lldp_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,795 +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)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_logging
version_added: "2.4"
author: "Trishna Guha (@trishnaguha)"
short_description: Manage logging on network devices
description:
- This module provides declarative management of logging
on Cisco NX-OS devices.
options:
dest:
description:
- Destination of the logs.
choices: ['console', 'logfile', 'module', 'monitor', 'server']
remote_server:
description:
- Hostname or IP Address for remote logging (when dest is 'server').
version_added: '2.7'
use_vrf:
description:
- VRF to be used while configuring remote logging (when dest is 'server').
version_added: '2.7'
interface:
description:
- Interface to be used while configuring source-interface for logging (e.g., 'Ethernet1/2', 'mgmt0')
version_added: '2.7'
name:
description:
- If value of C(dest) is I(logfile) it indicates file-name.
facility:
description:
- Facility name for logging.
dest_level:
description:
- Set logging severity levels.
aliases: ['level']
facility_level:
description:
- Set logging severity levels for facility based log messages.
aggregate:
description: List of logging definitions.
state:
description:
- State of the logging configuration.
default: present
choices: ['present', 'absent']
event:
description:
- Link/trunk enable/default interface configuration logging
choices: ['link-enable', 'link-default', 'trunk-enable', 'trunk-default']
version_added: '2.8'
interface_message:
description:
- Add interface description to interface syslogs.
Does not work with version 6.0 images using nxapi as a transport.
choices: ['add-interface-description']
version_added: '2.8'
file_size:
description:
- Set logfile size
version_added: '2.8'
facility_link_status:
description:
- Set logging facility ethpm link status.
Not idempotent with version 6.0 images.
choices: ['link-down-notif', 'link-down-error', 'link-up-notif', 'link-up-error']
version_added: '2.8'
timestamp:
description:
- Set logging timestamp format
choices: ['microseconds', 'milliseconds', 'seconds']
version_added: '2.8'
purge:
description:
- Remove any switch logging configuration that does not match what has been configured
Not supported for ansible_connection local.
All nxos_logging tasks must use the same ansible_connection type.
type: bool
default: no
version_added: '2.8'
extends_documentation_fragment: nxos
"""
EXAMPLES = """
- name: configure console logging with level
nxos_logging:
dest: console
level: 2
state: present
- name: remove console logging configuration
nxos_logging:
dest: console
level: 2
state: absent
- name: configure file logging with level
nxos_logging:
dest: logfile
name: testfile
dest_level: 3
state: present
- name: Configure logging logfile with size
nxos_logging:
dest: logfile
name: testfile
dest_level: 3
file_size: 16384
- name: configure facility level logging
nxos_logging:
facility: daemon
facility_level: 0
state: present
- name: remove facility level logging
nxos_logging:
facility: daemon
facility_level: 0
state: absent
- name: Configure Remote Logging
nxos_logging:
dest: server
remote_server: test-syslogserver.com
facility: auth
facility_level: 1
use_vrf: management
state: present
- name: Configure Source Interface for Logging
nxos_logging:
interface: mgmt0
state: present
- name: Purge nxos_logging configuration not managed by this playbook
nxos_logging:
purge: true
- name: Configure logging timestamp
nxos_logging:
timestamp: milliseconds
state: present
- name: Configure logging facility ethpm link status
nxos_logging:
facility: ethpm
facility_link_status: link-up-notif
state: present
- name: Configure logging message ethernet description
nxos_logging:
interface_message: add-interface-description
state: present
- name: Configure logging event link enable
nxos_logging:
event: link-enable
state: present
- name: Configure logging using aggregate
nxos_logging:
aggregate:
- { dest: console, dest_level: 2 }
- { dest: logfile, dest_level: 2, name: testfile }
- { facility: daemon, facility_level: 0 }
state: present
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- logging console 2
- logging logfile testfile 3
- logging level daemon 0
"""
import re
import copy
from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands, save_module_context, read_module_context
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, normalize_interface
from ansible.module_utils.basic import AnsibleModule
STATIC_CLI = {'link-enable': 'logging event link-status enable',
'link-default': 'logging event link-status default',
'trunk-enable': 'logging event trunk-status enable',
'trunk-default': 'logging event trunk-status default',
'microseconds': 'logging timestamp microseconds',
'milliseconds': 'logging timestamp milliseconds',
'seconds': 'logging timestamp seconds',
'link-up-error': 'link-up error',
'link-up-notif': 'link-up notif',
'link-down-error': 'link-down error',
'link-down-notif': 'link-down notif',
'add-interface-description': 'logging message interface type ethernet description'}
DEFAULT_LOGGING_LEVEL = {0: [],
1: [],
2: ['pktmgr'],
3: ['adjmgr', 'arp', 'icmpv6', 'l2rib', 'netstack'],
4: [],
5: ['mrib', 'm6rib'],
6: [],
7: []}
DEST_GROUP = ['console', 'logfile', 'module', 'monitor', 'server']
def map_obj_to_commands(module, updates):
commands = list()
want, have = updates
for w in want:
state = w['state']
del w['state']
if state == 'absent' and w in have:
if w['facility'] is not None:
if not w['dest'] and not w['facility_link_status'] and w['facility'] not in DEFAULT_LOGGING_LEVEL[int(w['facility_level'])]:
commands.append('no logging level {0} {1}'.format(w['facility'], w['facility_level']))
if w['facility_link_status'] and w['facility'] in ('ethpm'):
commands.append('no logging level {0} {1}'.format(w['facility'], STATIC_CLI[w['facility_link_status']]))
if w['name'] is not None:
commands.append('no logging logfile')
if w['dest'] in ('console', 'module', 'monitor'):
commands.append('no logging {0}'.format(w['dest']))
if w['dest'] == 'server':
commands.append('no logging server {0}'.format(w['remote_server']))
if w['interface']:
commands.append('no logging source-interface')
if w['event'] and w['event'] in STATIC_CLI:
commands.append('no ' + STATIC_CLI[w['event']])
if w['message'] and w['message'] in STATIC_CLI:
commands.append('no ' + STATIC_CLI[w['message']])
if w['timestamp'] and w['timestamp'] in STATIC_CLI:
commands.append('no ' + STATIC_CLI[w['timestamp']])
if state == 'present' and w not in have:
if w['facility'] is None:
if w['dest']:
if w['dest'] not in ('logfile', 'server'):
commands.append('logging {0} {1}'.format(w['dest'], w['dest_level']))
elif w['dest'] == 'logfile':
if w['file_size']:
commands.append('logging logfile {0} {1} size {2}'.format(
w['name'], w['dest_level'], w['file_size']))
else:
commands.append('logging logfile {0} {1}'.format(
w['name'], w['dest_level']))
elif w['dest'] == 'server':
if w['facility_level']:
if w['use_vrf']:
commands.append('logging server {0} {1} use-vrf {2}'.format(
w['remote_server'], w['facility_level'], w['use_vrf']))
else:
commands.append('logging server {0} {1}'.format(
w['remote_server'], w['facility_level']))
else:
if w['use_vrf']:
commands.append('logging server {0} use-vrf {1}'.format(
w['remote_server'], w['use_vrf']))
else:
commands.append('logging server {0}'.format(w['remote_server']))
if w['facility']:
if w['dest'] == 'server':
if w['facility_level']:
if w['use_vrf']:
commands.append('logging server {0} {1} facility {2} use-vrf {3}'.format(
w['remote_server'], w['facility_level'], w['facility'], w['use_vrf']))
else:
commands.append('logging server {0} {1} facility {2}'.format(
w['remote_server'], w['facility_level'], w['facility']))
else:
if w['use_vrf']:
commands.append('logging server {0} facility {1} use-vrf {2}'.format(
w['remote_server'], w['facility'], w['use_vrf']))
else:
commands.append('logging server {0} facility {1}'.format(w['remote_server'],
w['facility']))
else:
if w['facility_link_status']:
commands.append('logging level {0} {1}'.format(
w['facility'], STATIC_CLI[w['facility_link_status']]))
else:
if not match_facility_default(module, w['facility'], w['facility_level']):
commands.append('logging level {0} {1}'.format(w['facility'],
w['facility_level']))
if w['interface']:
commands.append('logging source-interface {0} {1}'.format(*split_interface(w['interface'])))
if w['event'] and w['event'] in STATIC_CLI:
commands.append(STATIC_CLI[w['event']])
if w['message'] and w['message'] in STATIC_CLI:
commands.append(STATIC_CLI[w['message']])
if w['timestamp'] and w['timestamp'] in STATIC_CLI:
commands.append(STATIC_CLI[w['timestamp']])
return commands
def match_facility_default(module, facility, want_level):
''' Check wanted facility to see if it matches current device default '''
matches_default = False
# Sample output from show logging level command
# Facility Default Severity Current Session Severity
# -------- ---------------- ------------------------
# bfd 5 5
#
# 0(emergencies) 1(alerts) 2(critical)
# 3(errors) 4(warnings) 5(notifications)
# 6(information) 7(debugging)
regexl = r'\S+\s+(\d+)\s+(\d+)'
cmd = {'command': 'show logging level {0}'.format(facility), 'output': 'text'}
facility_data = run_commands(module, cmd)
for line in facility_data[0].split('\n'):
mo = re.search(regexl, line)
if mo and int(mo.group(1)) == int(want_level) and int(mo.group(2)) == int(want_level):
matches_default = True
return matches_default
def split_interface(interface):
match = re.search(r'(\D+)(\S*)', interface, re.M)
if match:
return match.group(1), match.group(2)
def parse_facility_link_status(line, facility, status):
facility_link_status = None
if facility is not None:
match = re.search(r'logging level {0} {1} (\S+)'.format(facility, status), line, re.M)
if match:
facility_link_status = status + "-" + match.group(1)
return facility_link_status
def parse_event_status(line, event):
status = None
match = re.search(r'logging event {0} (\S+)'.format(event + '-status'), line, re.M)
if match:
state = match.group(1)
if state:
status = state
return status
def parse_event(line):
event = None
match = re.search(r'logging event (\S+)', line, re.M)
if match:
state = match.group(1)
if state == 'link-status':
event = 'link'
elif state == 'trunk-status':
event = 'trunk'
return event
def parse_message(line):
message = None
match = re.search(r'logging message interface type ethernet description', line, re.M)
if match:
message = 'add-interface-description'
return message
def parse_file_size(line, name, level):
file_size = None
match = re.search(r'logging logfile {0} {1} size (\S+)'.format(name, level), line, re.M)
if match:
file_size = match.group(1)
if file_size == '8192':
file_size = None
return file_size
def parse_timestamp(line):
timestamp = None
match = re.search(r'logging timestamp (\S+)', line, re.M)
if match:
timestamp = match.group(1)
return timestamp
def parse_name(line, dest):
name = None
if dest is not None:
if dest == 'logfile':
match = re.search(r'logging logfile (\S+)', line, re.M)
if match:
name = match.group(1)
else:
pass
return name
def parse_remote_server(line, dest):
remote_server = None
if dest and dest == 'server':
match = re.search(r'logging server (\S+)', line, re.M)
if match:
remote_server = match.group(1)
return remote_server
def parse_dest_level(line, dest, name):
dest_level = None
def parse_match(match):
level = None
if match:
if int(match.group(1)) in range(0, 8):
level = match.group(1)
else:
pass
return level
if dest and dest != 'server':
if dest == 'logfile':
match = re.search(r'logging logfile {0} (\S+)'.format(name), line, re.M)
if match:
dest_level = parse_match(match)
elif dest == 'server':
match = re.search(r'logging server (?:\S+) (\d+)', line, re.M)
if match:
dest_level = parse_match(match)
else:
match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M)
if match:
dest_level = parse_match(match)
return dest_level
def parse_facility_level(line, facility, dest):
facility_level = None
if dest == 'server':
match = re.search(r'logging server (?:\S+) (\d+)', line, re.M)
if match:
facility_level = match.group(1)
elif facility is not None:
match = re.search(r'logging level {0} (\S+)'.format(facility), line, re.M)
if match:
facility_level = match.group(1)
return facility_level
def parse_facility(line):
facility = None
match = re.search(r'logging server (?:\S+) (?:\d+) (?:\S+) (?:\S+) (?:\S+) (\S+)', line, re.M)
if match:
facility = match.group(1)
return facility
def parse_use_vrf(line, dest):
use_vrf = None
if dest and dest == 'server':
match = re.search(r'logging server (?:\S+) (?:\d+) use-vrf (\S+)', line, re.M)
if match:
use_vrf = match.group(1)
return use_vrf
def parse_interface(line):
interface = None
match = re.search(r'logging source-interface (\S*)', line, re.M)
if match:
interface = match.group(1)
return interface
def map_config_to_obj(module):
obj = []
data = get_config(module, flags=[' all | section logging'])
for line in data.split('\n'):
if re.search(r'no (\S+)', line, re.M):
state = 'absent'
else:
state = 'present'
match = re.search(r'logging (\S+)', line, re.M)
if state == 'present' and match:
event_status = None
name = None
dest_level = None
dest = None
facility = None
remote_server = None
facility_link_status = None
file_size = None
facility_level = None
if match.group(1) in DEST_GROUP:
dest = match.group(1)
name = parse_name(line, dest)
remote_server = parse_remote_server(line, dest)
dest_level = parse_dest_level(line, dest, name)
if dest == 'server':
facility = parse_facility(line)
facility_level = parse_facility_level(line, facility, dest)
if dest == 'logfile':
file_size = parse_file_size(line, name, dest_level)
elif match.group(1) == 'level':
match_facility = re.search(r'logging level (\S+)', line, re.M)
facility = match_facility.group(1)
level = parse_facility_level(line, facility, dest)
if level.isdigit():
facility_level = level
else:
facility_link_status = parse_facility_link_status(line, facility, level)
elif match.group(1) == 'event' and state == 'present':
event = parse_event(line)
if event:
status = parse_event_status(line, event)
if status:
event_status = event + '-' + status
else:
continue
else:
pass
obj.append({'dest': dest,
'remote_server': remote_server,
'use_vrf': parse_use_vrf(line, dest),
'name': name,
'facility': facility,
'dest_level': dest_level,
'facility_level': facility_level,
'interface': parse_interface(line),
'facility_link_status': facility_link_status,
'event': event_status,
'file_size': file_size,
'message': parse_message(line),
'timestamp': parse_timestamp(line)})
cmd = [{'command': 'show logging | section enabled | section console', 'output': 'text'},
{'command': 'show logging | section enabled | section monitor', 'output': 'text'}]
default_data = run_commands(module, cmd)
for line in default_data:
flag = False
match = re.search(r'Logging (\w+):(?:\s+) (?:\w+) (?:\W)Severity: (\w+)', str(line), re.M)
if match:
if match.group(1) == 'console' and match.group(2) == 'critical':
dest_level = '2'
flag = True
elif match.group(1) == 'monitor' and match.group(2) == 'notifications':
dest_level = '5'
flag = True
if flag:
obj.append({'dest': match.group(1),
'remote_server': None,
'name': None,
'facility': None,
'dest_level': dest_level,
'facility_level': None,
'use_vrf': None,
'interface': None,
'facility_link_status': None,
'event': None,
'file_size': None,
'message': None,
'timestamp': None})
return obj
def map_params_to_obj(module):
obj = []
if 'aggregate' in module.params and module.params['aggregate']:
args = {'dest': '',
'remote_server': '',
'use_vrf': '',
'name': '',
'facility': '',
'dest_level': '',
'facility_level': '',
'interface': '',
'facility_link_status': None,
'event': None,
'file_size': None,
'message': None,
'timestamp': None}
for c in module.params['aggregate']:
d = c.copy()
for key in args:
if key not in d:
d[key] = None
if d['dest_level'] is not None:
d['dest_level'] = str(d['dest_level'])
if d['facility_level'] is not None:
d['facility_level'] = str(d['facility_level'])
if d['interface']:
d['interface'] = normalize_interface(d['interface'])
if 'state' not in d:
d['state'] = module.params['state']
if d['file_size']:
d['file_size'] = str(d['file_size'])
obj.append(d)
else:
dest_level = None
facility_level = None
file_size = None
if module.params['dest_level'] is not None:
dest_level = str(module.params['dest_level'])
if module.params['facility_level'] is not None:
facility_level = str(module.params['facility_level'])
if module.params['file_size'] is not None:
file_size = str(module.params['file_size'])
obj.append({
'dest': module.params['dest'],
'remote_server': module.params['remote_server'],
'use_vrf': module.params['use_vrf'],
'name': module.params['name'],
'facility': module.params['facility'],
'dest_level': dest_level,
'facility_level': facility_level,
'interface': normalize_interface(module.params['interface']),
'state': module.params['state'],
'facility_link_status': module.params['facility_link_status'],
'event': module.params['event'],
'message': module.params['interface_message'],
'file_size': file_size,
'timestamp': module.params['timestamp']
})
return obj
def merge_wants(wants, want):
if not wants:
wants = list()
for w in want:
w = copy.copy(w)
state = w['state']
del w['state']
if state == 'absent':
if w in wants:
wants.remove(w)
elif w not in wants:
wants.append(w)
return wants
def absent(h):
h['state'] = 'absent'
return h
def outliers(haves, wants):
wants = list(wants)
return [absent(h) for h in haves if not (h in wants or wants.append(h))]
def main():
""" main entry point for module execution
"""
argument_spec = dict(
dest=dict(choices=DEST_GROUP),
name=dict(),
facility=dict(),
remote_server=dict(),
use_vrf=dict(),
dest_level=dict(type='int', aliases=['level']),
facility_level=dict(type='int'),
interface=dict(),
facility_link_status=dict(choices=['link-down-notif', 'link-down-error', 'link-up-notif', 'link-up-error']),
event=dict(choices=['link-enable', 'link-default', 'trunk-enable', 'trunk-default']),
interface_message=dict(choices=['add-interface-description']),
file_size=dict(type='int'),
timestamp=dict(choices=['microseconds', 'milliseconds', 'seconds']),
state=dict(default='present', choices=['present', 'absent']),
aggregate=dict(type='list'),
purge=dict(default=False, type='bool'),
)
argument_spec.update(nxos_argument_spec)
required_if = [('dest', 'logfile', ['name']),
('dest', 'server', ['remote_server'])]
module = AnsibleModule(argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
if warnings:
result['warnings'] = warnings
want = map_params_to_obj(module)
merged_wants = merge_wants(read_module_context(module), want)
have = map_config_to_obj(module)
commands = map_obj_to_commands(module, (want, have))
result['commands'] = commands
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
save_module_context(module, merged_wants)
if module.params.get('purge'):
pcommands = map_obj_to_commands(module, (outliers(have, merged_wants), have))
if pcommands:
if not module.check_mode:
load_config(module, pcommands)
result['changed'] = True
result['commands'] += pcommands
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,423 +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: nxos_ntp
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages core NTP configuration.
description:
- Manages core NTP configuration.
author:
- Jason Edelman (@jedelman8)
options:
server:
description:
- Network address of NTP server.
peer:
description:
- Network address of NTP peer.
key_id:
description:
- Authentication key identifier to use with
given NTP server or peer or keyword 'default'.
prefer:
description:
- Makes given NTP server or peer the preferred
NTP server or peer for the device.
choices: ['enabled', 'disabled']
vrf_name:
description:
- Makes the device communicate with the given
NTP server or peer over a specific VRF or
keyword 'default'.
source_addr:
description:
- Local source address from which NTP messages are sent
or keyword 'default'
source_int:
description:
- Local source interface from which NTP messages are sent.
Must be fully qualified interface name or keyword 'default'
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# Set NTP Server with parameters
- nxos_ntp:
server: 1.2.3.4
key_id: 32
prefer: enabled
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
'''
RETURN = '''
proposed:
description: k/v pairs of parameters passed into module
returned: always
type: dict
sample: {"address": "192.0.2.2", "key_id": "48",
"peer_type": "server", "prefer": "enabled",
"source": "192.0.2.3", "source_type": "source"}
existing:
description:
- k/v pairs of existing ntp server/peer
returned: always
type: dict
sample: {"address": "192.0.2.2", "key_id": "32",
"peer_type": "server", "prefer": "enabled",
"source": "ethernet2/1", "source_type": "source-interface"}
end_state:
description: k/v pairs of ntp info after module execution
returned: always
type: dict
sample: {"address": "192.0.2.2", "key_id": "48",
"peer_type": "server", "prefer": "enabled",
"source": "192.0.2.3", "source_type": "source"}
updates:
description: command sent to the device
returned: always
type: list
sample: ["ntp server 192.0.2.2 prefer key 48",
"no ntp source-interface ethernet2/1", "ntp source 192.0.2.3"]
changed:
description: check to see if a change was made on the device
returned: always
type: bool
sample: true
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.nxos.nxos import load_config, nxos_argument_spec, run_commands
def execute_show_command(command, module, command_type='cli_show'):
if 'show run' not in command:
output = 'json'
else:
output = 'text'
commands = [{
'command': command,
'output': output,
}]
return run_commands(module, commands)
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def get_ntp_source(module):
source_type = None
source = None
command = 'show run | inc ntp.source'
output = execute_show_command(command, module, command_type='cli_show_ascii')
if output:
try:
if 'interface' in output[0]:
source_type = 'source-interface'
else:
source_type = 'source'
source = output[0].split()[2].lower()
except (AttributeError, IndexError):
source_type = None
source = None
return source_type, source
def get_ntp_peer(module):
command = 'show run | inc ntp.(server|peer)'
ntp_peer_list = []
response = execute_show_command(
command, module, command_type='cli_show_ascii')
if response:
if isinstance(response, list):
ntp = response[0]
else:
ntp = response
if ntp:
ntp_regex = (
r".*ntp\s(server\s(?P<address>\S+)|peer\s(?P<peer_address>\S+))"
r"\s*((?P<prefer>prefer)\s*)?(use-vrf\s(?P<vrf_name>\S+)\s*)?"
r"(key\s(?P<key_id>\d+))?.*"
)
split_ntp = ntp.splitlines()
for peer_line in split_ntp:
if 'access-group' in peer_line:
continue
ntp_peer = {}
try:
peer_address = None
vrf_name = 'default'
prefer = None
key_id = None
match_ntp = re.match(ntp_regex, peer_line, re.DOTALL)
group_ntp = match_ntp.groupdict()
address = group_ntp["address"]
peer_address = group_ntp['peer_address']
prefer = group_ntp['prefer']
vrf_name = group_ntp['vrf_name']
key_id = group_ntp['key_id']
if prefer is not None:
prefer = 'enabled'
else:
prefer = 'disabled'
if address is not None:
peer_type = 'server'
elif peer_address is not None:
peer_type = 'peer'
address = peer_address
args = dict(peer_type=peer_type, address=address, prefer=prefer,
vrf_name=vrf_name, key_id=key_id)
ntp_peer = dict((k, v) for k, v in args.items())
ntp_peer_list.append(ntp_peer)
except AttributeError:
ntp_peer_list = []
return ntp_peer_list
def get_ntp_existing(address, peer_type, module):
peer_dict = {}
peer_server_list = []
peer_list = get_ntp_peer(module)
for peer in peer_list:
if peer['address'] == address:
peer_dict.update(peer)
else:
peer_server_list.append(peer)
source_type, source = get_ntp_source(module)
if (source_type is not None and source is not None):
peer_dict['source_type'] = source_type
peer_dict['source'] = source
return (peer_dict, peer_server_list)
def set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name):
command_strings = []
if prefer:
command_strings.append(' prefer')
if key_id:
command_strings.append(' key {0}'.format(key_id))
if vrf_name:
command_strings.append(' use-vrf {0}'.format(vrf_name))
command_strings.insert(0, 'ntp {0} {1}'.format(peer_type, address))
command = ''.join(command_strings)
return command
def config_ntp(delta, existing):
if (delta.get('address') or delta.get('peer_type') or delta.get('vrf_name') or
delta.get('key_id') or delta.get('prefer')):
address = delta.get('address', existing.get('address'))
peer_type = delta.get('peer_type', existing.get('peer_type'))
key_id = delta.get('key_id', existing.get('key_id'))
prefer = delta.get('prefer', existing.get('prefer'))
vrf_name = delta.get('vrf_name', existing.get('vrf_name'))
if delta.get('key_id') == 'default':
key_id = None
else:
peer_type = None
prefer = None
source_type = delta.get('source_type')
source = delta.get('source')
if prefer:
if prefer == 'enabled':
prefer = True
elif prefer == 'disabled':
prefer = False
if source:
source_type = delta.get('source_type', existing.get('source_type'))
ntp_cmds = []
if peer_type:
if existing.get('peer_type') and existing.get('address'):
ntp_cmds.append('no ntp {0} {1}'.format(existing.get('peer_type'),
existing.get('address')))
ntp_cmds.append(set_ntp_server_peer(
peer_type, address, prefer, key_id, vrf_name))
if source:
existing_source_type = existing.get('source_type')
existing_source = existing.get('source')
if existing_source_type and source_type != existing_source_type:
ntp_cmds.append('no ntp {0} {1}'.format(existing_source_type, existing_source))
if source == 'default':
if existing_source_type and existing_source:
ntp_cmds.append('no ntp {0} {1}'.format(existing_source_type, existing_source))
else:
ntp_cmds.append('ntp {0} {1}'.format(source_type, source))
return ntp_cmds
def main():
argument_spec = dict(
server=dict(type='str'),
peer=dict(type='str'),
key_id=dict(type='str'),
prefer=dict(type='str', choices=['enabled', 'disabled']),
vrf_name=dict(type='str'),
source_addr=dict(type='str'),
source_int=dict(type='str'),
state=dict(choices=['absent', 'present'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[
['server', 'peer'],
['source_addr', 'source_int']],
supports_check_mode=True)
warnings = list()
server = module.params['server'] or None
peer = module.params['peer'] or None
key_id = module.params['key_id']
prefer = module.params['prefer']
vrf_name = module.params['vrf_name']
source_addr = module.params['source_addr']
source_int = module.params['source_int']
state = module.params['state']
if source_int is not None:
source_int = source_int.lower()
if server:
peer_type = 'server'
address = server
elif peer:
peer_type = 'peer'
address = peer
else:
peer_type = None
address = None
source_type = None
source = None
if source_addr:
source_type = 'source'
source = source_addr
elif source_int:
source_type = 'source-interface'
source = source_int
if key_id or vrf_name or prefer:
if not server and not peer:
module.fail_json(
msg='Please supply the server or peer parameter')
args = dict(peer_type=peer_type, address=address, key_id=key_id,
prefer=prefer, vrf_name=vrf_name, source_type=source_type,
source=source)
proposed = dict((k, v) for k, v in args.items() if v is not None)
existing, peer_server_list = get_ntp_existing(address, peer_type, module)
end_state = existing
changed = False
commands = []
if state == 'present':
delta = dict(set(proposed.items()).difference(existing.items()))
if delta.get('key_id') and delta.get('key_id') == 'default':
if not existing.get('key_id'):
delta.pop('key_id')
if delta:
command = config_ntp(delta, existing)
if command:
commands.append(command)
elif state == 'absent':
if existing.get('peer_type') and existing.get('address'):
command = 'no ntp {0} {1}'.format(
existing['peer_type'], existing['address'])
if command:
commands.append([command])
existing_source_type = existing.get('source_type')
existing_source = existing.get('source')
proposed_source_type = proposed.get('source_type')
proposed_source = proposed.get('source')
if proposed_source_type:
if proposed_source_type == existing_source_type:
if proposed_source == existing_source:
command = 'no ntp {0} {1}'.format(
existing_source_type, existing_source)
if command:
commands.append([command])
cmds = flatten_list(commands)
if cmds:
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
changed = True
load_config(module, cmds)
end_state = get_ntp_existing(address, peer_type, module)[0]
if 'configure' in cmds:
cmds.pop(0)
results = {}
results['proposed'] = proposed
results['existing'] = existing
results['updates'] = cmds
results['changed'] = changed
results['warnings'] = warnings
results['end_state'] = end_state
results['peer_server_list'] = peer_server_list
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,322 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_ntp_auth
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages NTP authentication.
description:
- Manages NTP authentication.
author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- If C(state=absent), the module will remove the given key configuration if it exists.
- If C(state=absent) and C(authentication=on), authentication will be turned off.
options:
key_id:
description:
- Authentication key identifier (numeric).
md5string:
description:
- MD5 String.
auth_type:
description:
- Whether the given md5string is in cleartext or
has been encrypted. If in cleartext, the device
will encrypt it before storing it.
default: text
choices: ['text', 'encrypt']
trusted_key:
description:
- Whether the given key is required to be supplied by a time source
for the device to synchronize to the time source.
choices: [ 'false', 'true' ]
default: 'false'
authentication:
description:
- Turns NTP authentication on or off.
choices: ['on', 'off']
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# Basic NTP authentication configuration
- nxos_ntp_auth:
key_id: 32
md5string: hello
auth_type: text
'''
RETURN = '''
commands:
description: command sent to the device
returned: always
type: list
sample: ["ntp authentication-key 32 md5 helloWorld 0", "ntp trusted-key 32"]
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def execute_show_command(command, module):
if 'show run' not in command:
command = {
'command': command,
'output': 'json',
}
else:
command = {
'command': command,
'output': 'text',
}
return run_commands(module, [command])
def flatten_list(command_lists):
flat_command_list = []
for command in command_lists:
if isinstance(command, list):
flat_command_list.extend(command)
else:
flat_command_list.append(command)
return flat_command_list
def get_ntp_auth(module):
command = 'show ntp authentication-status'
body = execute_show_command(command, module)[0]
ntp_auth_str = body['authentication']
if 'enabled' in ntp_auth_str:
ntp_auth = True
else:
ntp_auth = False
return ntp_auth
def get_ntp_trusted_key(module):
trusted_key_list = []
command = 'show run | inc ntp.trusted-key'
trusted_key_str = execute_show_command(command, module)[0]
if trusted_key_str:
trusted_keys = trusted_key_str.splitlines()
else:
trusted_keys = []
for line in trusted_keys:
if line:
trusted_key_list.append(str(line.split()[2]))
return trusted_key_list
def get_ntp_auth_key(key_id, module):
authentication_key = {}
command = 'show run | inc ntp.authentication-key.{0}'.format(key_id)
auth_regex = (r".*ntp\sauthentication-key\s(?P<key_id>\d+)\s"
r"md5\s(?P<md5string>\S+)\s(?P<atype>\S+).*")
body = execute_show_command(command, module)[0]
try:
match_authentication = re.match(auth_regex, body, re.DOTALL)
group_authentication = match_authentication.groupdict()
authentication_key['key_id'] = group_authentication['key_id']
authentication_key['md5string'] = group_authentication['md5string']
if group_authentication['atype'] == '7':
authentication_key['auth_type'] = 'encrypt'
else:
authentication_key['auth_type'] = 'text'
except (AttributeError, TypeError):
authentication_key = {}
return authentication_key
def get_ntp_auth_info(key_id, module):
auth_info = get_ntp_auth_key(key_id, module)
trusted_key_list = get_ntp_trusted_key(module)
auth_power = get_ntp_auth(module)
if key_id in trusted_key_list:
auth_info['trusted_key'] = 'true'
else:
auth_info['trusted_key'] = 'false'
if auth_power:
auth_info['authentication'] = 'on'
else:
auth_info['authentication'] = 'off'
return auth_info
def auth_type_to_num(auth_type):
if auth_type == 'encrypt':
return '7'
else:
return '0'
def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
ntp_auth_cmds = []
if key_id and md5string:
auth_type_num = auth_type_to_num(auth_type)
ntp_auth_cmds.append(
'ntp authentication-key {0} md5 {1} {2}'.format(
key_id, md5string, auth_type_num))
if trusted_key == 'true':
ntp_auth_cmds.append(
'ntp trusted-key {0}'.format(key_id))
elif trusted_key == 'false':
ntp_auth_cmds.append(
'no ntp trusted-key {0}'.format(key_id))
if authentication == 'on':
ntp_auth_cmds.append(
'ntp authenticate')
elif authentication == 'off':
ntp_auth_cmds.append(
'no ntp authenticate')
return ntp_auth_cmds
def remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
auth_remove_cmds = []
if key_id:
auth_type_num = auth_type_to_num(auth_type)
auth_remove_cmds.append(
'no ntp authentication-key {0} md5 {1} {2}'.format(
key_id, md5string, auth_type_num))
if authentication:
auth_remove_cmds.append(
'no ntp authenticate')
return auth_remove_cmds
def main():
argument_spec = dict(
key_id=dict(type='str'),
md5string=dict(type='str'),
auth_type=dict(choices=['text', 'encrypt'], default='text'),
trusted_key=dict(choices=['true', 'false'], default='false'),
authentication=dict(choices=['on', 'off']),
state=dict(choices=['absent', 'present'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
key_id = module.params['key_id']
md5string = module.params['md5string']
auth_type = module.params['auth_type']
trusted_key = module.params['trusted_key']
authentication = module.params['authentication']
state = module.params['state']
if key_id:
if not trusted_key and not md5string:
module.fail_json(msg='trusted_key or md5string MUST be specified')
args = dict(key_id=key_id, md5string=md5string,
auth_type=auth_type, trusted_key=trusted_key,
authentication=authentication)
changed = False
proposed = dict((k, v) for k, v in args.items() if v is not None)
existing = get_ntp_auth_info(key_id, module)
end_state = existing
delta = dict(set(proposed.items()).difference(existing.items()))
commands = []
if state == 'present':
if delta:
command = set_ntp_auth_key(
key_id, md5string, delta.get('auth_type'),
delta.get('trusted_key'), delta.get('authentication'))
if command:
commands.append(command)
elif state == 'absent':
auth_toggle = None
if existing.get('authentication') == 'on':
auth_toggle = True
if not existing.get('key_id'):
key_id = None
command = remove_ntp_auth_key(
key_id, md5string, auth_type, trusted_key, auth_toggle)
if command:
commands.append(command)
cmds = flatten_list(commands)
if cmds:
if module.check_mode:
module.exit_json(changed=True, commands=cmds)
else:
load_config(module, cmds)
end_state = get_ntp_auth_info(key_id, module)
delta = dict(set(end_state.items()).difference(existing.items()))
if delta or (len(existing) != len(end_state)):
changed = True
if 'configure' in cmds:
cmds.pop(0)
results = {}
results['proposed'] = proposed
results['existing'] = existing
results['updates'] = cmds
results['changed'] = changed
results['warnings'] = warnings
results['end_state'] = end_state
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,165 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_ntp_options
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages NTP options.
description:
- Manages NTP options, e.g. authoritative server and logging.
author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- When C(state=absent), master and logging will be set to False and
stratum will be removed as well
options:
master:
description:
- Sets whether the device is an authoritative NTP server.
type: bool
stratum:
description:
- If C(master=true), an optional stratum can be supplied (1-15).
The device default is 8.
logging:
description:
- Sets whether NTP logging is enabled on the device.
type: bool
state:
description:
- Manage the state of the resource.
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
# Basic NTP options configuration
- nxos_ntp_options:
master: true
stratum: 12
logging: false
host: "{{ inventory_hostname }}"
username: "{{ un }}"
password: "{{ pwd }}"
'''
RETURN = '''
updates:
description: command sent to the device
returned: always
type: list
sample: ["no ntp logging", "ntp master 12"]
'''
import re
from ansible.module_utils.network.nxos.nxos import load_config, run_commands
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
def get_current(module):
cmd = ('show running-config | inc ntp')
master = False
logging = False
stratum = None
output = run_commands(module, ({'command': cmd, 'output': 'text'}))[0]
if output:
match = re.search(r"^ntp master(?: (\d+))", output, re.M)
if match:
master = True
stratum = match.group(1)
logging = 'ntp logging' in output.lower()
return {'master': master, 'stratum': stratum, 'logging': logging}
def main():
argument_spec = dict(
master=dict(required=False, type='bool'),
stratum=dict(required=False, type='str'),
logging=dict(required=False, type='bool'),
state=dict(choices=['absent', 'present'], default='present'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
master = module.params['master']
stratum = module.params['stratum']
logging = module.params['logging']
state = module.params['state']
if stratum and master is False:
if stratum != 8:
module.fail_json(msg='master MUST be True when stratum is changed')
current = get_current(module)
result = {'changed': False}
commands = list()
if state == 'absent':
if current['master']:
commands.append('no ntp master')
if current['logging']:
commands.append('no ntp logging')
elif state == 'present':
if master and not current['master']:
commands.append('ntp master')
elif master is False and current['master']:
commands.append('no ntp master')
if stratum and stratum != current['stratum']:
commands.append('ntp master %s' % stratum)
if logging and not current['logging']:
commands.append('ntp logging')
elif logging is False and current['logging']:
commands.append('no ntp logging')
result['commands'] = commands
result['updates'] = commands
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
result['warnings'] = warnings
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,408 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: nxos_nxapi
extends_documentation_fragment: nxos
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Manage NXAPI configuration on an NXOS device.
description:
- Configures the NXAPI feature on devices running Cisco NXOS. The
NXAPI feature is absent from the configuration by default. Since
this module manages the NXAPI feature it only supports the use
of the C(Cli) transport.
options:
http_port:
description:
- Configure the port with which the HTTP server will listen on
for requests. By default, NXAPI will bind the HTTP service
to the standard HTTP port 80. This argument accepts valid
port values in the range of 1 to 65535.
required: false
default: 80
http:
description:
- Controls the operating state of the HTTP protocol as one of the
underlying transports for NXAPI. By default, NXAPI will enable
the HTTP transport when the feature is first configured. To
disable the use of the HTTP transport, set the value of this
argument to False.
required: false
default: yes
type: bool
aliases: ['enable_http']
https_port:
description:
- Configure the port with which the HTTPS server will listen on
for requests. By default, NXAPI will bind the HTTPS service
to the standard HTTPS port 443. This argument accepts valid
port values in the range of 1 to 65535.
required: false
default: 443
https:
description:
- Controls the operating state of the HTTPS protocol as one of the
underlying transports for NXAPI. By default, NXAPI will disable
the HTTPS transport when the feature is first configured. To
enable the use of the HTTPS transport, set the value of this
argument to True.
required: false
default: no
type: bool
aliases: ['enable_https']
sandbox:
description:
- The NXAPI feature provides a web base UI for developers for
entering commands. This feature is initially disabled when
the NXAPI feature is configured for the first time. When the
C(sandbox) argument is set to True, the developer sandbox URL
will accept requests and when the value is set to False, the
sandbox URL is unavailable. This is supported on NX-OS 7K series.
required: false
default: no
type: bool
aliases: ['enable_sandbox']
state:
description:
- The C(state) argument controls whether or not the NXAPI
feature is configured on the remote device. When the value
is C(present) the NXAPI feature configuration is present in
the device running-config. When the values is C(absent) the
feature configuration is removed from the running-config.
choices: ['present', 'absent']
required: false
default: present
ssl_strong_ciphers:
description:
- Controls the use of whether strong or weak ciphers are configured.
By default, this feature is disabled and weak ciphers are
configured. To enable the use of strong ciphers, set the value of
this argument to True.
required: false
default: no
type: bool
version_added: "2.7"
tlsv1_0:
description:
- Controls the use of the Transport Layer Security version 1.0 is
configured. By default, this feature is enabled. To disable the
use of TLSV1.0, set the value of this argument to True.
required: false
default: yes
type: bool
version_added: "2.7"
tlsv1_1:
description:
- Controls the use of the Transport Layer Security version 1.1 is
configured. By default, this feature is disabled. To enable the
use of TLSV1.1, set the value of this argument to True.
required: false
default: no
type: bool
version_added: "2.7"
tlsv1_2:
description:
- Controls the use of the Transport Layer Security version 1.2 is
configured. By default, this feature is disabled. To enable the
use of TLSV1.2, set the value of this argument to True.
required: false
default: no
type: bool
version_added: "2.7"
"""
EXAMPLES = """
- name: Enable NXAPI access with default configuration
nxos_nxapi:
state: present
- name: Enable NXAPI with no HTTP, HTTPS at port 9443 and sandbox disabled
nxos_nxapi:
enable_http: false
https_port: 9443
https: yes
enable_sandbox: no
- name: remove NXAPI configuration
nxos_nxapi:
state: absent
"""
RETURN = """
updates:
description:
- Returns the list of commands that need to be pushed into the remote
device to satisfy the arguments
returned: always
type: list
sample: ['no feature nxapi']
"""
import re
from distutils.version import LooseVersion
from ansible.module_utils.network.nxos.nxos import run_commands, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.network.nxos.nxos import get_capabilities
from ansible.module_utils.basic import AnsibleModule
def check_args(module, warnings, capabilities):
network_api = capabilities.get('network_api', 'nxapi')
if network_api == 'nxapi':
module.fail_json(msg='module not supported over nxapi transport')
os_platform = capabilities['device_info']['network_os_platform']
if '7K' not in os_platform and module.params['sandbox']:
module.fail_json(msg='sandbox or enable_sandbox is supported on NX-OS 7K series of switches')
state = module.params['state']
if state == 'started':
module.params['state'] = 'present'
warnings.append('state=started is deprecated and will be removed in a '
'a future release. Please use state=present instead')
elif state == 'stopped':
module.params['state'] = 'absent'
warnings.append('state=stopped is deprecated and will be removed in a '
'a future release. Please use state=absent instead')
for key in ['http_port', 'https_port']:
if module.params[key] is not None:
if not 1 <= module.params[key] <= 65535:
module.fail_json(msg='%s must be between 1 and 65535' % key)
return warnings
def map_obj_to_commands(want, have, module, warnings, capabilities):
send_commands = list()
commands = dict()
os_platform = None
os_version = None
device_info = capabilities.get('device_info')
if device_info:
os_version = device_info.get('network_os_version')
if os_version:
os_version = os_version[:3]
os_platform = device_info.get('network_os_platform')
if os_platform:
os_platform = os_platform[:3]
def needs_update(x):
return want.get(x) is not None and (want.get(x) != have.get(x))
if needs_update('state'):
if want['state'] == 'absent':
return ['no feature nxapi']
send_commands.append('feature nxapi')
elif want['state'] == 'absent':
return send_commands
for parameter in ['http', 'https']:
port_param = parameter + '_port'
if needs_update(parameter):
if want.get(parameter) is False:
commands[parameter] = 'no nxapi %s' % parameter
else:
commands[parameter] = 'nxapi %s port %s' % (parameter, want.get(port_param))
if needs_update(port_param) and want.get(parameter) is True:
commands[parameter] = 'nxapi %s port %s' % (parameter, want.get(port_param))
if needs_update('sandbox'):
commands['sandbox'] = 'nxapi sandbox'
if not want['sandbox']:
commands['sandbox'] = 'no %s' % commands['sandbox']
if os_platform and os_version:
if (os_platform == 'N9K' or os_platform == 'N3K') and LooseVersion(os_version) >= "9.2":
if needs_update('ssl_strong_ciphers'):
commands['ssl_strong_ciphers'] = 'nxapi ssl ciphers weak'
if want['ssl_strong_ciphers'] is True:
commands['ssl_strong_ciphers'] = 'no nxapi ssl ciphers weak'
have_ssl_protocols = ''
want_ssl_protocols = ''
for key, value in {'tlsv1_2': 'TLSv1.2', 'tlsv1_1': 'TLSv1.1', 'tlsv1_0': 'TLSv1'}.items():
if needs_update(key):
if want.get(key) is True:
want_ssl_protocols = " ".join([want_ssl_protocols, value])
elif have.get(key) is True:
have_ssl_protocols = " ".join([have_ssl_protocols, value])
if len(want_ssl_protocols) > 0:
commands['ssl_protocols'] = 'nxapi ssl protocols%s' % (" ".join([want_ssl_protocols, have_ssl_protocols]))
else:
warnings.append('os_version and/or os_platform keys from '
'platform capabilities are not available. '
'Any NXAPI SSL optional arguments will be ignored')
send_commands.extend(commands.values())
return send_commands
def parse_http(data):
http_res = [r'nxapi http port (\d+)']
http_port = None
for regex in http_res:
match = re.search(regex, data, re.M)
if match:
http_port = int(match.group(1))
break
return {'http': http_port is not None, 'http_port': http_port}
def parse_https(data):
https_res = [r'nxapi https port (\d+)']
https_port = None
for regex in https_res:
match = re.search(regex, data, re.M)
if match:
https_port = int(match.group(1))
break
return {'https': https_port is not None, 'https_port': https_port}
def parse_sandbox(data):
sandbox = [item for item in data.split('\n') if re.search(r'.*sandbox.*', item)]
value = False
if sandbox and sandbox[0] == 'nxapi sandbox':
value = True
return {'sandbox': value}
def parse_ssl_strong_ciphers(data):
ciphers_res = [r'(\w+) nxapi ssl ciphers weak']
value = None
for regex in ciphers_res:
match = re.search(regex, data, re.M)
if match:
value = match.group(1)
break
return {'ssl_strong_ciphers': value == 'no'}
def parse_ssl_protocols(data):
tlsv1_0 = re.search(r'(?<!\S)TLSv1(?!\S)', data, re.M) is not None
tlsv1_1 = re.search(r'(?<!\S)TLSv1.1(?!\S)', data, re.M) is not None
tlsv1_2 = re.search(r'(?<!\S)TLSv1.2(?!\S)', data, re.M) is not None
return {'tlsv1_0': tlsv1_0, 'tlsv1_1': tlsv1_1, 'tlsv1_2': tlsv1_2}
def map_config_to_obj(module):
out = run_commands(module, ['show run all | inc nxapi'], check_rc=False)[0]
match = re.search(r'no feature nxapi', out, re.M)
# There are two possible outcomes when nxapi is disabled on nxos platforms.
# 1. Nothing is displayed in the running config.
# 2. The 'no feature nxapi' command is displayed in the running config.
if match or out == '':
return {'state': 'absent'}
out = str(out).strip()
obj = {'state': 'present'}
obj.update(parse_http(out))
obj.update(parse_https(out))
obj.update(parse_sandbox(out))
obj.update(parse_ssl_strong_ciphers(out))
obj.update(parse_ssl_protocols(out))
return obj
def map_params_to_obj(module):
obj = {
'http': module.params['http'],
'http_port': module.params['http_port'],
'https': module.params['https'],
'https_port': module.params['https_port'],
'sandbox': module.params['sandbox'],
'state': module.params['state'],
'ssl_strong_ciphers': module.params['ssl_strong_ciphers'],
'tlsv1_0': module.params['tlsv1_0'],
'tlsv1_1': module.params['tlsv1_1'],
'tlsv1_2': module.params['tlsv1_2']
}
return obj
def main():
""" main entry point for module execution
"""
argument_spec = dict(
http=dict(aliases=['enable_http'], type='bool', default=True),
http_port=dict(type='int', default=80),
https=dict(aliases=['enable_https'], type='bool', default=False),
https_port=dict(type='int', default=443),
sandbox=dict(aliases=['enable_sandbox'], type='bool'),
state=dict(default='present', choices=['started', 'stopped', 'present', 'absent']),
ssl_strong_ciphers=dict(type='bool', default=False),
tlsv1_0=dict(type='bool', default=True),
tlsv1_1=dict(type='bool', default=False),
tlsv1_2=dict(type='bool', default=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
warning_msg = "Module nxos_nxapi currently defaults to configure 'http port 80'. "
warning_msg += "Default behavior is changing to configure 'https port 443'"
warning_msg += " when params 'http, http_port, https, https_port' are not set in the playbook"
module.deprecate(msg=warning_msg, version="2.11")
capabilities = get_capabilities(module)
check_args(module, warnings, capabilities)
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands(want, have, module, warnings, capabilities)
result = {'changed': False, 'warnings': warnings, 'commands': commands}
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,153 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_ospf
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages configuration of an ospf instance.
description:
- Manages configuration of an ospf instance.
author: Gabriele Gerbino (@GGabriele)
options:
ospf:
description:
- Name of the ospf instance.
required: true
state:
description:
- Determines whether the config should be present or not
on the device.
required: false
default: present
choices: ['present','absent']
'''
EXAMPLES = '''
- nxos_ospf:
ospf: 1
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["router ospf 1"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
PARAM_TO_COMMAND_KEYMAP = {
'ospf': 'router ospf'
}
def get_value(config, module):
splitted_config = config.splitlines()
value_list = []
REGEX = r'^router ospf\s(?P<ospf>\S+).*'
for line in splitted_config:
value = ''
if 'router ospf' in line:
try:
match_ospf = re.match(REGEX, line, re.DOTALL)
ospf_group = match_ospf.groupdict()
value = ospf_group['ospf']
except AttributeError:
value = ''
if value:
value_list.append(value)
return value_list
def get_existing(module):
existing = {}
config = str(get_config(module))
value = get_value(config, module)
if value:
existing['ospf'] = value
return existing
def state_present(module, proposed, candidate):
commands = ['router ospf {0}'.format(proposed['ospf'])]
candidate.add(commands, parents=[])
def state_absent(module, proposed, candidate):
commands = ['no router ospf {0}'.format(proposed['ospf'])]
candidate.add(commands, parents=[])
def main():
argument_spec = dict(
ospf=dict(required=True, type='str'),
state=dict(choices=['present', 'absent'], default='present', required=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
result = dict(changed=False, warnings=warnings)
state = module.params['state']
ospf = str(module.params['ospf'])
existing = get_existing(module)
proposed = dict(ospf=ospf)
if not existing:
existing_list = []
else:
existing_list = existing['ospf']
candidate = CustomNetworkConfig(indent=3)
if state == 'present' and ospf not in existing_list:
state_present(module, proposed, candidate)
if state == 'absent' and ospf in existing_list:
state_absent(module, proposed, candidate)
if candidate:
candidate = candidate.items_text()
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate
else:
result['commands'] = []
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,441 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_ospf_vrf
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Manages a VRF for an OSPF router.
description:
- Manages a VRF for an OSPF router.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Value I(default) restores params default value, if any.
Otherwise it removes the existing param configuration.
options:
vrf:
description:
- Name of the resource instance. Valid value is a string.
The name 'default' is a valid VRF representing the global OSPF.
default: default
ospf:
description:
- Name of the OSPF instance.
required: true
router_id:
description:
- Router Identifier (ID) of the OSPF router VRF instance.
default_metric:
description:
- Specify the default Metric value. Valid values are an integer
or the keyword 'default'.
log_adjacency:
description:
- Controls the level of log messages generated whenever a
neighbor changes state. Valid values are 'log', 'detail',
and 'default'.
choices: ['log','detail','default']
timer_throttle_lsa_start:
description:
- Specify the start interval for rate-limiting Link-State
Advertisement (LSA) generation. Valid values are an integer,
in milliseconds, or the keyword 'default'.
timer_throttle_lsa_hold:
description:
- Specify the hold interval for rate-limiting Link-State
Advertisement (LSA) generation. Valid values are an integer,
in milliseconds, or the keyword 'default'.
timer_throttle_lsa_max:
description:
- Specify the max interval for rate-limiting Link-State
Advertisement (LSA) generation. Valid values are an integer,
in milliseconds, or the keyword 'default'.
timer_throttle_spf_start:
description:
- Specify initial Shortest Path First (SPF) schedule delay.
Valid values are an integer, in milliseconds, or
the keyword 'default'.
timer_throttle_spf_hold:
description:
- Specify minimum hold time between Shortest Path First (SPF)
calculations. Valid values are an integer, in milliseconds,
or the keyword 'default'.
timer_throttle_spf_max:
description:
- Specify the maximum wait time between Shortest Path First (SPF)
calculations. Valid values are an integer, in milliseconds,
or the keyword 'default'.
auto_cost:
description:
- Specifies the reference bandwidth used to assign OSPF cost.
Valid values are an integer, in Mbps, or the keyword 'default'.
bfd:
description:
- Enables BFD on all OSPF interfaces.
- "Dependency: 'feature bfd'"
version_added: "2.9"
type: str
choices: ['enable', 'disable']
passive_interface:
description:
- Setting to C(yes) will suppress routing update on interface.
version_added: "2.4"
type: bool
state:
description:
- State of ospf vrf configuration.
default: present
choices: ['present', 'absent']
'''
EXAMPLES = '''
- nxos_ospf_vrf:
ospf: 1
timer_throttle_spf_start: 50
timer_throttle_spf_hold: 1000
timer_throttle_spf_max: 2000
timer_throttle_lsa_start: 60
timer_throttle_lsa_hold: 1100
timer_throttle_lsa_max: 3000
vrf: test
bfd: enable
state: present
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample:
- router ospf 1
- vrf test
- bfd
- timers throttle lsa 60 1100 3000
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
BOOL_PARAMS = [
'passive_interface'
]
PARAM_TO_COMMAND_KEYMAP = {
'vrf': 'vrf',
'router_id': 'router-id',
'default_metric': 'default-metric',
'log_adjacency': 'log-adjacency-changes',
'timer_throttle_lsa_start': 'timers throttle lsa',
'timer_throttle_lsa_max': 'timers throttle lsa',
'timer_throttle_lsa_hold': 'timers throttle lsa',
'timer_throttle_spf_max': 'timers throttle spf',
'timer_throttle_spf_start': 'timers throttle spf',
'timer_throttle_spf_hold': 'timers throttle spf',
'auto_cost': 'auto-cost reference-bandwidth',
'bfd': 'bfd',
'passive_interface': 'passive-interface default'
}
PARAM_TO_DEFAULT_KEYMAP = {
'timer_throttle_lsa_start': '0',
'timer_throttle_lsa_max': '5000',
'timer_throttle_lsa_hold': '5000',
'timer_throttle_spf_start': '200',
'timer_throttle_spf_max': '5000',
'timer_throttle_spf_hold': '1000',
'auto_cost': '40000',
'bfd': 'disable',
'default_metric': '',
'passive_interface': False,
'router_id': '',
'log_adjacency': '',
}
def get_existing(module, args):
existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
parents = ['router ospf {0}'.format(module.params['ospf'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
config = netcfg.get_section(parents)
for arg in args:
if arg not in ['ospf', 'vrf']:
existing[arg] = PARAM_TO_DEFAULT_KEYMAP.get(arg)
if config:
if module.params['vrf'] == 'default':
splitted_config = config.splitlines()
vrf_index = False
for index in range(0, len(splitted_config) - 1):
if 'vrf' in splitted_config[index].strip():
vrf_index = index
break
if vrf_index:
config = '\n'.join(splitted_config[0:vrf_index])
splitted_config = config.splitlines()
for line in splitted_config:
if 'passive' in line:
existing['passive_interface'] = True
elif 'router-id' in line:
existing['router_id'] = re.search(r'router-id (\S+)', line).group(1)
elif 'metric' in line:
existing['default_metric'] = re.search(r'default-metric (\S+)', line).group(1)
elif 'adjacency' in line:
log = re.search(r'log-adjacency-changes(?: (\S+))?', line).group(1)
if log:
existing['log_adjacency'] = log
else:
existing['log_adjacency'] = 'log'
elif 'auto' in line:
cost = re.search(r'auto-cost reference-bandwidth (\d+) (\S+)', line).group(1)
if 'Gbps' in line:
cost = int(cost) * 1000
existing['auto_cost'] = str(cost)
elif 'bfd' in line:
existing['bfd'] = 'enable'
elif 'timers throttle lsa' in line:
tmp = re.search(r'timers throttle lsa (\S+) (\S+) (\S+)', line)
existing['timer_throttle_lsa_start'] = tmp.group(1)
existing['timer_throttle_lsa_hold'] = tmp.group(2)
existing['timer_throttle_lsa_max'] = tmp.group(3)
elif 'timers throttle spf' in line:
tmp = re.search(r'timers throttle spf (\S+) (\S+) (\S+)', line)
existing['timer_throttle_spf_start'] = tmp.group(1)
existing['timer_throttle_spf_hold'] = tmp.group(2)
existing['timer_throttle_spf_max'] = tmp.group(3)
existing['vrf'] = module.params['vrf']
existing['ospf'] = module.params['ospf']
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key in table:
new_key = key_map.get(key)
if new_key:
new_dict[new_key] = table.get(key)
return new_dict
def get_timer_prd(key, proposed):
if proposed.get(key):
return proposed.get(key)
else:
return PARAM_TO_DEFAULT_KEYMAP.get(key)
def state_present(module, existing, proposed, candidate):
commands = list()
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items():
if key == 'vrf':
continue
if value is True:
commands.append(key)
elif value is False:
if key == 'passive-interface default':
if existing_commands.get(key):
commands.append('no {0}'.format(key))
else:
commands.append('no {0}'.format(key))
elif value == 'default' or value == '':
if key == 'log-adjacency-changes':
commands.append('no {0}'.format(key))
elif existing_commands.get(key):
existing_value = existing_commands.get(key)
commands.append('no {0} {1}'.format(key, existing_value))
else:
if key == 'timers throttle lsa':
command = '{0} {1} {2} {3}'.format(
key,
get_timer_prd('timer_throttle_lsa_start', proposed),
get_timer_prd('timer_throttle_lsa_hold', proposed),
get_timer_prd('timer_throttle_lsa_max', proposed))
elif key == 'timers throttle spf':
command = '{0} {1} {2} {3}'.format(
key,
get_timer_prd('timer_throttle_spf_start', proposed),
get_timer_prd('timer_throttle_spf_hold', proposed),
get_timer_prd('timer_throttle_spf_max', proposed))
elif key == 'log-adjacency-changes':
if value == 'log':
command = key
elif value == 'detail':
command = '{0} {1}'.format(key, value)
elif key == 'auto-cost reference-bandwidth':
if len(value) < 5:
command = '{0} {1} Mbps'.format(key, value)
else:
value = str(int(value) // 1000)
command = '{0} {1} Gbps'.format(key, value)
elif key == 'bfd':
command = 'no bfd' if value == 'disable' else 'bfd'
else:
command = '{0} {1}'.format(key, value.lower())
if command not in commands:
commands.append(command)
if commands:
parents = ['router ospf {0}'.format(module.params['ospf'])]
if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf']))
candidate.add(commands, parents=parents)
def state_absent(module, existing, proposed, candidate):
commands = []
parents = ['router ospf {0}'.format(module.params['ospf'])]
if module.params['vrf'] == 'default':
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in existing_commands.items():
if value and key != 'vrf':
command = None
if key == 'passive-interface default':
command = 'no {0}'.format(key)
elif key == 'timers throttle lsa':
if (existing['timer_throttle_lsa_start'] !=
PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_lsa_start') or
existing['timer_throttle_lsa_hold'] !=
PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_lsa_hold') or
existing['timer_throttle_lsa_max'] !=
PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_lsa_max')):
command = 'no {0} {1} {2} {3}'.format(
key,
existing['timer_throttle_lsa_start'],
existing['timer_throttle_lsa_hold'],
existing['timer_throttle_lsa_max'])
elif key == 'timers throttle spf':
if (existing['timer_throttle_spf_start'] !=
PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_spf_start') or
existing['timer_throttle_spf_hold'] !=
PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_spf_hold') or
existing['timer_throttle_spf_max'] !=
PARAM_TO_DEFAULT_KEYMAP.get('timer_throttle_spf_max')):
command = 'no {0} {1} {2} {3}'.format(
key,
existing['timer_throttle_spf_start'],
existing['timer_throttle_spf_hold'],
existing['timer_throttle_spf_max'])
elif key == 'log-adjacency-changes':
command = 'no {0}'.format(key)
elif key == 'auto-cost reference-bandwidth':
if value != PARAM_TO_DEFAULT_KEYMAP.get('auto_cost'):
command = 'no {0}'.format(key)
else:
command = None
elif key == 'bfd':
if value == 'enable':
command = 'no bfd'
else:
existing_value = existing_commands.get(key)
command = 'no {0} {1}'.format(key, existing_value)
if command:
if command not in commands:
commands.append(command)
else:
if (existing.get('vrf') and
existing.get('vrf') == module.params['vrf']):
commands = ['no vrf {0}'.format(module.params['vrf'])]
if commands:
candidate.add(commands, parents=parents)
def main():
argument_spec = dict(
vrf=dict(required=False, type='str', default='default'),
ospf=dict(required=True, type='str'),
router_id=dict(required=False, type='str'),
default_metric=dict(required=False, type='str'),
log_adjacency=dict(required=False, type='str', choices=['log', 'detail', 'default']),
timer_throttle_lsa_start=dict(required=False, type='str'),
timer_throttle_lsa_hold=dict(required=False, type='str'),
timer_throttle_lsa_max=dict(required=False, type='str'),
timer_throttle_spf_start=dict(required=False, type='str'),
timer_throttle_spf_hold=dict(required=False, type='str'),
timer_throttle_spf_max=dict(required=False, type='str'),
auto_cost=dict(required=False, type='str'),
bfd=dict(required=False, type='str', choices=['enable', 'disable']),
passive_interface=dict(required=False, type='bool'),
state=dict(choices=['present', 'absent'], default='present', required=False)
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
warnings = list()
result = dict(changed=False, commands=[], warnings=warnings)
state = module.params['state']
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args)
proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
proposed = {}
for key, value in proposed_args.items():
if key != 'interface':
if str(value).lower() == 'true':
value = True
elif str(value).lower() == 'false':
value = False
elif str(value).lower() == 'default':
value = PARAM_TO_DEFAULT_KEYMAP.get(key)
if value is None:
value = 'default'
if existing.get(key) != value:
proposed[key] = value
candidate = CustomNetworkConfig(indent=3)
if state == 'present':
state_present(module, existing, proposed, candidate)
if state == 'absent' and existing:
state_absent(module, existing, proposed, candidate)
if candidate:
candidate = candidate.items_text()
result['commands'] = candidate
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,190 +0,0 @@
#!/usr/bin/python
#
# 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/>.
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = '''
---
module: nxos_overlay_global
extends_documentation_fragment: nxos
version_added: "2.2"
short_description: Configures anycast gateway MAC of the switch.
description:
- Configures anycast gateway MAC of the switch.
author: Gabriele Gerbino (@GGabriele)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- Default restores params default value
- Supported MAC address format are "E.E.E", "EE-EE-EE-EE-EE-EE",
"EE:EE:EE:EE:EE:EE" and "EEEE.EEEE.EEEE"
options:
anycast_gateway_mac:
description:
- Anycast gateway mac of the switch.
required: true
'''
EXAMPLES = '''
- nxos_overlay_global:
anycast_gateway_mac: "b.b.b"
'''
RETURN = '''
commands:
description: commands sent to the device
returned: always
type: list
sample: ["fabric forwarding anycast-gateway-mac 000B.000B.000B"]
'''
import re
from ansible.module_utils.network.nxos.nxos import get_config, load_config
from ansible.module_utils.network.nxos.nxos import nxos_argument_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.config import CustomNetworkConfig
PARAM_TO_COMMAND_KEYMAP = {
'anycast_gateway_mac': 'fabric forwarding anycast-gateway-mac',
}
def get_existing(module, args):
existing = {}
config = str(get_config(module))
for arg in args:
command = PARAM_TO_COMMAND_KEYMAP[arg]
has_command = re.findall(r'(?:{0}\s)(?P<value>.*)$'.format(command), config, re.M)
value = ''
if has_command:
value = has_command[0]
existing[arg] = value
return existing
def apply_key_map(key_map, table):
new_dict = {}
for key, value in table.items():
new_key = key_map.get(key)
if value:
new_dict[new_key] = value
return new_dict
def get_commands(module, existing, proposed, candidate):
commands = list()
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, proposed in proposed_commands.items():
existing_value = existing_commands.get(key)
if proposed == 'default' and existing_value:
commands.append('no {0} {1}'.format(key, existing_value))
elif 'anycast-gateway-mac' in key and proposed != 'default':
proposed = normalize_mac(proposed, module)
existing_value = normalize_mac(existing_value, module)
if proposed != existing_value:
command = '{0} {1}'.format(key, proposed)
commands.append(command)
if commands:
candidate.add(commands, parents=[])
def normalize_mac(proposed_mac, module):
if proposed_mac is None:
return ''
try:
if '-' in proposed_mac:
splitted_mac = proposed_mac.split('-')
if len(splitted_mac) != 6:
raise ValueError
for octect in splitted_mac:
if len(octect) != 2:
raise ValueError
elif '.' in proposed_mac:
splitted_mac = []
splitted_dot_mac = proposed_mac.split('.')
if len(splitted_dot_mac) != 3:
raise ValueError
for octect in splitted_dot_mac:
if len(octect) > 4:
raise ValueError
else:
octect_len = len(octect)
padding = 4 - octect_len
splitted_mac.append(octect.zfill(padding + 1))
elif ':' in proposed_mac:
splitted_mac = proposed_mac.split(':')
if len(splitted_mac) != 6:
raise ValueError
for octect in splitted_mac:
if len(octect) != 2:
raise ValueError
else:
raise ValueError
except ValueError:
module.fail_json(msg='Invalid MAC address format', proposed_mac=proposed_mac)
joined_mac = ''.join(splitted_mac)
mac = [joined_mac[i:i + 4] for i in range(0, len(joined_mac), 4)]
return '.'.join(mac).upper()
def main():
argument_spec = dict(
anycast_gateway_mac=dict(required=True, type='str'),
)
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
warnings = list()
result = {'changed': False, 'commands': [], 'warnings': warnings}
args = PARAM_TO_COMMAND_KEYMAP.keys()
existing = get_existing(module, args)
proposed = dict((k, v) for k, v in module.params.items()
if v is not None and k in args)
candidate = CustomNetworkConfig(indent=3)
get_commands(module, existing, proposed, candidate)
if candidate:
candidate = candidate.items_text()
result['commands'] = candidate
if not module.check_mode:
load_config(module, candidate)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save