diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index b602d6f1bc..32e0c20384 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -486,6 +486,7 @@ files: $modules/network/panos/panos_address.py: itdependsnetworks ivanbojer jtschichold $modules/network/protocol/: $team_networking $modules/network/routing/: $team_networking + $modules/network/slxos/: $team_extreme $modules/network/sros/: privateip $modules/network/system/: $team_networking $modules/network/vyos/: Qalthos samdoran @@ -850,6 +851,9 @@ files: $module_utils/network/onyx: maintainers: $team_onyx labels: networking + $module_utils/network/slxos: + maintainers: $team_extreme + labels: networking $module_utils/openstack.py: maintainers: $team_openstack labels: @@ -953,6 +957,9 @@ files: lib/ansible/plugins/cliconf/onyx.py: maintainers: $team_onyx labels: networking + lib/ansible/plugins/cliconf/slxos.py: + maintainers: $team_extreme + labels: networking lib/ansible/plugins/connection/lxd.py: maintainers: mattclay lib/ansible/plugins/connection/netconf.py: @@ -1043,6 +1050,9 @@ files: lib/ansible/plugins/terminal/onyx.py: maintainers: $team_onyx labels: networking + lib/ansible/plugins/terminal/slxos.py: + maintainers: $team_extreme + labels: networking lib/ansible/plugins/terminal/sros.py: maintainers: $team_networking labels: networking @@ -1121,6 +1131,7 @@ macros: team_cloudstack: resmo dpassante team_cumulus: isharacomix jrrivers privateip team_cyberark_conjur: jvanderhoof ryanprior + team_extreme: bigmstone LindsayHill team_ipa: Nosmoht akasurde team_manageiq: gtanzillo abellotti zgalor yaacov cben team_meraki: dagwieers kbreit diff --git a/lib/ansible/module_utils/network/slxos/__init__.py b/lib/ansible/module_utils/network/slxos/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/module_utils/network/slxos/slxos.py b/lib/ansible/module_utils/network/slxos/slxos.py new file mode 100644 index 0000000000..bbd7a6a7ee --- /dev/null +++ b/lib/ansible/module_utils/network/slxos/slxos.py @@ -0,0 +1,107 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +import json +from ansible.module_utils._text import to_text +from ansible.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection + + +def get_connection(module): + """Get switch connection + + Creates reusable SSH connection to the switch described in a given module. + + Args: + module: A valid AnsibleModule instance. + + Returns: + An instance of `ansible.module_utils.connection.Connection` with a + connection to the switch described in the provided module. + + Raises: + AnsibleConnectionFailure: An error occurred connecting to the device + """ + if hasattr(module, 'slxos_connection'): + return module.slxos_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module.slxos_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module.slxos_connection + + +def get_capabilities(module): + """Get switch capabilities + + Collects and returns a python object with the switch capabilities. + + Args: + module: A valid AnsibleModule instance. + + Returns: + A dictionary containing the switch capabilities. + """ + if hasattr(module, 'slxos_capabilities'): + return module.slxos_capabilities + + capabilities = Connection(module._socket_path).get_capabilities() + module.slxos_capabilities = json.loads(capabilities) + return module.slxos_capabilities + + +def run_commands(module, commands): + """Run command list against connection. + + Get new or previously used connection and send commands to it one at a time, + collecting response. + + Args: + module: A valid AnsibleModule instance. + commands: Iterable of command strings. + + Returns: + A list of output strings. + """ + responses = list() + connection = get_connection(module) + + for cmd in to_list(commands): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + else: + command = cmd + prompt = None + answer = None + + out = connection.get(command, prompt, answer) + + try: + out = to_text(out, errors='surrogate_or_strict') + except UnicodeError: + module.fail_json(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) + + responses.append(out) + + return responses diff --git a/lib/ansible/modules/network/slxos/__init__.py b/lib/ansible/modules/network/slxos/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/modules/network/slxos/slxos_command.py b/lib/ansible/modules/network/slxos/slxos_command.py new file mode 100644 index 0000000000..e735725a7f --- /dev/null +++ b/lib/ansible/modules/network/slxos/slxos_command.py @@ -0,0 +1,243 @@ +#!/usr/bin/python +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = """ +--- +module: slxos_command +version_added: "2.6" +author: "Lindsay Hill (@LindsayHill)" +short_description: Run commands on remote devices running Extreme Networks SLX-OS +description: + - Sends arbitrary commands to an SLX node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(slxos_config) to configure SLX-OS devices. +notes: + - Tested against SLX-OS 17s.1.02 + - If a command sent to the device requires answering a prompt, it is possible + to pass a dict containing I(command), I(answer) and I(prompt). See examples. +options: + commands: + description: + - List of commands to send to the remote SLX-OS device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + default: null + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + required: false + default: all + choices: ['any', 'all'] + retries: + description: + - Specifies the number of retries a command should by tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + required: false + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + required: false + default: 1 +""" + +EXAMPLES = """ +tasks: + - name: run show version on remote devices + slxos_command: + commands: show version + + - name: run show version and check to see if output contains SLX + slxos_command: + commands: show version + wait_for: result[0] contains SLX + + - name: run multiple commands on remote nodes + slxos_command: + commands: + - show version + - show interfaces + + - name: run multiple commands and evaluate the output + slxos_command: + commands: + - show version + - show interface status + wait_for: + - result[0] contains SLX + - result[1] contains Eth + - name: run command that requires answering a prompt + slxos_command: + commands: + - command: 'clear sessions' + prompt: 'This operation will logout all the user sessions. Do you want to continue (yes/no)?:' + answer: y +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import time + +from ansible.module_utils.network.slxos.slxos import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.common.utils import ComplexList +from ansible.module_utils.network.common.parsing import Conditional +from ansible.module_utils.six import string_types + + +__metaclass__ = type + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split('\n') + yield item + + +def parse_commands(module, warnings): + command = ComplexList(dict( + command=dict(key=True), + prompt=dict(), + answer=dict() + ), module) + commands = command(module.params['commands']) + for item in list(commands): + configure_type = re.match(r'conf(?:\w*)(?:\s+(\w+))?', item['command']) + if module.check_mode: + if configure_type and configure_type.group(1) not in ('confirm', 'replace', 'revert', 'network'): + module.fail_json( + msg='slxos_command does not support running config mode ' + 'commands. Please use slxos_config instead' + ) + if not item['command'].startswith('show'): + warnings.append( + 'only show commands are supported when using check mode, not ' + 'executing `%s`' % item['command'] + ) + commands.remove(item) + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'changed': False, + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/cliconf/slxos.py b/lib/ansible/plugins/cliconf/slxos.py new file mode 100644 index 0000000000..ca065ad9ed --- /dev/null +++ b/lib/ansible/plugins/cliconf/slxos.py @@ -0,0 +1,98 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from itertools import chain + +from ansible.module_utils._text import to_bytes, to_text +from ansible.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'slxos' + reply = self.get(b'show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'SLX\-OS Operating System Version: (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + reply = self.get(b'show chassis') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'^Chassis Name:(\s+)(\S+)', data, re.M) + if match: + device_info['network_os_model'] = match.group(2) + + reply = self.get(b'show running-config | inc "switch-attributes host-name') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'switch-attributes host-name (\S+)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + def get_config(self, source='running', flags=None): + if source not in ('running', 'startup'): + return self.invalid_params("fetching configuration from %s is not supported" % source) + if source == 'running': + cmd = 'show running-config' + else: + cmd = 'show startup-config' + + flags = [] if flags is None else flags + cmd += ' '.join(flags) + cmd = cmd.strip() + + return self.send_command(cmd) + + def edit_config(self, command): + for cmd in chain(['configure terminal'], to_list(command), ['end']): + if isinstance(cmd, dict): + command = cmd['command'] + prompt = cmd['prompt'] + answer = cmd['answer'] + newline = cmd.get('newline', True) + else: + command = cmd + prompt = None + answer = None + newline = True + + self.send_command(command, prompt, answer, False, newline) + + def get(self, command, prompt=None, answer=None, sendonly=False): + return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly) + + def get_capabilities(self): + result = {} + result['rpc'] = self.get_base_rpc() + result['network_api'] = 'cliconf' + result['device_info'] = self.get_device_info() + return json.dumps(result) diff --git a/lib/ansible/plugins/terminal/slxos.py b/lib/ansible/plugins/terminal/slxos.py new file mode 100644 index 0000000000..245189468f --- /dev/null +++ b/lib/ansible/plugins/terminal/slxos.py @@ -0,0 +1,54 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"([\r\n]|(\x1b\[\?7h))[\w\+\-\.:\/\[\]]+(?:\([^\)]+\)){0,3}(?:[>#]) ?$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + # re.compile(br"^% \w+", re.M), + re.compile(br"% ?Bad secret"), + re.compile(br"[\r\n%] Bad passwords"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"[^\r\n]+ not found"), + re.compile(br"'[^']' +returned error code: ?\d+"), + re.compile(br"Bad mask", re.I), + re.compile(br"% ?(\S+) ?overlaps with ?(\S+)", re.I), + re.compile(br"[%\S] ?Informational: ?[\s]+", re.I), + re.compile(br"syntax error: unknown argument.", re.I) + ] + + def on_open_shell(self): + try: + self._exec_cli_command(u'terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/test/units/module_utils/network/slxos/test_slxos.py b/test/units/module_utils/network/slxos/test_slxos.py new file mode 100644 index 0000000000..1e855fb9ac --- /dev/null +++ b/test/units/module_utils/network/slxos/test_slxos.py @@ -0,0 +1,131 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from os import path +import json + +from mock import MagicMock, patch, call + +from ansible.compat.tests import unittest +from ansible.module_utils.network.slxos import slxos + + +class TestPluginCLIConfSLXOS(unittest.TestCase): + """ Test class for SLX-OS CLI Conf Methods + """ + + def test_get_connection_established(self): + """ Test get_connection with established connection + """ + module = MagicMock() + connection = slxos.get_connection(module) + self.assertEqual(connection, module.slxos_connection) + + @patch('ansible.module_utils.network.slxos.slxos.Connection') + def test_get_connection_new(self, connection): + """ Test get_connection with new connection + """ + socket_path = "little red riding hood" + module = MagicMock(spec=[ + 'fail_json', + ]) + module._socket_path = socket_path + + connection().get_capabilities.return_value = '{"network_api": "cliconf"}' + returned_connection = slxos.get_connection(module) + connection.assert_called_with(socket_path) + self.assertEqual(returned_connection, module.slxos_connection) + + @patch('ansible.module_utils.network.slxos.slxos.Connection') + def test_get_connection_incorrect_network_api(self, connection): + """ Test get_connection with incorrect network_api response + """ + socket_path = "little red riding hood" + module = MagicMock(spec=[ + 'fail_json', + ]) + module._socket_path = socket_path + module.fail_json.side_effect = TypeError + + connection().get_capabilities.return_value = '{"network_api": "nope"}' + + with self.assertRaises(TypeError): + slxos.get_connection(module) + + @patch('ansible.module_utils.network.slxos.slxos.Connection') + def test_get_capabilities(self, connection): + """ Test get_capabilities + """ + socket_path = "little red riding hood" + module = MagicMock(spec=[ + 'fail_json', + ]) + module._socket_path = socket_path + module.fail_json.side_effect = TypeError + + capabilities = {'network_api': 'cliconf'} + + connection().get_capabilities.return_value = json.dumps(capabilities) + + capabilities_returned = slxos.get_capabilities(module) + + self.assertEqual(capabilities, capabilities_returned) + + @patch('ansible.module_utils.network.slxos.slxos.Connection') + def test_run_commands(self, connection): + """ Test get_capabilities + """ + module = MagicMock() + + commands = [ + 'hello', + 'dolly', + 'well hello', + 'dolly', + 'its so nice to have you back', + 'where you belong', + ] + + responses = [ + 'Dolly, never go away again1', + 'Dolly, never go away again2', + 'Dolly, never go away again3', + 'Dolly, never go away again4', + 'Dolly, never go away again5', + 'Dolly, never go away again6', + ] + + module.slxos_connection.get.side_effect = responses + + run_command_responses = slxos.run_commands(module, commands) + + calls = [] + + for command in commands: + calls.append(call( + command, + None, + None + )) + + module.slxos_connection.get.assert_has_calls(calls) + + self.assertEqual(responses, run_command_responses) diff --git a/test/units/modules/network/slxos/__init__.py b/test/units/modules/network/slxos/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/units/modules/network/slxos/fixtures/show_version b/test/units/modules/network/slxos/fixtures/show_version new file mode 100644 index 0000000000..0d648378ac --- /dev/null +++ b/test/units/modules/network/slxos/fixtures/show_version @@ -0,0 +1,18 @@ +SLX-OS Operating System Software +SLX-OS Operating System Version: 17s.1.02 +Copyright (c) 1995-2018 Brocade Communications Systems, Inc. +Firmware name: 17s.1.02 +Build Time: 00:06:59 Sep 28, 2017 +Install Time: 15:58:29 Feb 9, 2018 +Kernel: 2.6.34.6 +Host Version: Ubuntu 14.04 LTS +Host Kernel: Linux 3.14.17 + +Control Processor: QEMU Virtual CPU version 2.0.0 + +System Uptime: 34days 4hrs 41mins 53secs + +Slot Name Primary/Secondary Versions Status +--------------------------------------------------------------------------- +SW/0 SLX-OS 17s.1.02 ACTIVE* + 17s.1.02 diff --git a/test/units/modules/network/slxos/slxos_module.py b/test/units/modules/network/slxos/slxos_module.py new file mode 100644 index 0000000000..e2d3d4401b --- /dev/null +++ b/test/units/modules/network/slxos/slxos_module.py @@ -0,0 +1,87 @@ +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json + +from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase + + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as file_desc: + data = file_desc.read() + + try: + data = json.loads(data) + except: + pass + + fixture_data[path] = data + return data + + +class TestSlxosModule(ModuleTestCase): + + def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False): + + self.load_fixtures(commands) + + if failed: + result = self.failed() + self.assertTrue(result['failed'], result) + else: + result = self.changed(changed) + self.assertEqual(result['changed'], changed, result) + + if commands is not None: + if sort: + self.assertEqual(sorted(commands), sorted(result['commands']), result['commands']) + else: + self.assertEqual(commands, result['commands'], result['commands']) + + return result + + def failed(self): + with self.assertRaises(AnsibleFailJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertTrue(result['failed'], result) + return result + + def changed(self, changed=False): + with self.assertRaises(AnsibleExitJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], changed, result) + return result + + def load_fixtures(self, commands=None): + pass diff --git a/test/units/modules/network/slxos/test_slxos_command.py b/test/units/modules/network/slxos/test_slxos_command.py new file mode 100644 index 0000000000..841754cb2f --- /dev/null +++ b/test/units/modules/network/slxos/test_slxos_command.py @@ -0,0 +1,121 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible.compat.tests.mock import patch +from ansible.modules.network.slxos import slxos_command +from units.modules.utils import set_module_args +from .slxos_module import TestSlxosModule, load_fixture + + +class TestSlxosCommandModule(TestSlxosModule): + + module = slxos_command + + def setUp(self): + super(TestSlxosCommandModule, self).setUp() + + self.mock_run_commands = patch('ansible.modules.network.slxos.slxos_command.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestSlxosCommandModule, self).tearDown() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item['command']) + command = obj['command'] + except ValueError: + command = item['command'] + filename = str(command).replace(' ', '_') + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_slxos_command_simple(self): + set_module_args(dict(commands=['show version'])) + result = self.execute_module() + self.assertEqual(len(result['stdout']), 1) + self.assertTrue(result['stdout'][0].startswith('SLX-OS Operating System Software')) + + def test_slxos_command_multiple(self): + set_module_args(dict(commands=['show version', 'show version'])) + result = self.execute_module() + self.assertEqual(len(result['stdout']), 2) + self.assertTrue(result['stdout'][0].startswith('SLX-OS Operating System Software')) + + def test_slxos_command_wait_for(self): + wait_for = 'result[0] contains "SLX-OS Operating System Software"' + set_module_args(dict(commands=['show version'], wait_for=wait_for)) + self.execute_module() + + def test_slxos_command_wait_for_fails(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=['show version'], wait_for=wait_for)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 10) + + def test_slxos_command_retries(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=['show version'], wait_for=wait_for, retries=2)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 2) + + def test_slxos_command_match_any(self): + wait_for = ['result[0] contains "SLX-OS"', + 'result[0] contains "test string"'] + set_module_args(dict(commands=['show version'], wait_for=wait_for, match='any')) + self.execute_module() + + def test_slxos_command_match_all(self): + wait_for = ['result[0] contains "SLX-OS"', + 'result[0] contains "SLX-OS Operating System Software"'] + set_module_args(dict(commands=['show version'], wait_for=wait_for, match='all')) + self.execute_module() + + def test_slxos_command_match_all_failure(self): + wait_for = ['result[0] contains "SLX-OS Operating System Software"', + 'result[0] contains "test string"'] + commands = ['show version', 'show version'] + set_module_args(dict(commands=commands, wait_for=wait_for, match='all')) + self.execute_module(failed=True) + + def test_slxos_command_configure_error(self): + commands = ['configure terminal'] + set_module_args({ + 'commands': commands, + '_ansible_check_mode': True, + }) + result = self.execute_module(failed=True) + self.assertEqual( + result['msg'], + 'slxos_command does not support running config mode commands. ' + 'Please use slxos_config instead' + ) diff --git a/test/units/plugins/cliconf/__init__.py b/test/units/plugins/cliconf/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/units/plugins/cliconf/fixtures/slxos/show_chassis b/test/units/plugins/cliconf/fixtures/slxos/show_chassis new file mode 100644 index 0000000000..1f7f0c51b3 --- /dev/null +++ b/test/units/plugins/cliconf/fixtures/slxos/show_chassis @@ -0,0 +1,40 @@ + +Chassis Name: BR-SLX9140 +switchType: 3001 + +FAN Unit: 1 +Time Awake: 36 days + +FAN Unit: 2 +Time Awake: 36 days + +FAN Unit: 3 +Time Awake: 36 days + +FAN Unit: 5 +Time Awake: 36 days + +FAN Unit: 6 +Time Awake: 36 days + +POWER SUPPLY Unit: 1 +Factory Part Num: 11-1111111-11 +Factory Serial Num: ASERIALNUMB +Time Awake: 36 days + +POWER SUPPLY Unit: 2 +Factory Part Num: 11-1111111-11 +Factory Serial Num: ASERIALNUMB +Time Awake: 36 days + +CHASSIS/WWN Unit: 1 +Power Consume Factor: 0 +Factory Part Num: 11-1111111-11 +Factory Serial Num: ASERIALNUMB +Manufacture: Day: 12 Month: 1 Year: 2017 +Update: Day: 5 Month: 4 Year: 2018 +Time Alive: 277 days +Time Awake: 36 days + +Airflow direction : Port side INTAKE + diff --git a/test/units/plugins/cliconf/fixtures/slxos/show_running-config b/test/units/plugins/cliconf/fixtures/slxos/show_running-config new file mode 100644 index 0000000000..b2e540d97e --- /dev/null +++ b/test/units/plugins/cliconf/fixtures/slxos/show_running-config @@ -0,0 +1,624 @@ +root enable +host-table aging-mode conversational +clock timezone Europe/Warsaw +hardware + profile tcam default + profile overlay-visibility default + profile route-table default maximum_paths 8 + system-mode default +! +http server use-vrf default-vrf +http server use-vrf mgmt-vrf +node-id 1 +! +ntp server 172.16.10.2 use-vrf mgmt-vrf +! +logging raslog console INFO +logging syslog-server 10.1.5.11 use-vrf mgmt-vrf +! +logging auditlog class SECURITY +logging auditlog class CONFIGURATION +logging auditlog class FIRMWARE +logging syslog-facility local LOG_LOCAL0 +logging syslog-client localip CHASSIS_IP +switch-attributes chassis-name SLX9140-LEAF2 +switch-attributes host-name DC2LEAF2 +no support autoupload enable +support ffdc +resource-monitor cpu enable threshold 90 action raslog +resource-monitor memory enable threshold 100 action raslog +resource-monitor process memory enable alarm 1000 critical 1200 +system-monitor fan threshold marginal-threshold 1 down-threshold 2 +system-monitor fan alert state removed action raslog +system-monitor power threshold marginal-threshold 1 down-threshold 2 +system-monitor power alert state removed action raslog +system-monitor temp threshold marginal-threshold 1 down-threshold 2 +system-monitor cid-card threshold marginal-threshold 1 down-threshold 2 +system-monitor cid-card alert state none action none +system-monitor compact-flash threshold marginal-threshold 1 down-threshold 0 +system-monitor MM threshold marginal-threshold 1 down-threshold 0 +system-monitor LineCard threshold marginal-threshold 1 down-threshold 2 +system-monitor LineCard alert state none action none +system-monitor SFM threshold marginal-threshold 1 down-threshold 2 +telemetry server use-vrf mgmt-vrf + transport tcp + port 50051 + activate +! +telemetry profile system-utilization default_system_utilization_statistics + interval 60 + add total-system-memory + add total-used-memory + add total-free-memory + add cached-memory + add buffers + add user-free-memory + add kernel-free-memory + add total-swap-memory + add total-free-swap-memory + add total-used-swap-memory + add user-process + add system-process + add niced-process + add iowait + add hw-interrupt + add sw-interrupt + add idle-state + add steal-time + add uptime +! +telemetry profile interface default_interface_statistics + interval 30 + add out-pkts + add in-pkts + add out-unicast-pkts + add in-unicast-pkts + add out-broadcast-pkts + add in-broadcast-pkts + add out-multicast-pkts + add in-multicast-pkts + add out-pkts-per-second + add in-pkts-per-second + add out-bandwidth + add in-bandwidth + add out-octets + add in-octets + add out-errors + add in-errors + add out-crc-errors + add in-crc-errors + add out-discards + add in-discards +! +line vty + exec-timeout 10 +! +threshold-monitor Buffer limit 70 +vrf mgmt-vrf + address-family ipv4 unicast + ip route 0.0.0.0/0 172.168.192.1 + ! + address-family ipv6 unicast + ! +! +ssh server key rsa 2048 +ssh server key ecdsa 256 +ssh server key dsa +ssh server use-vrf default-vrf +ssh server use-vrf mgmt-vrf +telnet server use-vrf default-vrf +telnet server use-vrf mgmt-vrf +role name admin desc Administrator +role name user desc User +aaa authentication login local +aaa accounting exec default start-stop none +aaa accounting commands default start-stop none +service password-encryption +username admin password "AINTNOPARTYLIKEAHOTELPARTYCAUSEAHOTELPARTYDONTSLEEPNOONEWOULDEVERACTUALLYTYPETHISWHYAREYOUHERE\n" encryption-level 7 role admin desc Administrator +cee-map default + precedence 1 + priority-group-table 1 weight 40 pfc on + priority-group-table 15.0 pfc off + priority-group-table 15.1 pfc off + priority-group-table 15.2 pfc off + priority-group-table 15.3 pfc off + priority-group-table 15.4 pfc off + priority-group-table 15.5 pfc off + priority-group-table 15.6 pfc off + priority-group-table 15.7 pfc off + priority-group-table 2 weight 60 pfc off + priority-table 2 2 2 1 2 2 2 15.0 + remap lossless-priority priority 0 +! +mac access-list extended M1 + seq 10 permit any any +! +vlan 1 + ip igmp snooping startup-query-interval 100 + ipv6 mld snooping startup-query-interval 100 +! +vlan 100 +! +vlan 200 +! +vlan 1001 + router-interface Ve 1001 + description Thomas-Test-Cluster +! +qos map cos-mutation all-zero-map + map cos 0 to cos 0 + map cos 1 to cos 0 + map cos 2 to cos 0 + map cos 3 to cos 0 + map cos 4 to cos 0 + map cos 5 to cos 0 + map cos 6 to cos 0 + map cos 7 to cos 0 +! +qos map cos-mutation default + map cos 0 to cos 0 + map cos 1 to cos 1 + map cos 2 to cos 2 + map cos 3 to cos 3 + map cos 4 to cos 4 + map cos 5 to cos 5 + map cos 6 to cos 6 + map cos 7 to cos 7 +! +qos map cos-traffic-class all-zero-map + map cos 0 to traffic-class 0 + map cos 1 to traffic-class 0 + map cos 2 to traffic-class 0 + map cos 3 to traffic-class 0 + map cos 4 to traffic-class 0 + map cos 5 to traffic-class 0 + map cos 6 to traffic-class 0 + map cos 7 to traffic-class 0 +! +qos map cos-traffic-class default + map cos 0 to traffic-class 1 + map cos 1 to traffic-class 0 + map cos 2 to traffic-class 2 + map cos 3 to traffic-class 3 + map cos 4 to traffic-class 4 + map cos 5 to traffic-class 5 + map cos 6 to traffic-class 6 + map cos 7 to traffic-class 7 +! +qos map cos-dscp all-zero-map + map cos 0 to dscp 0 + map cos 1 to dscp 0 + map cos 2 to dscp 0 + map cos 3 to dscp 0 + map cos 4 to dscp 0 + map cos 5 to dscp 0 + map cos 6 to dscp 0 + map cos 7 to dscp 0 +! +qos map cos-dscp default + map cos 0 to dscp 0 + map cos 1 to dscp 8 + map cos 2 to dscp 16 + map cos 3 to dscp 24 + map cos 4 to dscp 32 + map cos 5 to dscp 40 + map cos 6 to dscp 48 + map cos 7 to dscp 56 +! +qos map traffic-class-cos all-zero-map + map traffic-class 0 to cos 0 + map traffic-class 1 to cos 0 + map traffic-class 2 to cos 0 + map traffic-class 3 to cos 0 + map traffic-class 4 to cos 0 + map traffic-class 5 to cos 0 + map traffic-class 6 to cos 0 + map traffic-class 7 to cos 0 +! +qos map traffic-class-cos default + map traffic-class 0 to cos 0 + map traffic-class 1 to cos 1 + map traffic-class 2 to cos 2 + map traffic-class 3 to cos 3 + map traffic-class 4 to cos 4 + map traffic-class 5 to cos 5 + map traffic-class 6 to cos 6 + map traffic-class 7 to cos 7 +! +qos map traffic-class-mutation all-zero-map + map traffic-class 0 to traffic-class 0 + map traffic-class 1 to traffic-class 0 + map traffic-class 2 to traffic-class 0 + map traffic-class 3 to traffic-class 0 + map traffic-class 4 to traffic-class 0 + map traffic-class 5 to traffic-class 0 + map traffic-class 6 to traffic-class 0 + map traffic-class 7 to traffic-class 0 +! +qos map traffic-class-mutation default + map traffic-class 0 to traffic-class 0 + map traffic-class 1 to traffic-class 1 + map traffic-class 2 to traffic-class 2 + map traffic-class 3 to traffic-class 3 + map traffic-class 4 to traffic-class 4 + map traffic-class 5 to traffic-class 5 + map traffic-class 6 to traffic-class 6 + map traffic-class 7 to traffic-class 7 +! +qos map traffic-class-dscp all-zero-map + map traffic-class 0 to dscp 0 + map traffic-class 1 to dscp 0 + map traffic-class 2 to dscp 0 + map traffic-class 3 to dscp 0 + map traffic-class 4 to dscp 0 + map traffic-class 5 to dscp 0 + map traffic-class 6 to dscp 0 + map traffic-class 7 to dscp 0 +! +qos map traffic-class-dscp default + map traffic-class 0 to dscp 0 + map traffic-class 1 to dscp 8 + map traffic-class 2 to dscp 16 + map traffic-class 3 to dscp 24 + map traffic-class 4 to dscp 32 + map traffic-class 5 to dscp 40 + map traffic-class 6 to dscp 48 + map traffic-class 7 to dscp 56 +! +qos map dscp-mutation all-zero-map + map dscp 0-63 to dscp 0 +! +qos map dscp-mutation default + map dscp 0 to dscp 0 + map dscp 1 to dscp 1 + map dscp 10 to dscp 10 + map dscp 11 to dscp 11 + map dscp 12 to dscp 12 + map dscp 13 to dscp 13 + map dscp 14 to dscp 14 + map dscp 15 to dscp 15 + map dscp 16 to dscp 16 + map dscp 17 to dscp 17 + map dscp 18 to dscp 18 + map dscp 19 to dscp 19 + map dscp 2 to dscp 2 + map dscp 20 to dscp 20 + map dscp 21 to dscp 21 + map dscp 22 to dscp 22 + map dscp 23 to dscp 23 + map dscp 24 to dscp 24 + map dscp 25 to dscp 25 + map dscp 26 to dscp 26 + map dscp 27 to dscp 27 + map dscp 28 to dscp 28 + map dscp 29 to dscp 29 + map dscp 3 to dscp 3 + map dscp 30 to dscp 30 + map dscp 31 to dscp 31 + map dscp 32 to dscp 32 + map dscp 33 to dscp 33 + map dscp 34 to dscp 34 + map dscp 35 to dscp 35 + map dscp 36 to dscp 36 + map dscp 37 to dscp 37 + map dscp 38 to dscp 38 + map dscp 39 to dscp 39 + map dscp 4 to dscp 4 + map dscp 40 to dscp 40 + map dscp 41 to dscp 41 + map dscp 42 to dscp 42 + map dscp 43 to dscp 43 + map dscp 44 to dscp 44 + map dscp 45 to dscp 45 + map dscp 46 to dscp 46 + map dscp 47 to dscp 47 + map dscp 48 to dscp 48 + map dscp 49 to dscp 49 + map dscp 5 to dscp 5 + map dscp 50 to dscp 50 + map dscp 51 to dscp 51 + map dscp 52 to dscp 52 + map dscp 53 to dscp 53 + map dscp 54 to dscp 54 + map dscp 55 to dscp 55 + map dscp 56 to dscp 56 + map dscp 57 to dscp 57 + map dscp 58 to dscp 58 + map dscp 59 to dscp 59 + map dscp 6 to dscp 6 + map dscp 60 to dscp 60 + map dscp 61 to dscp 61 + map dscp 62 to dscp 62 + map dscp 63 to dscp 63 + map dscp 7 to dscp 7 + map dscp 8 to dscp 8 + map dscp 9 to dscp 9 +! +qos map dscp-traffic-class all-zero-map + map dscp 0-63 to traffic-class 0 +! +qos map dscp-traffic-class default + map dscp 0-7 to traffic-class 0 + map dscp 16-23 to traffic-class 2 + map dscp 24-31 to traffic-class 3 + map dscp 32-39 to traffic-class 4 + map dscp 40-47 to traffic-class 5 + map dscp 48-55 to traffic-class 6 + map dscp 56-63 to traffic-class 7 + map dscp 8-15 to traffic-class 1 +! +qos map dscp-cos all-zero-map + map dscp 0-63 to cos 0 +! +qos map dscp-cos default + map dscp 0-7 to cos 0 + map dscp 16-23 to cos 2 + map dscp 24-31 to cos 3 + map dscp 32-39 to cos 4 + map dscp 40-47 to cos 5 + map dscp 48-55 to cos 6 + map dscp 56-63 to cos 7 + map dscp 8-15 to cos 1 +! +protocol lldp + advertise optional-tlv management-address + system-description Brocade BR-SLX9140 Router +! +vlan dot1q tag native +police-remark-profile default +! +class-map BD-100 +! +class-map C1 + match access-group M1 +! +class-map cee +! +class-map default +! +policy-map P1 + class C1 + police cir 1000000 + ! +! +policy-map P2 + class default + police cir 12121212 + ! +! +no protocol vrrp +no protocol vrrp-extended +router bgp + local-as 65301 + capability as4-enable + bfd interval 300 min-rx 300 multiplier 3 + neighbor leaf_group peer-group + neighbor leaf_group remote-as 65500 + neighbor leaf_group bfd + neighbor 10.220.4.3 remote-as 65500 + neighbor 10.220.4.3 peer-group leaf_group + address-family ipv4 unicast + network 172.32.252.5/32 + maximum-paths 8 + ! + address-family ipv6 unicast + ! + address-family l2vpn evpn + ! +! +interface Loopback 1 + ip address 172.16.128.6/32 + no shutdown +! +interface Loopback 2 + ip address 172.16.129.5/32 + no shutdown +! +interface Management 0 + no tcp burstrate + no shutdown + vrf forwarding mgmt-vrf + ip address dhcp +! +interface Ethernet 0/1 + speed 25000 + fec mode disabled + switchport + switchport mode access + switchport access vlan 1 + no shutdown +! +interface Ethernet 0/2 + no shutdown +! +interface Ethernet 0/3 + speed 25000 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/4 + shutdown +! +interface Ethernet 0/5 + service-policy in P1 + no shutdown +! +interface Ethernet 0/6 + mtu 1548 + description L2 Interface + no shutdown +! +interface Ethernet 0/7 + mtu 1548 + description L2 Interface + no shutdown +! +interface Ethernet 0/8 + switchport + switchport mode trunk + switchport trunk allowed vlan add 100,200 + switchport trunk tag native-vlan + shutdown +! +interface Ethernet 0/9 + shutdown +! +interface Ethernet 0/10 + no shutdown +! +interface Ethernet 0/11 + no shutdown +! +interface Ethernet 0/12 + no shutdown +! +interface Ethernet 0/13 + no shutdown +! +interface Ethernet 0/14 + no shutdown +! +interface Ethernet 0/15 + shutdown +! +interface Ethernet 0/16 + shutdown +! +interface Ethernet 0/17 + shutdown +! +interface Ethernet 0/18 + shutdown +! +interface Ethernet 0/19 + shutdown +! +interface Ethernet 0/20 + shutdown +! +interface Ethernet 0/21 + shutdown +! +interface Ethernet 0/22 + shutdown +! +interface Ethernet 0/23 + shutdown +! +interface Ethernet 0/24 + shutdown +! +interface Ethernet 0/25 + shutdown +! +interface Ethernet 0/26 + shutdown +! +interface Ethernet 0/27 + shutdown +! +interface Ethernet 0/28 + shutdown +! +interface Ethernet 0/29 + shutdown +! +interface Ethernet 0/30 + shutdown +! +interface Ethernet 0/31 + shutdown +! +interface Ethernet 0/32 + shutdown +! +interface Ethernet 0/33 + shutdown +! +interface Ethernet 0/34 + shutdown +! +interface Ethernet 0/35 + shutdown +! +interface Ethernet 0/36 + shutdown +! +interface Ethernet 0/37 + shutdown +! +interface Ethernet 0/38 + shutdown +! +interface Ethernet 0/39 + shutdown +! +interface Ethernet 0/40 + shutdown +! +interface Ethernet 0/41 + shutdown +! +interface Ethernet 0/42 + shutdown +! +interface Ethernet 0/43 + shutdown +! +interface Ethernet 0/44 + shutdown +! +interface Ethernet 0/45 + shutdown +! +interface Ethernet 0/46 + shutdown +! +interface Ethernet 0/47 + shutdown +! +interface Ethernet 0/48 + shutdown +! +interface Ethernet 0/49 + shutdown +! +interface Ethernet 0/50 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/51 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/52 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/53 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/54 + fec mode disabled + no shutdown +! +interface Port-channel 200 + switchport + switchport mode access + switchport access vlan 200 + shutdown +! +interface Port-channel 1024 + insight enable + no shutdown +! +monitor session 1 + source ethernet 0/1 destination port-channel 1024 direction both +! +monitor session 2 +! +bridge-domain 100 p2mp +! +cluster MCT1 1 +! diff --git a/test/units/plugins/cliconf/fixtures/slxos/show_startup-config b/test/units/plugins/cliconf/fixtures/slxos/show_startup-config new file mode 100644 index 0000000000..b2e540d97e --- /dev/null +++ b/test/units/plugins/cliconf/fixtures/slxos/show_startup-config @@ -0,0 +1,624 @@ +root enable +host-table aging-mode conversational +clock timezone Europe/Warsaw +hardware + profile tcam default + profile overlay-visibility default + profile route-table default maximum_paths 8 + system-mode default +! +http server use-vrf default-vrf +http server use-vrf mgmt-vrf +node-id 1 +! +ntp server 172.16.10.2 use-vrf mgmt-vrf +! +logging raslog console INFO +logging syslog-server 10.1.5.11 use-vrf mgmt-vrf +! +logging auditlog class SECURITY +logging auditlog class CONFIGURATION +logging auditlog class FIRMWARE +logging syslog-facility local LOG_LOCAL0 +logging syslog-client localip CHASSIS_IP +switch-attributes chassis-name SLX9140-LEAF2 +switch-attributes host-name DC2LEAF2 +no support autoupload enable +support ffdc +resource-monitor cpu enable threshold 90 action raslog +resource-monitor memory enable threshold 100 action raslog +resource-monitor process memory enable alarm 1000 critical 1200 +system-monitor fan threshold marginal-threshold 1 down-threshold 2 +system-monitor fan alert state removed action raslog +system-monitor power threshold marginal-threshold 1 down-threshold 2 +system-monitor power alert state removed action raslog +system-monitor temp threshold marginal-threshold 1 down-threshold 2 +system-monitor cid-card threshold marginal-threshold 1 down-threshold 2 +system-monitor cid-card alert state none action none +system-monitor compact-flash threshold marginal-threshold 1 down-threshold 0 +system-monitor MM threshold marginal-threshold 1 down-threshold 0 +system-monitor LineCard threshold marginal-threshold 1 down-threshold 2 +system-monitor LineCard alert state none action none +system-monitor SFM threshold marginal-threshold 1 down-threshold 2 +telemetry server use-vrf mgmt-vrf + transport tcp + port 50051 + activate +! +telemetry profile system-utilization default_system_utilization_statistics + interval 60 + add total-system-memory + add total-used-memory + add total-free-memory + add cached-memory + add buffers + add user-free-memory + add kernel-free-memory + add total-swap-memory + add total-free-swap-memory + add total-used-swap-memory + add user-process + add system-process + add niced-process + add iowait + add hw-interrupt + add sw-interrupt + add idle-state + add steal-time + add uptime +! +telemetry profile interface default_interface_statistics + interval 30 + add out-pkts + add in-pkts + add out-unicast-pkts + add in-unicast-pkts + add out-broadcast-pkts + add in-broadcast-pkts + add out-multicast-pkts + add in-multicast-pkts + add out-pkts-per-second + add in-pkts-per-second + add out-bandwidth + add in-bandwidth + add out-octets + add in-octets + add out-errors + add in-errors + add out-crc-errors + add in-crc-errors + add out-discards + add in-discards +! +line vty + exec-timeout 10 +! +threshold-monitor Buffer limit 70 +vrf mgmt-vrf + address-family ipv4 unicast + ip route 0.0.0.0/0 172.168.192.1 + ! + address-family ipv6 unicast + ! +! +ssh server key rsa 2048 +ssh server key ecdsa 256 +ssh server key dsa +ssh server use-vrf default-vrf +ssh server use-vrf mgmt-vrf +telnet server use-vrf default-vrf +telnet server use-vrf mgmt-vrf +role name admin desc Administrator +role name user desc User +aaa authentication login local +aaa accounting exec default start-stop none +aaa accounting commands default start-stop none +service password-encryption +username admin password "AINTNOPARTYLIKEAHOTELPARTYCAUSEAHOTELPARTYDONTSLEEPNOONEWOULDEVERACTUALLYTYPETHISWHYAREYOUHERE\n" encryption-level 7 role admin desc Administrator +cee-map default + precedence 1 + priority-group-table 1 weight 40 pfc on + priority-group-table 15.0 pfc off + priority-group-table 15.1 pfc off + priority-group-table 15.2 pfc off + priority-group-table 15.3 pfc off + priority-group-table 15.4 pfc off + priority-group-table 15.5 pfc off + priority-group-table 15.6 pfc off + priority-group-table 15.7 pfc off + priority-group-table 2 weight 60 pfc off + priority-table 2 2 2 1 2 2 2 15.0 + remap lossless-priority priority 0 +! +mac access-list extended M1 + seq 10 permit any any +! +vlan 1 + ip igmp snooping startup-query-interval 100 + ipv6 mld snooping startup-query-interval 100 +! +vlan 100 +! +vlan 200 +! +vlan 1001 + router-interface Ve 1001 + description Thomas-Test-Cluster +! +qos map cos-mutation all-zero-map + map cos 0 to cos 0 + map cos 1 to cos 0 + map cos 2 to cos 0 + map cos 3 to cos 0 + map cos 4 to cos 0 + map cos 5 to cos 0 + map cos 6 to cos 0 + map cos 7 to cos 0 +! +qos map cos-mutation default + map cos 0 to cos 0 + map cos 1 to cos 1 + map cos 2 to cos 2 + map cos 3 to cos 3 + map cos 4 to cos 4 + map cos 5 to cos 5 + map cos 6 to cos 6 + map cos 7 to cos 7 +! +qos map cos-traffic-class all-zero-map + map cos 0 to traffic-class 0 + map cos 1 to traffic-class 0 + map cos 2 to traffic-class 0 + map cos 3 to traffic-class 0 + map cos 4 to traffic-class 0 + map cos 5 to traffic-class 0 + map cos 6 to traffic-class 0 + map cos 7 to traffic-class 0 +! +qos map cos-traffic-class default + map cos 0 to traffic-class 1 + map cos 1 to traffic-class 0 + map cos 2 to traffic-class 2 + map cos 3 to traffic-class 3 + map cos 4 to traffic-class 4 + map cos 5 to traffic-class 5 + map cos 6 to traffic-class 6 + map cos 7 to traffic-class 7 +! +qos map cos-dscp all-zero-map + map cos 0 to dscp 0 + map cos 1 to dscp 0 + map cos 2 to dscp 0 + map cos 3 to dscp 0 + map cos 4 to dscp 0 + map cos 5 to dscp 0 + map cos 6 to dscp 0 + map cos 7 to dscp 0 +! +qos map cos-dscp default + map cos 0 to dscp 0 + map cos 1 to dscp 8 + map cos 2 to dscp 16 + map cos 3 to dscp 24 + map cos 4 to dscp 32 + map cos 5 to dscp 40 + map cos 6 to dscp 48 + map cos 7 to dscp 56 +! +qos map traffic-class-cos all-zero-map + map traffic-class 0 to cos 0 + map traffic-class 1 to cos 0 + map traffic-class 2 to cos 0 + map traffic-class 3 to cos 0 + map traffic-class 4 to cos 0 + map traffic-class 5 to cos 0 + map traffic-class 6 to cos 0 + map traffic-class 7 to cos 0 +! +qos map traffic-class-cos default + map traffic-class 0 to cos 0 + map traffic-class 1 to cos 1 + map traffic-class 2 to cos 2 + map traffic-class 3 to cos 3 + map traffic-class 4 to cos 4 + map traffic-class 5 to cos 5 + map traffic-class 6 to cos 6 + map traffic-class 7 to cos 7 +! +qos map traffic-class-mutation all-zero-map + map traffic-class 0 to traffic-class 0 + map traffic-class 1 to traffic-class 0 + map traffic-class 2 to traffic-class 0 + map traffic-class 3 to traffic-class 0 + map traffic-class 4 to traffic-class 0 + map traffic-class 5 to traffic-class 0 + map traffic-class 6 to traffic-class 0 + map traffic-class 7 to traffic-class 0 +! +qos map traffic-class-mutation default + map traffic-class 0 to traffic-class 0 + map traffic-class 1 to traffic-class 1 + map traffic-class 2 to traffic-class 2 + map traffic-class 3 to traffic-class 3 + map traffic-class 4 to traffic-class 4 + map traffic-class 5 to traffic-class 5 + map traffic-class 6 to traffic-class 6 + map traffic-class 7 to traffic-class 7 +! +qos map traffic-class-dscp all-zero-map + map traffic-class 0 to dscp 0 + map traffic-class 1 to dscp 0 + map traffic-class 2 to dscp 0 + map traffic-class 3 to dscp 0 + map traffic-class 4 to dscp 0 + map traffic-class 5 to dscp 0 + map traffic-class 6 to dscp 0 + map traffic-class 7 to dscp 0 +! +qos map traffic-class-dscp default + map traffic-class 0 to dscp 0 + map traffic-class 1 to dscp 8 + map traffic-class 2 to dscp 16 + map traffic-class 3 to dscp 24 + map traffic-class 4 to dscp 32 + map traffic-class 5 to dscp 40 + map traffic-class 6 to dscp 48 + map traffic-class 7 to dscp 56 +! +qos map dscp-mutation all-zero-map + map dscp 0-63 to dscp 0 +! +qos map dscp-mutation default + map dscp 0 to dscp 0 + map dscp 1 to dscp 1 + map dscp 10 to dscp 10 + map dscp 11 to dscp 11 + map dscp 12 to dscp 12 + map dscp 13 to dscp 13 + map dscp 14 to dscp 14 + map dscp 15 to dscp 15 + map dscp 16 to dscp 16 + map dscp 17 to dscp 17 + map dscp 18 to dscp 18 + map dscp 19 to dscp 19 + map dscp 2 to dscp 2 + map dscp 20 to dscp 20 + map dscp 21 to dscp 21 + map dscp 22 to dscp 22 + map dscp 23 to dscp 23 + map dscp 24 to dscp 24 + map dscp 25 to dscp 25 + map dscp 26 to dscp 26 + map dscp 27 to dscp 27 + map dscp 28 to dscp 28 + map dscp 29 to dscp 29 + map dscp 3 to dscp 3 + map dscp 30 to dscp 30 + map dscp 31 to dscp 31 + map dscp 32 to dscp 32 + map dscp 33 to dscp 33 + map dscp 34 to dscp 34 + map dscp 35 to dscp 35 + map dscp 36 to dscp 36 + map dscp 37 to dscp 37 + map dscp 38 to dscp 38 + map dscp 39 to dscp 39 + map dscp 4 to dscp 4 + map dscp 40 to dscp 40 + map dscp 41 to dscp 41 + map dscp 42 to dscp 42 + map dscp 43 to dscp 43 + map dscp 44 to dscp 44 + map dscp 45 to dscp 45 + map dscp 46 to dscp 46 + map dscp 47 to dscp 47 + map dscp 48 to dscp 48 + map dscp 49 to dscp 49 + map dscp 5 to dscp 5 + map dscp 50 to dscp 50 + map dscp 51 to dscp 51 + map dscp 52 to dscp 52 + map dscp 53 to dscp 53 + map dscp 54 to dscp 54 + map dscp 55 to dscp 55 + map dscp 56 to dscp 56 + map dscp 57 to dscp 57 + map dscp 58 to dscp 58 + map dscp 59 to dscp 59 + map dscp 6 to dscp 6 + map dscp 60 to dscp 60 + map dscp 61 to dscp 61 + map dscp 62 to dscp 62 + map dscp 63 to dscp 63 + map dscp 7 to dscp 7 + map dscp 8 to dscp 8 + map dscp 9 to dscp 9 +! +qos map dscp-traffic-class all-zero-map + map dscp 0-63 to traffic-class 0 +! +qos map dscp-traffic-class default + map dscp 0-7 to traffic-class 0 + map dscp 16-23 to traffic-class 2 + map dscp 24-31 to traffic-class 3 + map dscp 32-39 to traffic-class 4 + map dscp 40-47 to traffic-class 5 + map dscp 48-55 to traffic-class 6 + map dscp 56-63 to traffic-class 7 + map dscp 8-15 to traffic-class 1 +! +qos map dscp-cos all-zero-map + map dscp 0-63 to cos 0 +! +qos map dscp-cos default + map dscp 0-7 to cos 0 + map dscp 16-23 to cos 2 + map dscp 24-31 to cos 3 + map dscp 32-39 to cos 4 + map dscp 40-47 to cos 5 + map dscp 48-55 to cos 6 + map dscp 56-63 to cos 7 + map dscp 8-15 to cos 1 +! +protocol lldp + advertise optional-tlv management-address + system-description Brocade BR-SLX9140 Router +! +vlan dot1q tag native +police-remark-profile default +! +class-map BD-100 +! +class-map C1 + match access-group M1 +! +class-map cee +! +class-map default +! +policy-map P1 + class C1 + police cir 1000000 + ! +! +policy-map P2 + class default + police cir 12121212 + ! +! +no protocol vrrp +no protocol vrrp-extended +router bgp + local-as 65301 + capability as4-enable + bfd interval 300 min-rx 300 multiplier 3 + neighbor leaf_group peer-group + neighbor leaf_group remote-as 65500 + neighbor leaf_group bfd + neighbor 10.220.4.3 remote-as 65500 + neighbor 10.220.4.3 peer-group leaf_group + address-family ipv4 unicast + network 172.32.252.5/32 + maximum-paths 8 + ! + address-family ipv6 unicast + ! + address-family l2vpn evpn + ! +! +interface Loopback 1 + ip address 172.16.128.6/32 + no shutdown +! +interface Loopback 2 + ip address 172.16.129.5/32 + no shutdown +! +interface Management 0 + no tcp burstrate + no shutdown + vrf forwarding mgmt-vrf + ip address dhcp +! +interface Ethernet 0/1 + speed 25000 + fec mode disabled + switchport + switchport mode access + switchport access vlan 1 + no shutdown +! +interface Ethernet 0/2 + no shutdown +! +interface Ethernet 0/3 + speed 25000 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/4 + shutdown +! +interface Ethernet 0/5 + service-policy in P1 + no shutdown +! +interface Ethernet 0/6 + mtu 1548 + description L2 Interface + no shutdown +! +interface Ethernet 0/7 + mtu 1548 + description L2 Interface + no shutdown +! +interface Ethernet 0/8 + switchport + switchport mode trunk + switchport trunk allowed vlan add 100,200 + switchport trunk tag native-vlan + shutdown +! +interface Ethernet 0/9 + shutdown +! +interface Ethernet 0/10 + no shutdown +! +interface Ethernet 0/11 + no shutdown +! +interface Ethernet 0/12 + no shutdown +! +interface Ethernet 0/13 + no shutdown +! +interface Ethernet 0/14 + no shutdown +! +interface Ethernet 0/15 + shutdown +! +interface Ethernet 0/16 + shutdown +! +interface Ethernet 0/17 + shutdown +! +interface Ethernet 0/18 + shutdown +! +interface Ethernet 0/19 + shutdown +! +interface Ethernet 0/20 + shutdown +! +interface Ethernet 0/21 + shutdown +! +interface Ethernet 0/22 + shutdown +! +interface Ethernet 0/23 + shutdown +! +interface Ethernet 0/24 + shutdown +! +interface Ethernet 0/25 + shutdown +! +interface Ethernet 0/26 + shutdown +! +interface Ethernet 0/27 + shutdown +! +interface Ethernet 0/28 + shutdown +! +interface Ethernet 0/29 + shutdown +! +interface Ethernet 0/30 + shutdown +! +interface Ethernet 0/31 + shutdown +! +interface Ethernet 0/32 + shutdown +! +interface Ethernet 0/33 + shutdown +! +interface Ethernet 0/34 + shutdown +! +interface Ethernet 0/35 + shutdown +! +interface Ethernet 0/36 + shutdown +! +interface Ethernet 0/37 + shutdown +! +interface Ethernet 0/38 + shutdown +! +interface Ethernet 0/39 + shutdown +! +interface Ethernet 0/40 + shutdown +! +interface Ethernet 0/41 + shutdown +! +interface Ethernet 0/42 + shutdown +! +interface Ethernet 0/43 + shutdown +! +interface Ethernet 0/44 + shutdown +! +interface Ethernet 0/45 + shutdown +! +interface Ethernet 0/46 + shutdown +! +interface Ethernet 0/47 + shutdown +! +interface Ethernet 0/48 + shutdown +! +interface Ethernet 0/49 + shutdown +! +interface Ethernet 0/50 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/51 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/52 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/53 + fec mode RS-FEC + no shutdown +! +interface Ethernet 0/54 + fec mode disabled + no shutdown +! +interface Port-channel 200 + switchport + switchport mode access + switchport access vlan 200 + shutdown +! +interface Port-channel 1024 + insight enable + no shutdown +! +monitor session 1 + source ethernet 0/1 destination port-channel 1024 direction both +! +monitor session 2 +! +bridge-domain 100 p2mp +! +cluster MCT1 1 +! diff --git a/test/units/plugins/cliconf/fixtures/slxos/show_version b/test/units/plugins/cliconf/fixtures/slxos/show_version new file mode 100644 index 0000000000..0d648378ac --- /dev/null +++ b/test/units/plugins/cliconf/fixtures/slxos/show_version @@ -0,0 +1,18 @@ +SLX-OS Operating System Software +SLX-OS Operating System Version: 17s.1.02 +Copyright (c) 1995-2018 Brocade Communications Systems, Inc. +Firmware name: 17s.1.02 +Build Time: 00:06:59 Sep 28, 2017 +Install Time: 15:58:29 Feb 9, 2018 +Kernel: 2.6.34.6 +Host Version: Ubuntu 14.04 LTS +Host Kernel: Linux 3.14.17 + +Control Processor: QEMU Virtual CPU version 2.0.0 + +System Uptime: 34days 4hrs 41mins 53secs + +Slot Name Primary/Secondary Versions Status +--------------------------------------------------------------------------- +SW/0 SLX-OS 17s.1.02 ACTIVE* + 17s.1.02 diff --git a/test/units/plugins/cliconf/test_slxos.py b/test/units/plugins/cliconf/test_slxos.py new file mode 100644 index 0000000000..ac5f073fa9 --- /dev/null +++ b/test/units/plugins/cliconf/test_slxos.py @@ -0,0 +1,140 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from os import path +import json + +from mock import MagicMock, call + +from ansible.compat.tests import unittest +from ansible.plugins.cliconf import slxos + +FIXTURE_DIR = b'%s/fixtures/slxos' % ( + path.dirname(path.abspath(__file__)).encode('utf-8') +) + + +def _connection_side_effect(*args, **kwargs): + try: + if args: + value = args[0] + else: + value = kwargs.get('command') + + fixture_path = path.abspath( + b'%s/%s' % (FIXTURE_DIR, b'_'.join(value.split(b' '))) + ) + with open(fixture_path, 'rb') as file_desc: + return file_desc.read() + except (OSError, IOError): + if args: + value = args[0] + return value + elif kwargs.get('command'): + value = kwargs.get('command') + return value + + return 'Nope' + + +class TestPluginCLIConfSLXOS(unittest.TestCase): + """ Test class for SLX-OS CLI Conf Methods + """ + def setUp(self): + self._mock_connection = MagicMock() + self._mock_connection.send.side_effect = _connection_side_effect + self._cliconf = slxos.Cliconf(self._mock_connection) + + def tearDown(self): + pass + + def test_get_device_info(self): + """ Test get_device_info + """ + device_info = self._cliconf.get_device_info() + + mock_device_info = { + 'network_os': 'slxos', + 'network_os_model': 'BR-SLX9140', + 'network_os_version': '17s.1.02', + } + + self.assertEqual(device_info, mock_device_info) + + def test_get_config(self): + """ Test get_config + """ + running_config = self._cliconf.get_config() + + fixture_path = path.abspath(b'%s/show_running-config' % FIXTURE_DIR) + with open(fixture_path, 'rb') as file_desc: + mock_running_config = file_desc.read() + self.assertEqual(running_config, mock_running_config) + + startup_config = self._cliconf.get_config() + + fixture_path = path.abspath(b'%s/show_running-config' % FIXTURE_DIR) + with open(fixture_path, 'rb') as file_desc: + mock_startup_config = file_desc.read() + self.assertEqual(startup_config, mock_startup_config) + + def test_edit_config(self): + """ Test edit_config + """ + test_config_command = b'this\nis\nthe\nsong\nthat\nnever\nends' + + self._cliconf.edit_config(test_config_command) + + send_calls = [] + + for command in [b'configure terminal', test_config_command, b'end']: + send_calls.append(call( + command=command, + prompt_retry_check=False, + sendonly=False, + newline=True + )) + + self._mock_connection.send.assert_has_calls(send_calls) + + def test_get_capabilities(self): + """ Test get_capabilities + """ + capabilities = json.loads(self._cliconf.get_capabilities()) + mock_capabilities = { + 'network_api': 'cliconf', + 'rpc': [ + 'get_config', + 'edit_config', + 'get_capabilities', + 'get' + ], + 'device_info': { + 'network_os_model': 'BR-SLX9140', + 'network_os_version': '17s.1.02', + 'network_os': 'slxos' + } + } + + self.assertEqual( + mock_capabilities, + capabilities + ) diff --git a/test/units/plugins/terminal/__init__.py b/test/units/plugins/terminal/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/units/plugins/terminal/test_slxos.py b/test/units/plugins/terminal/test_slxos.py new file mode 100644 index 0000000000..4c5f3b30b3 --- /dev/null +++ b/test/units/plugins/terminal/test_slxos.py @@ -0,0 +1,59 @@ +# +# (c) 2018 Extreme Networks Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from os import path +import json + +from mock import MagicMock + +from ansible.compat.tests import unittest +from ansible.plugins.terminal import slxos +from ansible.errors import AnsibleConnectionFailure + + +class TestPluginTerminalSLXOS(unittest.TestCase): + """ Test class for SLX-OS Terminal Module + """ + def setUp(self): + self._mock_connection = MagicMock() + self._terminal = slxos.TerminalModule(self._mock_connection) + + def tearDown(self): + pass + + def test_on_open_shell(self): + """ Test on_open_shell + """ + self._mock_connection.exec_command.side_effect = [ + b'Looking out my window I see a brick building, and people. Cool.', + ] + self._terminal.on_open_shell() + self._mock_connection.exec_command.assert_called_with(u'terminal length 0') + + def test_on_open_shell_error(self): + """ Test on_open_shell with error + """ + self._mock_connection.exec_command.side_effect = [ + AnsibleConnectionFailure + ] + + with self.assertRaises(AnsibleConnectionFailure): + self._terminal.on_open_shell()