mirror of https://github.com/ansible/ansible.git
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 updatedpull/61093/head
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…
Reference in New Issue