icx: new module icx_system (#60024)

* new module

* new terminal

* new terminal

* new cliconf

* cliconf

* icx cliconf

* icx_cliconf

* icx test units module

* icx units module

* icx banner unit test

* PR changes resolved

* changes resolved

* Changes Resolved

* check_running_config changes resolved

* added notes

* removed icx rst

* new changes

* deleted icx rst

* icx .rst

* modified platform_index.rst

* modified platform_index.rst

* changes resolved

* PR comments resolved

* Update platform_index.rst

PR comment resolved

* test

* Revert "test"

This reverts commit 99b72c6614.

* new module icx_system

* new changes

* new changes

* new changes

* Fixed bot bugs

* notes updated
This commit is contained in:
sushma-alethea 2019-08-27 08:55:46 +05:30 committed by Ganesh Nalawade
parent d2429cbd6a
commit fb1094f38d
3 changed files with 642 additions and 0 deletions

View file

@ -0,0 +1,471 @@
#!/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_system
version_added: "2.9"
author: "Ruckus Wireless (@Commscope)"
short_description: Manage the system attributes on Ruckus ICX 7000 series switches
description:
- This module provides declarative management of node system attributes
on Ruckus ICX 7000 series switches. It provides an option to configure host system
parameters or remove those parameters from the device active
configuration.
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:
hostname:
description:
- Configure the device hostname parameter. This option takes an ASCII string value.
type: str
domain_name:
description:
- Configure the IP domain name on the remote device to the provided value.
Value should be in the dotted name form and
will be appended to the hostname to create a fully-qualified domain name.
type: list
domain_search:
description:
- Provides the list of domain names to
append to the hostname for the purpose of doing name resolution.
This argument accepts a list of names and will be reconciled
with the current active configuration on the running node.
type: list
name_servers:
description:
- List of DNS name servers by IP address to use to perform name resolution
lookups.
type: list
aaa_servers:
description:
- Configures radius/tacacs server
type: list
suboptions:
type:
description:
- specifiy the type of the server
type: str
choices: ['radius','tacacs']
hostname:
description:
- Configures the host name of the RADIUS server
type: str
auth_port_type:
description:
- specifies the type of the authentication port
type: str
choices: ['auth-port']
auth_port_num:
description:
- Configures the authentication UDP port. The default value is 1812.
type: str
acct_port_num:
description:
- Configures the accounting UDP port. The default value is 1813.
type: str
acct_type:
description:
- Usage of the accounting port.
type: str
choices: ['accounting-only', 'authentication-only','authorization-only', default]
auth_key:
description:
- Configure the key for the server
type: str
auth_key_type:
description:
- List of authentication level specified in the choices
type: list
choices: ['dot1x','mac-auth','web-auth']
state:
description:
- State of the configuration
values in the device's current active configuration. When set
to I(present), the values should be configured in the device active
configuration and when set to I(absent) the values should not be
in the device active configuration
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
"""
EXAMPLES = """
- name: configure hostname and domain name
icx_system:
hostname: icx
domain_search:
- ansible.com
- redhat.com
- ruckus.com
- name: configure radius server of type auth-port
icx_system:
aaa_servers:
- type: radius
hostname: radius-server
auth_port_type: auth-port
auth_port_num: 1821
acct_port_num: 1321
acct_type: accounting-only
auth_key: abc
auth_key_type:
- dot1x
- mac-auth
- name: configure tacacs server
icx_system:
aaa_servers:
- type: tacacs
hostname: tacacs-server
auth_port_type: auth-port
auth_port_num: 1821
acct_port_num: 1321
acct_type: accounting-only
auth_key: xyz
- name: configure name servers
icx_system:
name_servers:
- 8.8.8.8
- 8.8.4.4
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- hostname icx
- ip domain name test.example.com
- radius-server host 172.16.10.12 auth-port 2083 acct-port 1850 default key abc dot1x mac-auth
- tacacs-server host 10.2.3.4 auth-port 4058 authorization-only key xyz
"""
import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.network.icx.icx import get_config, load_config
from ansible.module_utils.network.common.utils import ComplexList, validate_ip_v6_address
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
def diff_list(want, have):
adds = [w for w in want if w not in have]
removes = [h for h in have if h not in want]
return (adds, removes)
def map_obj_to_commands(want, have, module):
commands = list()
state = module.params['state']
def needs_update(x):
return want.get(x) is not None and (want.get(x) != have.get(x))
if state == 'absent':
if have['name_servers'] == [] and have['aaa_servers'] == [] and have['domain_search'] == [] and have['hostname'] is None:
if want['hostname']:
commands.append('no hostname')
if want['domain_search']:
for item in want['domain_search']:
commands.append('no ip dns domain-list %s' % item)
if want['name_servers']:
for item in want['name_servers']:
commands.append('no ip dns server-address %s' % item)
if want['aaa_servers']:
want_servers = []
want_server = want['aaa_servers']
if want_server:
want_list = deepcopy(want_server)
for items in want_list:
items['auth_key'] = None
want_servers.append(items)
for item in want_servers:
ipv6addr = validate_ip_v6_address(item['hostname'])
if ipv6addr:
commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname'])
else:
commands.append('no ' + item['type'] + '-server host ' + item['hostname'])
if want['hostname']:
if have['hostname'] == want['hostname']:
commands.append('no hostname')
if want['domain_search']:
for item in want['domain_search']:
if item in have['domain_search']:
commands.append('no ip dns domain-list %s' % item)
if want['name_servers']:
for item in want['name_servers']:
if item in have['name_servers']:
commands.append('no ip dns server-address %s' % item)
if want['aaa_servers']:
want_servers = []
want_server = want['aaa_servers']
have_server = have['aaa_servers']
if want_server:
want_list = deepcopy(want_server)
for items in want_list:
items['auth_key'] = None
want_servers.append(items)
for item in want_servers:
if item in have_server:
ipv6addr = validate_ip_v6_address(item['hostname'])
if ipv6addr:
commands.append('no ' + item['type'] + '-server host ipv6 ' + item['hostname'])
else:
commands.append('no ' + item['type'] + '-server host ' + item['hostname'])
elif state == 'present':
if needs_update('hostname'):
commands.append('hostname %s' % want['hostname'])
if want['domain_search']:
adds, removes = diff_list(want['domain_search'], have['domain_search'])
for item in removes:
commands.append('no ip dns domain-list %s' % item)
for item in adds:
commands.append('ip dns domain-list %s' % item)
if want['name_servers']:
adds, removes = diff_list(want['name_servers'], have['name_servers'])
for item in removes:
commands.append('no ip dns server-address %s' % item)
for item in adds:
commands.append('ip dns server-address %s' % item)
if want['aaa_servers']:
want_servers = []
want_server = want['aaa_servers']
have_server = have['aaa_servers']
want_list = deepcopy(want_server)
for items in want_list:
items['auth_key'] = None
want_servers.append(items)
adds, removes = diff_list(want_servers, have_server)
for item in removes:
ip6addr = validate_ip_v6_address(item['hostname'])
if ip6addr:
cmd = 'no ' + item['type'] + '-server host ipv6 ' + item['hostname']
else:
cmd = 'no ' + item['type'] + '-server host ' + item['hostname']
commands.append(cmd)
for w_item in adds:
for item in want_server:
if item['hostname'] == w_item['hostname'] and item['type'] == w_item['type']:
auth_key = item['auth_key']
ip6addr = validate_ip_v6_address(w_item['hostname'])
if ip6addr:
cmd = w_item['type'] + '-server host ipv6 ' + w_item['hostname']
else:
cmd = w_item['type'] + '-server host ' + w_item['hostname']
if w_item['auth_port_type']:
cmd += ' ' + w_item['auth_port_type'] + ' ' + w_item['auth_port_num']
if w_item['acct_port_num'] and w_item['type'] == 'radius':
cmd += ' acct-port ' + w_item['acct_port_num']
if w_item['type'] == 'tacacs':
if any((w_item['acct_port_num'], w_item['auth_key_type'])):
module.fail_json(msg='acct_port and auth_key_type is not applicable for tacacs server')
if w_item['acct_type']:
cmd += ' ' + w_item['acct_type']
if auth_key is not None:
cmd += ' key ' + auth_key
if w_item['auth_key_type'] and w_item['type'] == 'radius':
val = ''
for y in w_item['auth_key_type']:
val = val + ' ' + y
cmd += val
commands.append(cmd)
return commands
def parse_hostname(config):
match = re.search(r'^hostname (\S+)', config, re.M)
if match:
return match.group(1)
def parse_domain_search(config):
match = re.findall(r'^ip dns domain[- ]list (\S+)', config, re.M)
matches = list()
for name in match:
matches.append(name)
return matches
def parse_name_servers(config):
matches = list()
values = list()
lines = config.split('\n')
for line in lines:
if 'ip dns server-address' in line:
values = line.split(' ')
for val in values:
match = re.search(r'([0-9.]+)', val)
if match:
matches.append(match.group())
return matches
def parse_aaa_servers(config):
configlines = config.split('\n')
obj = []
for line in configlines:
auth_key_type = []
if 'radius-server' in line or 'tacacs-server' in line:
aaa_type = 'radius' if 'radius-server' in line else 'tacacs'
match = re.search(r'(host ipv6 (\S+))|(host (\S+))', line)
if match:
hostname = match.group(2) if match.group(2) is not None else match.group(4)
match = re.search(r'auth-port ([0-9]+)', line)
if match:
auth_port_num = match.group(1)
else:
auth_port_num = None
match = re.search(r'acct-port ([0-9]+)', line)
if match:
acct_port_num = match.group(1)
else:
acct_port_num = None
match = re.search(r'acct-port [0-9]+ (\S+)', line)
if match:
acct_type = match.group(1)
else:
acct_type = None
if aaa_type == 'tacacs':
match = re.search(r'auth-port [0-9]+ (\S+)', line)
if match:
acct_type = match.group(1)
else:
acct_type = None
match = re.search(r'(dot1x)', line)
if match:
auth_key_type.append('dot1x')
match = re.search(r'(mac-auth)', line)
if match:
auth_key_type.append('mac-auth')
match = re.search(r'(web-auth)', line)
if match:
auth_key_type.append('web-auth')
obj.append({
'type': aaa_type,
'hostname': hostname,
'auth_port_type': 'auth-port',
'auth_port_num': auth_port_num,
'acct_port_num': acct_port_num,
'acct_type': acct_type,
'auth_key': None,
'auth_key_type': set(auth_key_type) if len(auth_key_type) > 0 else None
})
return obj
def map_config_to_obj(module):
compare = module.params['check_running_config']
config = get_config(module, None, compare=compare)
return {
'hostname': parse_hostname(config),
'domain_search': parse_domain_search(config),
'name_servers': parse_name_servers(config),
'aaa_servers': parse_aaa_servers(config)
}
def map_params_to_obj(module):
if module.params['aaa_servers']:
for item in module.params['aaa_servers']:
if item['auth_key_type']:
item['auth_key_type'] = set(item['auth_key_type'])
obj = {
'hostname': module.params['hostname'],
'domain_name': module.params['domain_name'],
'domain_search': module.params['domain_search'],
'name_servers': module.params['name_servers'],
'state': module.params['state'],
'aaa_servers': module.params['aaa_servers']
}
return obj
def main():
""" Main entry point for Ansible module execution
"""
server_spec = dict(
type=dict(choices=['radius', 'tacacs']),
hostname=dict(),
auth_port_type=dict(choices=['auth-port']),
auth_port_num=dict(),
acct_port_num=dict(),
acct_type=dict(choices=['accounting-only', 'authentication-only', 'authorization-only', 'default']),
auth_key=dict(),
auth_key_type=dict(type='list', choices=['dot1x', 'mac-auth', 'web-auth'])
)
argument_spec = dict(
hostname=dict(),
domain_name=dict(type='list'),
domain_search=dict(type='list'),
name_servers=dict(type='list'),
aaa_servers=dict(type='list', elements='dict', options=server_spec),
state=dict(choices=['present', 'absent'], default='present'),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
result = {'changed': False}
warnings = list()
result['warnings'] = warnings
exec_command(module, 'skip')
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()

View file

@ -0,0 +1,7 @@
ip dns domain-list ansib.eg.com
ip dns domain-list red.com
ip dns domain-list test1.com
ip dns server-address 10.22.22.64
ip dns server-address 172.22.22.64
radius-server host 172.16.20.14 auth-port 1837 acct-port 5021 accounting-only key database mac-auth
tacacs-server host 182.16.10.20

View file

@ -0,0 +1,164 @@
# 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
import json
from units.compat.mock import patch
from ansible.modules.network.icx import icx_system
from units.modules.utils import set_module_args
from .icx_module import TestICXModule, load_fixture
class TestICXSystemModule(TestICXModule):
module = icx_system
def setUp(self):
super(TestICXSystemModule, self).setUp()
self.mock_get_config = patch('ansible.modules.network.icx.icx_system.get_config')
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.modules.network.icx.icx_system.load_config')
self.load_config = self.mock_load_config.start()
self.mock_exec_command = patch('ansible.modules.network.icx.icx_system.exec_command')
self.exec_command = self.mock_exec_command.start()
self.set_running_config()
def tearDown(self):
super(TestICXSystemModule, 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_file(*args, **kwargs):
module = args
for arg in args:
if arg.params['check_running_config'] is True:
return load_fixture('icx_system.txt').strip()
else:
return ''
self.get_config.side_effect = load_file
self.load_config.return_value = None
def test_icx_system_set_config(self):
set_module_args(dict(hostname='ruckus', name_servers=['172.16.10.2', '11.22.22.4'], domain_search=['ansible.com', 'redhat.com']))
if not self.ENV_ICX_USE_DIFF:
commands = [
'hostname ruckus',
'ip dns domain-list ansible.com',
'ip dns domain-list redhat.com',
'ip dns server-address 11.22.22.4',
'ip dns server-address 172.16.10.2'
]
self.execute_module(changed=True, commands=commands)
else:
commands = [
'hostname ruckus',
'ip dns domain-list ansible.com',
'ip dns domain-list redhat.com',
'ip dns server-address 11.22.22.4',
'ip dns server-address 172.16.10.2',
'no ip dns domain-list ansib.eg.com',
'no ip dns domain-list red.com',
'no ip dns domain-list test1.com',
'no ip dns server-address 10.22.22.64',
'no ip dns server-address 172.22.22.64'
]
self.execute_module(changed=True, commands=commands)
def test_icx_system_remove_config(self):
set_module_args(dict(name_servers=['10.22.22.64', '11.22.22.4'], domain_search=['ansib.eg.com', 'redhat.com'], state='absent'))
if not self.ENV_ICX_USE_DIFF:
commands = [
'no ip dns domain-list ansib.eg.com',
'no ip dns domain-list redhat.com',
'no ip dns server-address 10.22.22.64',
'no ip dns server-address 11.22.22.4'
]
self.execute_module(changed=True, commands=commands)
else:
commands = [
'no ip dns domain-list ansib.eg.com',
'no ip dns server-address 10.22.22.64',
]
self.execute_module(changed=True, commands=commands)
def test_icx_system_remove_config_compare(self):
set_module_args(
dict(
name_servers=[
'10.22.22.64',
'11.22.22.4'],
domain_search=[
'ansib.eg.com',
'redhat.com'],
state='absent',
check_running_config=True))
if self.get_running_config(compare=True):
if not self.ENV_ICX_USE_DIFF:
commands = [
'no ip dns domain-list ansib.eg.com',
'no ip dns server-address 10.22.22.64',
]
self.execute_module(changed=True, commands=commands)
else:
commands = [
'no ip dns domain-list ansib.eg.com',
'no ip dns server-address 10.22.22.64',
]
self.execute_module(changed=True, commands=commands)
def test_icx_aaa_servers_radius_set(self):
radius = [
dict(
type='radius',
hostname='2001:db8::1',
auth_port_type='auth-port',
auth_port_num='1821',
acct_port_num='1321',
acct_type='accounting-only',
auth_key='radius',
auth_key_type=[
'mac-auth']),
dict(
type='radius',
hostname='172.16.10.24',
auth_port_type='auth-port',
auth_port_num='2001',
acct_port_num='5000',
acct_type='authentication-only',
auth_key='radius-server'),
dict(
type='tacacs',
hostname='ansible.com')]
set_module_args(dict(hostname='ruckus', aaa_servers=radius))
if not self.ENV_ICX_USE_DIFF:
commands = [
'hostname ruckus',
'radius-server host 172.16.10.24 auth-port 2001 acct-port 5000 authentication-only key radius-server',
'radius-server host ipv6 2001:db8::1 auth-port 1821 acct-port 1321 accounting-only key radius mac-auth',
'tacacs-server host ansible.com'
]
self.execute_module(changed=True, commands=commands)
else:
commands = [
'hostname ruckus',
'no radius-server host 172.16.20.14',
'no tacacs-server host 182.16.10.20',
'radius-server host 172.16.10.24 auth-port 2001 acct-port 5000 authentication-only key radius-server',
'radius-server host ipv6 2001:db8::1 auth-port 1821 acct-port 1321 accounting-only key radius mac-auth',
'tacacs-server host ansible.com'
]
self.execute_module(changed=True, commands=commands)