Resource module for ios_lacp (#60884)

* ios lacp resource

Signed-off-by: Sumit Jaiswal <sjaiswal@redhat.com>
This commit is contained in:
Sumit Jaiswal 2019-08-20 12:05:14 +05:30 committed by GitHub
parent 5027416fb5
commit 642e54f958
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 714 additions and 4 deletions

View file

@ -29,6 +29,8 @@ class FactsArgs(object):
'!vlans',
'lag_interfaces',
'!lag_interfaces',
'lacp',
'!lacp',
]
argument_spec = {

View file

@ -0,0 +1,47 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The arg spec for the ios_lacp module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class LacpArgs(object):
"""The arg spec for the ios_lacp module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {'options': {'system': {'options': {'priority': {'required': True, 'type': 'int'}},
'type': 'dict'}
}, 'type': 'dict'
},
'state': {'choices': ['merged', 'replaced', 'deleted'], 'default': 'merged',
'type': 'str'}
}

View file

@ -0,0 +1,186 @@
#
# -*- 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 ios_lacp class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.ios.facts.facts import Facts
from ansible.module_utils.network.ios.utils.utils import dict_to_set
class Lacp(ConfigBase):
"""
The ios_lacp class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'lacp',
]
def __init__(self, module):
super(Lacp, self).__init__(module)
def get_lacp_facts(self):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
lacp_facts = facts['ansible_network_resources'].get('lacp')
if not lacp_facts:
return []
return lacp_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
existing_lacp_facts = self.get_lacp_facts()
commands.extend(self.set_config(existing_lacp_facts))
if commands:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
result['commands'] = commands
changed_lacp_facts = self.get_lacp_facts()
result['before'] = existing_lacp_facts
if result['changed']:
result['after'] = changed_lacp_facts
result['warnings'] = warnings
return result
def set_config(self, existing_lacp_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['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']
if state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
commands.extend(self._set_config(want, have))
return commands
def _state_merged(self, want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
commands.extend(self._set_config(want, have))
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
commands.extend(self._clear_config(have))
else:
commands.extend(self._clear_config(have))
return commands
def _remove_command_from_config_list(self, cmd, commands):
commands.append('no %s' % cmd)
return commands
def _add_command_to_config_list(self, cmd, commands):
if cmd not in commands:
commands.append(cmd)
def _set_config(self, want, have):
# Set the interface config based on the want and have config
commands = []
want_dict = dict_to_set(want)
have_dict = dict_to_set(have)
diff = want_dict - have_dict
if diff:
cmd = 'lacp system-priority {0}'.format(want.get('system').get('priority'))
self._add_command_to_config_list(cmd, commands)
return commands
def _clear_config(self, have):
# Delete the interface config based on the want and have config
commands = []
if have.get('system').get('priority') and have.get('system').get('priority') != 32768:
cmd = 'lacp system-priority'
self._remove_command_from_config_list(cmd, commands)
return commands

View file

@ -19,6 +19,7 @@ from ansible.module_utils.network.ios.facts.interfaces.interfaces import Interfa
from ansible.module_utils.network.ios.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts
from ansible.module_utils.network.ios.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.ios.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.ios.facts.lacp.lacp import LacpFacts
from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config
@ -34,6 +35,7 @@ FACT_RESOURCE_SUBSETS = dict(
l2_interfaces=L2_InterfacesFacts,
vlans=VlansFacts,
lag_interfaces=Lag_interfacesFacts,
lacp=LacpFacts,
)

View file

@ -0,0 +1,83 @@
#
# -*- 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 ios lacp fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.ios.argspec.lacp.lacp import LacpArgs
class LacpFacts(object):
""" The ios 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 connection:
pass
if not data:
data = connection.get('show lacp sys-id')
obj = {}
if data:
lacp_obj = self.render_config(self.generated_spec, data)
if lacp_obj:
obj = lacp_obj
ansible_facts['ansible_network_resources'].pop('lacp', None)
facts = {}
params = utils.validate_config(self.argument_spec, {'config': obj})
facts['lacp'] = utils.remove_empties(params['config'])
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, spec, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
config['system']['priority'] = int(conf.split(',')[0])
return utils.remove_empties(config)

View file

@ -39,7 +39,7 @@ def dict_to_set(sample_dict):
for key, value in iteritems(each):
if isinstance(value, list):
each[key] = tuple(value)
li.append(tuple(each.items()))
li.append(tuple(iteritems(each)))
v = tuple(li)
else:
v = tuple(v)
@ -48,10 +48,10 @@ def dict_to_set(sample_dict):
for key, value in iteritems(v):
if isinstance(value, list):
v[key] = tuple(value)
li.extend(tuple(v.items()))
li.extend(tuple(iteritems(v)))
v = tuple(li)
test_dict.update({k: v})
return_set = set(tuple(test_dict.items()))
return_set = set(tuple(iteritems(test_dict)))
return return_set

View file

@ -55,7 +55,7 @@ options:
all and the resources like interfaces, vlans etc.
Can specify a list of values to include a larger subset.
choices: ['all', '!all', 'interfaces', '!interfaces', 'l2_interfaces', '!l2_interfaces', 'vlans', '!vlans',
'lag_interfaces', '!lag_interfaces']
'lag_interfaces', '!lag_interfaces', 'lacp', '!lacp']
version_added: "2.9"
"""

View file

@ -0,0 +1,181 @@
#!/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 ios_lacp
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: ios_lacp
version_added: 2.9
short_description: Manage Global Link Aggregation Control Protocol (LACP) on Cisco IOS devices.
description: This module provides declarative management of Global LACP on Cisco IOS network devices.
author: Sumit Jaiswal (@justjais)
notes:
- Tested against Cisco IOSv Version 15.2 on VIRL
- This module works with connection C(network_cli),
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
options:
config:
description: The provided configurations.
type: dict
suboptions:
system:
description: This option sets the default system parameters for LACP.
type: dict
suboptions:
priority:
description:
- LACP priority for the system.
- Refer to vendor documentation for valid values.
type: int
required: True
state:
description:
- The state the configuration should be left in
type: str
choices:
- merged
- replaced
- deleted
default: merged
"""
EXAMPLES = """
# Using merged
#
# Before state:
# -------------
#
# vios#show lacp sys-id
# 32768, 5e00.0000.8000
- name: Merge provided configuration with device configuration
ios_lacp:
config:
system:
priority: 123
state: merged
# After state:
# ------------
#
# vios#show lacp sys-id
# 123, 5e00.0000.8000
# Using replaced
#
# Before state:
# -------------
#
# vios#show lacp sys-id
# 500, 5e00.0000.8000
- name: Replaces Global LACP configuration
ios_lacp:
config:
system:
priority: 123
state: replaced
# After state:
# ------------
#
# vios#show lacp sys-id
# 123, 5e00.0000.8000
# Using Deleted
#
# Before state:
# -------------
#
# vios#show lacp sys-id
# 500, 5e00.0000.8000
- name: Delete Global LACP attribute
ios_lacp:
state: deleted
# After state:
# -------------
#
# vios#show lacp sys-id
# 32768, 5e00.0000.8000
"""
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: ['lacp system-priority 10']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.ios.argspec.lacp.lacp import LacpArgs
from ansible.module_utils.network.ios.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()

View file

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

View file

@ -0,0 +1 @@
dependencies: []

View file

@ -0,0 +1,24 @@
---
- name: Collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: Set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
delegate_to: localhost
- name: Get the IOS version
ios_facts:
gather_subset: all
- name: Run test case (connection=network_cli)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

View file

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

View file

@ -0,0 +1,8 @@
---
- name: Populate Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
lacp system-priority 500
when: ansible_net_version != "15.6(2)T"

View file

@ -0,0 +1,8 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
no lacp system-priority
when: ansible_net_version != "15.6(2)T"

View file

@ -0,0 +1,41 @@
---
- debug:
msg: "Start Deleted integration state for ios_lacp ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete configured System LACP
ios_lacp: &deleted
state: deleted
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ deleted['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ deleted['before'] == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ deleted['before'] == result['before'] }}"
- name: Delete configured System LACP (IDEMPOTENT)
ios_lacp: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
when: ansible_net_version != "15.6(2)T"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,42 @@
---
- debug:
msg: "START Merged ios_lacp state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge provided configuration system LACP
ios_lacp: &merged
config:
system:
priority: 10
state: merged
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ merged['before'] == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ merged['before'] == result['before'] }}"
- name: Merge provided configuration system LACP (IDEMPOTENT)
ios_lacp: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
when: ansible_net_version != "15.6(2)T"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,44 @@
---
- debug:
msg: "START Replaced ios_lacp state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Replaces system LACP configuration provided LACP configuration
ios_lacp: &replaced
config:
system:
priority: 20
state: replaced
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ replaced['commands'] | symmetric_difference(result['commands']) | length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ replaced['before'] == result['before'] }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['before'] == result['before'] }}"
- name: Replaces system LACP configuration provided LACP configuration (IDEMPOTENT)
ios_lacp: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
when: ansible_net_version != "15.6(2)T"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,36 @@
---
merged:
before:
system:
priority: 32768
commands:
- "lacp system-priority 10"
after:
- system:
priority: 10
replaced:
before:
system:
priority: 500
commands:
- "lacp system-priority 20"
after:
system:
priority: 20
deleted:
before:
system:
priority: 500
commands:
- "no lacp system-priority"
after:
system:
priority: 32768