icx: new module icx_linkagg (#59967)

* new module

* new module

* new terminal

* new terminal

* new cliconf

* new cliconf

* cliconf

* cliconf

* icx cliconf

* icx cliconf

* icx_cliconf

* icx test units module

* icx test units module

* icx units module

* icx units module

* icx banner unit test

* icx banner unit test

* PR changes resolved

* changes resolved

* Changes Resolved

* check_running_config changes resolved

* added notes

* added notes

* removed icx rst

* new changes

* new changes

* deleted icx rst

* icx .rst

* icx .rst

* modified platform_index.rst

* modified platform_index.rst

* modified platform_index.rst

* modified platform_index.rst

* changes resolved

* changes resolved

* PR comments resolved

* PR comments resolved

* Update platform_index.rst

PR comment resolved

* Update platform_index.rst

PR comment resolved

* new module icx_linkagg

* bug fixes

* Fixing bot errors

* Trying to fix aggregate document error

* Added options under aggregate in documentation

* Fixed bot error

* Fixed documentation error

* Fix bot error

* notes updated
pull/61093/head
sushma-alethea 5 years ago committed by Ganesh Nalawade
parent 20ec927280
commit 7e1a347695

@ -0,0 +1,328 @@
#!/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': 'community'}
DOCUMENTATION = """
---
module: icx_linkagg
version_added: "2.9"
author: "Ruckus Wireless (@Commscope)"
short_description: Manage link aggregation groups on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of link aggregation groups
on Ruckus ICX network devices.
notes:
- Tested against ICX 10.1.
- For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html).
options:
group:
description:
- Channel-group number for the port-channel
Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID
type: int
name:
description:
- Name of the LAG
type: str
mode:
description:
- Mode of the link aggregation group.
type: str
choices: ['dynamic', 'static']
members:
description:
- List of port members or ranges of the link aggregation group.
type: list
state:
description:
- State of the link aggregation group.
type: str
default: present
choices: ['present', 'absent']
check_running_config:
description:
- Check running configuration. This can be set as environment variable.
Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter.
type: bool
default: yes
aggregate:
description:
- List of link aggregation definitions.
type: list
suboptions:
group:
description:
- Channel-group number for the port-channel
Link aggregation group. Range 1-255 or set to 'auto' to auto-generates a LAG ID
type: int
name:
description:
- Name of the LAG
type: str
mode:
description:
- Mode of the link aggregation group.
type: str
choices: ['dynamic', 'static']
members:
description:
- List of port members or ranges of the link aggregation group.
type: list
state:
description:
- State of the link aggregation group.
type: str
choices: ['present', 'absent']
check_running_config:
description:
- Check running configuration. This can be set as environment variable.
Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter.
type: bool
purge:
description:
- Purge links not defined in the I(aggregate) parameter.
type: bool
default: no
"""
EXAMPLES = """
- name: create static link aggregation group
icx_linkagg:
group: 10
mode: static
name: LAG1
- name: create link aggregation group with auto id
icx_linkagg:
group: auto
mode: dynamic
name: LAG2
- name: delete link aggregation group
icx_linkagg:
group: 10
state: absent
- name: Set members to LAG
icx_linkagg:
group: 200
mode: static
members:
- ethernet 1/1/1 to 1/1/6
- ethernet 1/1/10
- name: Remove links other then LAG id 100 and 3 using purge
icx_linkagg:
aggregate:
- { group: 3}
- { group: 100}
purge: true
"""
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:
- lag LAG1 dynamic id 11
- ports ethernet 1/1/1 to 1/1/6
- no ports ethernet 1/1/10
- no lag LAG1 dynamic id 12
"""
import re
from copy import deepcopy
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import ConnectionError, exec_command
from ansible.module_utils.network.icx.icx import run_commands, get_config, load_config
from ansible.module_utils.network.common.config import CustomNetworkConfig
from ansible.module_utils.network.common.utils import remove_default_spec
def range_to_members(ranges, prefix=""):
match = re.findall(r'(ethe[a-z]* [0-9]/[0-9]/[0-9]+)( to [0-9]/[0-9]/[0-9]+)?', ranges)
members = list()
for m in match:
start, end = m
if(end == ''):
start = start.replace("ethe ", "ethernet ")
members.append("%s%s" % (prefix, start))
else:
start_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', start)
end_tmp = re.search(r'[0-9]/[0-9]/([0-9]+)', end)
start = int(start_tmp.group(1))
end = int(end_tmp.group(1)) + 1
for num in range(start, end):
members.append("%sethernet 1/1/%s" % (prefix, num))
return members
def map_config_to_obj(module):
objs = dict()
compare = module.params['check_running_config']
config = get_config(module, None, compare=compare)
obj = None
for line in config.split('\n'):
l = line.strip()
match1 = re.search(r'lag (\S+) (\S+) id (\S+)', l, re.M)
if match1:
obj = dict()
obj['name'] = match1.group(1)
obj['mode'] = match1.group(2)
obj['group'] = match1.group(3)
obj['state'] = 'present'
obj['members'] = list()
else:
match2 = re.search(r'ports .*', l, re.M)
if match2 and obj is not None:
obj['members'].extend(range_to_members(match2.group(0)))
elif obj is not None:
objs[obj['group']] = obj
obj = None
return objs
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'])
obj.append(d)
else:
obj.append({
'group': str(module.params['group']),
'mode': module.params['mode'],
'members': module.params['members'],
'state': module.params['state'],
'name': module.params['name']
})
return obj
def search_obj_in_list(group, lst):
for o in lst:
if o['group'] == group:
return o
return None
def is_member(member, lst):
for li in lst:
ml = range_to_members(li)
if member in ml:
return True
return False
def map_obj_to_commands(updates, module):
commands = list()
want, have = updates
purge = module.params['purge']
for w in want:
if have == {} and w['state'] == 'absent':
commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group']))
elif have.get(w['group']) is None:
commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group']))
if(w.get('members') is not None and w['state'] == 'present'):
for m in w['members']:
commands.append("ports %s" % (m))
if w['state'] == 'present':
commands.append("exit")
else:
commands.append("%slag %s %s id %s" % ('no ' if w['state'] == 'absent' else '', w['name'], w['mode'], w['group']))
if(w.get('members') is not None and w['state'] == 'present'):
for m in have[w['group']]['members']:
if not is_member(m, w['members']):
commands.append("no ports %s" % (m))
for m in w['members']:
sm = range_to_members(ranges=m)
for smm in sm:
if smm not in have[w['group']]['members']:
commands.append("ports %s" % (smm))
if w['state'] == 'present':
commands.append("exit")
if purge:
for h in have:
if search_obj_in_list(have[h]['group'], want) is None:
commands.append("no lag %s %s id %s" % (have[h]['name'], have[h]['mode'], have[h]['group']))
return commands
def main():
element_spec = dict(
group=dict(type='int'),
name=dict(type='str'),
mode=dict(choices=['dynamic', 'static']),
members=dict(type='list'),
state=dict(default='present',
choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['group'] = dict(required=True, type='int')
required_one_of = [['group', 'aggregate']]
required_together = [['name', 'group']]
mutually_exclusive = [['group', 'aggregate']]
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec, required_together=required_together),
purge=dict(default=False, type='bool')
)
argument_spec.update(element_spec)
module = AnsibleModule(argument_spec=argument_spec,
required_one_of=required_one_of,
required_together=required_together,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
warnings = list()
result = {'changed': False}
exec_command(module, 'skip')
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:
load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,7 @@
lag LAG1 dynamic id 100
ports ethe 1/1/3 ethe 1/1/5 to 1/1/8
disable ethe 1/1/3 ethe 1/1/5 to 1/1/8
!
lag LAG2 dynamic id 200
ports ethe 1/1/11 ethe 1/1/13 ethe 1/1/15
disable ethe 1/1/11 ethe 1/1/13 ethe 1/1/15

@ -0,0 +1,123 @@
# Copyright: (c) 2019, 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
from units.compat.mock import patch
from ansible.modules.network.icx import icx_linkagg
from units.modules.utils import set_module_args
from .icx_module import TestICXModule, load_fixture
class TestICXLinkaggModule(TestICXModule):
module = icx_linkagg
def setUp(self):
super(TestICXLinkaggModule, self).setUp()
self.mock_get_config = patch('ansible.modules.network.icx.icx_linkagg.get_config')
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.modules.network.icx.icx_linkagg.load_config')
self.load_config = self.mock_load_config.start()
self.mock_exec_command = patch('ansible.modules.network.icx.icx_linkagg.exec_command')
self.exec_command = self.mock_exec_command.start()
self.set_running_config()
def tearDown(self):
super(TestICXLinkaggModule, self).tearDown()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_exec_command.stop()
def load_fixtures(self, commands=None):
compares = None
def load_from_file(*args, **kwargs):
module = args
for arg in args:
if arg.params['check_running_config'] is True:
return load_fixture('lag_running_config.txt').strip()
else:
return ''
self.get_config.side_effect = load_from_file
self.load_config.return_value = None
def test_icx_linkage_create_new_LAG(self):
set_module_args(dict(group=10, name="LAG3", mode='static', members=['ethernet 1/1/4 to ethernet 1/1/7']))
if not self.ENV_ICX_USE_DIFF:
commands = ['lag LAG3 static id 10', 'ports ethernet 1/1/4 to ethernet 1/1/7', 'exit']
self.execute_module(commands=commands, changed=True)
else:
commands = ['lag LAG3 static id 10', 'ports ethernet 1/1/4 to ethernet 1/1/7', 'exit']
self.execute_module(commands=commands, changed=True)
def test_icx_linkage_modify_LAG(self):
set_module_args(dict(group=100, name="LAG1", mode='dynamic', members=['ethernet 1/1/4 to 1/1/7']))
if not self.ENV_ICX_USE_DIFF:
commands = [
'lag LAG1 dynamic id 100',
'ports ethernet 1/1/4 to 1/1/7',
'exit'
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'lag LAG1 dynamic id 100',
'no ports ethernet 1/1/3',
'no ports ethernet 1/1/8',
'ports ethernet 1/1/4',
'exit'
]
self.execute_module(commands=commands, changed=True)
def test_icx_linkage_modify_LAG_compare(self):
set_module_args(dict(group=100, name="LAG1", mode='dynamic', members=['ethernet 1/1/4 to 1/1/7'], check_running_config=True))
if self.get_running_config(compare=True):
if not self.ENV_ICX_USE_DIFF:
commands = [
'lag LAG1 dynamic id 100',
'no ports ethernet 1/1/3',
'no ports ethernet 1/1/8',
'ports ethernet 1/1/4',
'exit'
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'lag LAG1 dynamic id 100',
'no ports ethernet 1/1/3',
'no ports ethernet 1/1/8',
'ports ethernet 1/1/4',
'exit'
]
self.execute_module(commands=commands, changed=True)
def test_icx_linkage_purge_LAG(self):
set_module_args(dict(aggregate=[dict(group=100, name="LAG1", mode='dynamic')], purge=True))
if not self.ENV_ICX_USE_DIFF:
commands = [
'lag LAG1 dynamic id 100',
'exit'
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'lag LAG1 dynamic id 100',
'exit',
'no lag LAG2 dynamic id 200'
]
self.execute_module(commands=commands, changed=True)
def test_icx_linkage_remove_LAG(self):
set_module_args(dict(group=100, name="LAG1", mode='dynamic', members=['ethernet 1/1/4 to 1/1/7'], state='absent'))
if not self.ENV_ICX_USE_DIFF:
commands = [
'no lag LAG1 dynamic id 100'
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'no lag LAG1 dynamic id 100'
]
self.execute_module(commands=commands, changed=True)
Loading…
Cancel
Save