Add new module eos_l3_interfaces (#59820)

* Update base files for resource modules

* Add modules

* Add module_utils

* Add tests

* Deprecate eos_l3_interface

* Update facts
This commit is contained in:
Nathaniel Case 2019-08-22 09:06:38 -04:00 committed by GitHub
parent aefe31e6d9
commit 8c5936671e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 991 additions and 7 deletions

View file

@ -15,6 +15,8 @@ CHOICES = [
'!interfaces', '!interfaces',
'l2_interfaces', 'l2_interfaces',
'!l2_interfaces', '!l2_interfaces',
'l3_interfaces',
'!l3_interfaces',
'lacp', 'lacp',
'!lacp', '!lacp',
'lag_interfaces', 'lag_interfaces',

View file

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

View file

@ -0,0 +1,269 @@
# -*- 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 eos_l3_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.eos.facts.facts import Facts
class L3_interfaces(ConfigBase):
"""
The eos_l3_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'l3_interfaces',
]
def get_l3_interfaces_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
l3_interfaces_facts = facts['ansible_network_resources'].get('l3_interfaces')
if not l3_interfaces_facts:
return []
return l3_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
commands.extend(self.set_config(existing_l3_interfaces_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
result['before'] = existing_l3_interfaces_facts
if result['changed']:
result['after'] = changed_l3_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_l3_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_l3_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
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 == 'merged':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
@staticmethod
def _state_replaced(want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for interface in want:
for extant in have:
if interface["name"] == extant["name"]:
break
else:
extant = dict(name=interface["name"])
intf_commands = set_interface(interface, extant)
intf_commands.extend(clear_interface(interface, extant))
if intf_commands:
commands.append("interface {0}".format(interface["name"]))
commands.extend(intf_commands)
return commands
@staticmethod
def _state_overridden(want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for extant in have:
for interface in want:
if extant["name"] == interface["name"]:
break
else:
interface = dict(name=extant["name"])
if interface.get("ipv4"):
for ipv4 in interface["ipv4"]:
if ipv4["secondary"] is None:
del ipv4["secondary"]
intf_commands = set_interface(interface, extant)
intf_commands.extend(clear_interface(interface, extant))
if intf_commands:
commands.append("interface {0}".format(interface["name"]))
commands.extend(intf_commands)
return commands
@staticmethod
def _state_merged(want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for interface in want:
for extant in have:
if extant["name"] == interface["name"]:
break
else:
extant = dict(name=interface["name"])
intf_commands = set_interface(interface, extant)
if intf_commands:
commands.append("interface {0}".format(interface["name"]))
commands.extend(intf_commands)
return commands
@staticmethod
def _state_deleted(want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
for interface in want:
for extant in have:
if extant["name"] == interface["name"]:
break
else:
continue
# Clearing all args, send empty dictionary
interface = dict(name=interface["name"])
intf_commands = clear_interface(interface, extant)
if intf_commands:
commands.append("interface {0}".format(interface["name"]))
commands.extend(intf_commands)
return commands
def set_interface(want, have):
commands = []
want_ipv4 = set(tuple(address.items()) for address in want.get("ipv4") or [])
have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or [])
for address in want_ipv4 - have_ipv4:
address = dict(address)
if "secondary" in address and not address["secondary"]:
del address["secondary"]
if tuple(address.items()) in have_ipv4:
continue
address_cmd = "ip address {0}".format(address["address"])
if address.get("secondary"):
address_cmd += " secondary"
commands.append(address_cmd)
want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or [])
have_ipv6 = set(tuple(address.items()) for address in have.get("ipv6") or [])
for address in want_ipv6 - have_ipv6:
address = dict(address)
commands.append("ipv6 address {0}".format(address["address"]))
return commands
def clear_interface(want, have):
commands = []
want_ipv4 = set(tuple(address.items()) for address in want.get("ipv4") or [])
have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or [])
if not want_ipv4:
commands.append("no ip address")
else:
for address in (have_ipv4 - want_ipv4):
address = dict(address)
if "secondary" not in address:
address["secondary"] = False
if tuple(address.items()) in want_ipv4:
continue
address_cmd = "no ip address"
if address.get("secondary"):
address_cmd += " {0} secondary".format(address["address"])
commands.append(address_cmd)
if "secondary" not in address:
# Removing non-secondary removes all other interfaces
break
want_ipv6 = set(tuple(address.items()) for address in want.get("ipv6") or [])
have_ipv6 = set(tuple(address.items()) for address in have.get("ipv6") or [])
for address in have_ipv6 - want_ipv6:
address = dict(address)
commands.append("no ipv6 address {0}".format(address["address"]))
return commands

View file

@ -14,6 +14,7 @@ from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.eos.argspec.facts.facts import FactsArgs from ansible.module_utils.network.eos.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.eos.facts.interfaces.interfaces import InterfacesFacts from ansible.module_utils.network.eos.facts.interfaces.interfaces import InterfacesFacts
from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts from ansible.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
from ansible.module_utils.network.eos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
from ansible.module_utils.network.eos.facts.lacp.lacp import LacpFacts from ansible.module_utils.network.eos.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.eos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts from ansible.module_utils.network.eos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts
@ -29,6 +30,7 @@ FACT_LEGACY_SUBSETS = dict(
FACT_RESOURCE_SUBSETS = dict( FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts, interfaces=InterfacesFacts,
l2_interfaces=L2_interfacesFacts, l2_interfaces=L2_interfacesFacts,
l3_interfaces=L3_interfacesFacts,
lacp=LacpFacts, lacp=LacpFacts,
lag_interfaces=Lag_interfacesFacts, lag_interfaces=Lag_interfacesFacts,
vlans=VlansFacts, vlans=VlansFacts,

View file

@ -0,0 +1,101 @@
# -*- 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 eos l3_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from copy import deepcopy
import re
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.eos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs
class L3_interfacesFacts(object):
""" The eos 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 configuration
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get('show running-config | section ^interface')
# split the config into instances of the resource
resource_delim = 'interface'
find_pattern = r'(?:^|\n)%s.*?(?=(?:^|\n)%s|$)' % (resource_delim, resource_delim)
resources = [p.strip() for p in re.findall(find_pattern, data, re.DOTALL)]
objs = []
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj:
objs.append(obj)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['l3_interfaces'] = [utils.remove_empties(cfg) for cfg in 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)
config['name'] = utils.parse_conf_arg(conf, 'interface')
matches = re.findall(r'.*ip address (.+)$', conf, re.MULTILINE)
if matches:
config["ipv4"] = []
for match in matches:
address, dummy, remainder = match.partition(" ")
ipv4 = {"address": address}
if remainder == "secondary":
ipv4["secondary"] = True
config['ipv4'].append(ipv4)
matches = re.findall(r'.*ipv6 address (.+)$', conf, re.MULTILINE)
if matches:
config["ipv6"] = []
for match in matches:
address, dummy, remainder = match.partition(" ")
ipv6 = {"address": address}
config['ipv6'].append(ipv6)
return utils.remove_empties(config)

View file

@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['deprecated'],
'supported_by': 'network'} 'supported_by': 'network'}
DOCUMENTATION = """ DOCUMENTATION = """
@ -21,6 +21,10 @@ short_description: Manage L3 interfaces on Arista EOS network devices.
description: description:
- This module provides declarative management of L3 interfaces - This module provides declarative management of L3 interfaces
on Arista EOS network devices. on Arista EOS network devices.
deprecated:
removed_in: "2.13"
alternative: eos_l3_interfaces
why: Updated modules released with more functionality
notes: notes:
- Tested against EOS 4.15 - Tested against EOS 4.15
options: options:

View file

@ -50,7 +50,8 @@ options:
type: list type: list
choices: [ choices: [
'all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces', 'all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces',
'lacp', '!lacp', 'lag_interfaces', '!lag_interfaces', 'vlans', '!vlans', 'l3_interfaces', '!l3_interfaces', 'lacp', '!lacp', 'lag_interfaces', '!lag_interfaces',
'vlans', '!vlans',
] ]
version_added: "2.9" version_added: "2.9"
""" """

View file

@ -0,0 +1,305 @@
#!/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 eos_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: eos_l3_interfaces
version_added: 2.9
short_description: 'Manages L3 interface attributes of Arista EOS devices.'
description: 'This module provides declarative management of Layer 3 interfaces on Arista EOS devices.'
author: Nathaniel Case (@qalthos)
notes:
- 'Tested against vEOS v4.20.x'
- This module works with connection C(network_cli). See the
L(EOS Platform Options,../network/user_guide/platform_eos.html).
options:
config:
description: A dictionary of Layer 3 interface options
type: list
elements: dict
suboptions:
name:
description:
- Full name of the interface, i.e. Ethernet1.
type: str
required: True
ipv4:
description:
- List of IPv4 addresses to be set for the Layer 3 interface mentioned in I(name) option.
type: list
elements: dict
suboptions:
address:
description:
- IPv4 address to be set in the format <ipv4 address>/<mask>
eg. 192.0.2.1/24, or C(dhcp) to query DHCP for an IP address.
type: str
secondary:
description:
- Whether or not this address is a secondary address.
type: bool
default: False
ipv6:
description:
- List of IPv6 addresses to be set for the Layer 3 interface mentioned in I(name) option.
type: list
elements: dict
suboptions:
address:
description:
- IPv6 address to be set in the address format is <ipv6 address>/<mask>
eg. 2001:db8:2201:1::1/64 or C(auto-config) to use SLAAC to chose an address.
type: str
state:
description:
- The state the configuration should be left in
type: str
choices:
- merged
- replaced
- overridden
- deleted
default: merged
"""
EXAMPLES = """
---
# Using deleted
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ip address 192.0.2.12/24
# !
# interface Ethernet2
# ipv6 address 2001:db8::1/64
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
- name: Delete L3 attributes of given interfaces.
eos_l3_interfaces:
config:
- name: Ethernet1
- name: Ethernet2
state: deleted
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# !
# interface Ethernet2
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# Using merged
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ip address 192.0.2.12/24
# !
# interface Ethernet2
# ipv6 address 2001:db8::1/64
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
- name: Merge provided configuration with device configuration.
eos_l3_interfaces:
config:
- name: Ethernet1
ipv4:
address: 198.51.100.14/24
- name: Ethernet2
ipv4:
address: 203.0.113.27/24
state: merged
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ip address 198.51.100.14/24
# !
# interface Ethernet2
# ip address 203.0.113.27/24
# ipv6 address 2001:db8::1/64
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# Using overridden
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ip address 192.0.2.12/24
# !
# interface Ethernet2
# ipv6 address 2001:db8::1/64
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
- name: Override device configuration of all L2 interfaces on device with provided configuration.
eos_l3_interfaces:
config:
- name: Ethernet1
ipv6:
address: 2001:db8:feed::1/96
- name: Management1
ipv4:
address: dhcp
ipv6: auto-config
state: overridden
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ipv6 address 2001:db8:feed::1/96
# !
# interface Ethernet2
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
# Using replaced
# Before state:
# -------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ip address 192.0.2.12/24
# !
# interface Ethernet2
# ipv6 address 2001:db8::1/64
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
- name: Replace device configuration of specified L2 interfaces with provided configuration.
eos_l3_interfaces:
config:
- name: Ethernet2
ipv4:
address: 203.0.113.27/24
state: replaced
# After state:
# ------------
#
# veos#show running-config | section interface
# interface Ethernet1
# ip address 192.0.2.12/24
# !
# interface Ethernet2
# ip address 203.0.113.27/24
# !
# interface Management1
# ip address dhcp
# ipv6 address auto-config
"""
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 Ethernet2', 'ip address 192.0.2.12/24']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs
from ansible.module_utils.network.eos.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()

View file

@ -0,0 +1,2 @@
---
testcase: "*"

View file

@ -0,0 +1,2 @@
dependencies:
- prepare_eos_tests

View file

@ -0,0 +1,16 @@
---
- name: collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
register: test_cases
delegate_to: localhost
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=network_cli)
include: "{{ test_case_to_run }} ansible_connection=network_cli"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

View file

@ -0,0 +1,42 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
- name: Ethernet2
other_config:
- name: Management1
ipv4:
- address: dhcp
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- name: Delete EOS L3 interfaces as in given arguments.
eos_l3_interfaces:
config: "{{ config }}"
state: deleted
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.after) == []"
become: yes
- set_fact:
expected_config: "{{ config }} + {{ other_config }}"
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,55 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
ipv4:
- address: 198.51.100.14/24
- name: Ethernet2
ipv4:
- address: 203.0.113.227/31
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- name: Merge provided configuration with device configuration.
eos_l3_interfaces:
config: "{{ config }}"
state: merged
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.after) == []"
become: yes
- set_fact:
expected_config:
- name: Ethernet1
ipv4:
- address: 198.51.100.14/24
- address: 203.0.113.27/31
secondary: true
- name: Ethernet2
ipv4:
- address: 203.0.113.227/31
ipv6:
- address: 2001:db8::1/64
- name: Management1
ipv4:
- address: dhcp
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,45 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet1
ipv6:
- address: 2001:db8:feed::1/96
- name: Ethernet2
ipv6:
- address: 2001:db8::1/64
- name: Management1
ipv4:
- address: dhcp
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- name: Override device configuration of all L3 interfaces on device with provided configuration.
eos_l3_interfaces:
config: "{{ config }}"
state: overridden
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.after) == []"
become: yes
- set_fact:
expected_config: "{{ config }}"
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,48 @@
---
- include_tasks: reset_config.yml
- set_fact:
config:
- name: Ethernet2
ipv4:
- address: 203.0.113.205/31
other_config:
- name: Ethernet1
ipv4:
- address: 192.0.2.12/24
- address: 203.0.113.27/31
secondary: true
- name: Management1
ipv4:
- address: dhcp
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- name: Replace device configuration of specified L3 interfaces with provided configuration.
eos_l3_interfaces:
config: "{{ config }}"
state: replaced
register: result
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.before) == []"
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(result.after) == []"
become: yes
- set_fact:
expected_config: "{{ config }} + {{ other_config }}"
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(expected_config) == []"

View file

@ -0,0 +1,37 @@
---
- name: Reset state
cli_config:
config: |
interface Ethernet1
ip address 192.0.2.12/24
ip address 203.0.113.27/31 secondary
no ipv6 address
interface Ethernet2
no ip address
ipv6 address 2001:db8::1/64
interface Management1
ip address dhcp
no ipv6 address
become: yes
- eos_facts:
gather_network_resources: l3_interfaces
become: yes
- set_fact:
expected_config:
- name: Ethernet1
ipv4:
- address: 192.0.2.12/24
- address: 203.0.113.27/31
secondary: true
- name: Ethernet2
ipv6:
- address: 2001:db8::1/64
- name: Management1
ipv4:
- address: dhcp
- assert:
that:
- "ansible_facts.network_resources.l3_interfaces|symmetric_difference(expected_config) == []"

View file

@ -3580,11 +3580,11 @@ lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E326
lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E337 lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E337
lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E338 lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E338
lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E340 lib/ansible/modules/network/eos/_eos_l2_interface.py validate-modules:E340
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E322 lib/ansible/modules/network/eos/_eos_l3_interface.py validate-modules:E322
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E326 lib/ansible/modules/network/eos/_eos_l3_interface.py validate-modules:E326
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E337 lib/ansible/modules/network/eos/_eos_l3_interface.py validate-modules:E337
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E338 lib/ansible/modules/network/eos/_eos_l3_interface.py validate-modules:E338
lib/ansible/modules/network/eos/eos_l3_interface.py validate-modules:E340 lib/ansible/modules/network/eos/_eos_l3_interface.py validate-modules:E340
lib/ansible/modules/network/eos/_eos_linkagg.py validate-modules:E322 lib/ansible/modules/network/eos/_eos_linkagg.py validate-modules:E322
lib/ansible/modules/network/eos/_eos_linkagg.py validate-modules:E326 lib/ansible/modules/network/eos/_eos_linkagg.py validate-modules:E326
lib/ansible/modules/network/eos/_eos_linkagg.py validate-modules:E337 lib/ansible/modules/network/eos/_eos_linkagg.py validate-modules:E337