new nos_command module (#43056)

This commit is contained in:
Lindsay Hill 2018-08-10 13:50:02 -07:00 committed by John R Barker
parent 3fe48a41f7
commit 5981a7489b
16 changed files with 1669 additions and 0 deletions

10
.github/BOTMETA.yml vendored
View file

@ -486,6 +486,7 @@ files:
$modules/network/netconf/netconf_rpc.py: wisotzky $team_networking
$modules/network/netscaler/: $team_netscaler
$modules/network/netvisor/: $team_netvisor
$modules/network/nos/: $team_extreme
$modules/network/nuage/: pdellaert
$modules/network/nxos/: $team_nxos
$modules/network/nso/: $team_nso
@ -892,6 +893,9 @@ files:
$module_utils/network/netscaler:
maintainers: $team_netscaler
labels: networking
$module_utils/network/nos:
maintainers: $team_extreme
labels: networking
$module_utils/network/nso:
maintainers: $team_nso
labels: networking
@ -1024,6 +1028,9 @@ files:
lib/ansible/plugins/cliconf/ironware.py:
maintainers: paulquack
labels: networking
lib/ansible/plugins/cliconf/nos.py:
maintainers: $team_extreme
labels: networking
lib/ansible/plugins/cliconf/nxos.py:
maintainers: $team_nxos
labels:
@ -1137,6 +1144,9 @@ files:
lib/ansible/plugins/terminal/junos.py:
maintainers: $team_networking
labels: networking
lib/ansible/plugins/terminal/nos.py:
maintainers: $team_extreme
labels: networking
lib/ansible/plugins/terminal/nxos.py:
maintainers: $team_networking
labels:

View file

@ -0,0 +1,160 @@
#
# (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 <http://www.gnu.org/licenses/>.
#
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.connection import Connection, ConnectionError
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, 'nos_connection'):
return module.nos_connection
capabilities = get_capabilities(module)
network_api = capabilities.get('network_api')
if network_api == 'cliconf':
module.nos_connection = Connection(module._socket_path)
else:
module.fail_json(msg='Invalid connection type %s' % network_api)
return module.nos_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, 'nos_capabilities'):
return module.nos_capabilities
try:
capabilities = Connection(module._socket_path).get_capabilities()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
module.nos_capabilities = json.loads(capabilities)
return module.nos_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
try:
out = connection.get(command, prompt, answer)
out = to_text(out, errors='surrogate_or_strict')
except ConnectionError as exc:
module.fail_json(msg=to_text(exc))
except UnicodeError:
module.fail_json(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out)))
responses.append(out)
return responses
def get_config(module):
"""Get switch configuration
Gets the described device's current configuration. If a configuration has
already been retrieved it will return the previously obtained configuration.
Args:
module: A valid AnsibleModule instance.
Returns:
A string containing the configuration.
"""
if not hasattr(module, 'device_configs'):
module.device_configs = {}
elif module.device_configs != {}:
return module.device_configs
connection = get_connection(module)
try:
out = connection.get_config()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
cfg = to_text(out, errors='surrogate_then_replace').strip()
module.device_configs = cfg
return cfg
def load_config(module, commands):
"""Apply a list of commands to a device.
Given a list of commands apply them to the device to modify the
configuration in bulk.
Args:
module: A valid AnsibleModule instance.
commands: Iterable of command strings.
Returns:
None
"""
connection = get_connection(module)
try:
resp = connection.edit_config(commands)
return resp.get('response')
except ConnectionError as exc:
module.fail_json(msg=to_text(exc))

View file

@ -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 <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: nos_command
version_added: "2.7"
author: "Lindsay Hill (@LindsayHill)"
short_description: Run commands on remote devices running Extreme Networks NOS
description:
- Sends arbitrary commands to a NOS device 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(nos_config) to configure NOS devices.
notes:
- Tested against NOS 7.2.0
- 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 NOS 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
nos_command:
commands: show version
- name: run show version and check to see if output contains NOS
nos_command:
commands: show version
wait_for: result[0] contains NOS
- name: run multiple commands on remote nodes
nos_command:
commands:
- show version
- show interfaces
- name: run multiple commands and evaluate the output
nos_command:
commands:
- show version
- show interface status
wait_for:
- result[0] contains NOS
- result[1] contains Te
- name: run command that requires answering a prompt
nos_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.nos.nos 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='nos_command does not support running config mode '
'commands. Please use nos_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()

View file

@ -0,0 +1,96 @@
#
# (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 <http://www.gnu.org/licenses/>.
#
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'] = 'nos'
reply = self.get(b'show version')
data = to_text(reply, errors='surrogate_or_strict').strip()
match = re.search(r'Network 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'):
return self.invalid_params("fetching configuration from %s is not supported" % source)
if source == 'running':
cmd = 'show running-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)

View file

@ -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 <http://www.gnu.org/licenses/>.
#
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')

View file

@ -0,0 +1,149 @@
#
# (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 <http://www.gnu.org/licenses/>.
#
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.nos import nos
class TestPluginCLIConfNOS(unittest.TestCase):
""" Test class for NOS CLI Conf Methods
"""
def test_get_connection_established(self):
""" Test get_connection with established connection
"""
module = MagicMock()
connection = nos.get_connection(module)
self.assertEqual(connection, module.nos_connection)
@patch('ansible.module_utils.network.nos.nos.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 = nos.get_connection(module)
connection.assert_called_with(socket_path)
self.assertEqual(returned_connection, module.nos_connection)
@patch('ansible.module_utils.network.nos.nos.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):
nos.get_connection(module)
@patch('ansible.module_utils.network.nos.nos.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 = nos.get_capabilities(module)
self.assertEqual(capabilities, capabilities_returned)
@patch('ansible.module_utils.network.nos.nos.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.nos_connection.get.side_effect = responses
run_command_responses = nos.run_commands(module, commands)
calls = []
for command in commands:
calls.append(call(
command,
None,
None
))
module.nos_connection.get.assert_has_calls(calls)
self.assertEqual(responses, run_command_responses)
@patch('ansible.module_utils.network.nos.nos.Connection')
def test_load_config(self, connection):
""" Test load_config
"""
module = MagicMock()
commands = [
'what does it take',
'to be',
'number one?',
'two is not a winner',
'and three nobody remember',
]
nos.load_config(module, commands)
module.nos_connection.edit_config.assert_called_once_with(commands)

View file

@ -0,0 +1,17 @@
Network Operating System Software
Network Operating System Version: 7.2.0
Copyright (c) 1995-2017 Brocade Communications Systems, Inc.
Firmware name: 7.2.0
Build Time: 10:52:47 Jul 10, 2017
Install Time: 01:32:03 Jan 5, 2018
Kernel: 2.6.34.6
BootProm: 1.0.1
Control Processor: e500mc with 4096 MB of memory
Slot Name Primary/Secondary Versions Status
---------------------------------------------------------------------------
SW/0 NOS 7.2.0 ACTIVE*
7.2.0
SW/1 NOS 7.2.0 STANDBY
7.2.0

View file

@ -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 <http://www.gnu.org/licenses/>.
#
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 TestNosModule(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

View file

@ -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 <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.compat.tests.mock import patch
from ansible.modules.network.nos import nos_command
from units.modules.utils import set_module_args
from .nos_module import TestNosModule, load_fixture
class TestNosCommandModule(TestNosModule):
module = nos_command
def setUp(self):
super(TestNosCommandModule, self).setUp()
self.mock_run_commands = patch('ansible.modules.network.nos.nos_command.run_commands')
self.run_commands = self.mock_run_commands.start()
def tearDown(self):
super(TestNosCommandModule, 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_nos_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('Network Operating System Software'))
def test_nos_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('Network Operating System Software'))
def test_nos_command_wait_for(self):
wait_for = 'result[0] contains "Network Operating System Software"'
set_module_args(dict(commands=['show version'], wait_for=wait_for))
self.execute_module()
def test_nos_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_nos_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_nos_command_match_any(self):
wait_for = ['result[0] contains "Network"',
'result[0] contains "test string"']
set_module_args(dict(commands=['show version'], wait_for=wait_for, match='any'))
self.execute_module()
def test_nos_command_match_all(self):
wait_for = ['result[0] contains "Network"',
'result[0] contains "Network Operating System Software"']
set_module_args(dict(commands=['show version'], wait_for=wait_for, match='all'))
self.execute_module()
def test_nos_command_match_all_failure(self):
wait_for = ['result[0] contains "Network 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_nos_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'],
'nos_command does not support running config mode commands. '
'Please use nos_config instead'
)

View file

@ -0,0 +1,30 @@
Chassis Name: BR-VDX6740
switchType: 131
FAN Unit: 1
Time Awake: 0 days
FAN Unit: 2
Time Awake: 0 days
POWER SUPPLY Unit: 1
Factory Part Num: 23-1000043-01
Factory Serial Num:
Time Awake: 0 days
POWER SUPPLY Unit: 2
Factory Part Num: 23-1000043-01
Factory Serial Num:
Time Awake: 0 days
CHASSIS/WWN Unit: 1
Power Consume Factor: 0
Factory Part Num: 40-1000927-06
Factory Serial Num: CPL2541K01E
Manufacture: Day: 11 Month: 8 Year: 14
Update: Day: 18 Month: 7 Year: 2018
Time Alive: 1116 days
Time Awake: 0 days
Airflow direction : Port side INTAKE

View file

@ -0,0 +1,549 @@
diag post rbridge-id 104 enable
ntp server 10.10.10.1 use-vrf mgmt-vrf
logging raslog console INFO
logging auditlog class SECURITY
logging auditlog class CONFIGURATION
logging auditlog class FIRMWARE
logging syslog-facility local LOG_LOCAL7
logging syslog-client localip CHASSIS_IP
switch-attributes 104
chassis-name VDX6740
host-name LEAF4
!
no support autoupload enable
line vty
exec-timeout 10
!
zoning enabled-configuration cfg-name ""
zoning enabled-configuration default-zone-access allaccess
zoning enabled-configuration cfg-action cfg-save
dpod 104/0/1
reserve
!
dpod 104/0/2
!
dpod 104/0/3
!
dpod 104/0/4
!
dpod 104/0/5
!
dpod 104/0/6
!
dpod 104/0/7
!
dpod 104/0/8
!
dpod 104/0/9
!
dpod 104/0/10
!
dpod 104/0/11
!
dpod 104/0/12
!
dpod 104/0/13
!
dpod 104/0/14
!
dpod 104/0/15
!
dpod 104/0/16
!
dpod 104/0/17
!
dpod 104/0/18
!
dpod 104/0/19
!
dpod 104/0/20
!
dpod 104/0/21
!
dpod 104/0/22
!
dpod 104/0/23
!
dpod 104/0/24
!
dpod 104/0/25
!
dpod 104/0/26
!
dpod 104/0/27
!
dpod 104/0/28
!
dpod 104/0/29
!
dpod 104/0/30
!
dpod 104/0/31
!
dpod 104/0/32
!
dpod 104/0/33
!
dpod 104/0/34
!
dpod 104/0/35
!
dpod 104/0/36
!
dpod 104/0/37
!
dpod 104/0/38
!
dpod 104/0/39
!
dpod 104/0/40
!
dpod 104/0/41
!
dpod 104/0/42
!
dpod 104/0/43
!
dpod 104/0/44
!
dpod 104/0/45
!
dpod 104/0/46
!
dpod 104/0/47
!
dpod 104/0/48
!
dpod 104/0/49
!
dpod 104/0/50
!
dpod 104/0/51
!
dpod 104/0/52
!
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 "BwrsDbB+tABWGWpINOVKoQ==\n" encryption-level 7 role admin desc Administrator
username user password "BwrsDbB+tABWGWpINOVKoQ==\n" encryption-level 7 role user desc User
ip access-list extended test
seq 10 permit ip host 1.1.1.1 any log
!
snmp-server contact "Field Support."
snmp-server location "End User Premise."
snmp-server sys-descr "Extreme VDX Switch."
snmp-server enable trap
snmp-server community private groupname admin
snmp-server community public groupname user
snmp-server view All 1 included
snmp-server group admin v1 read All write All notify All
snmp-server group public v1 read All
snmp-server group public v2c read All
snmp-server group user v1 read All
snmp-server group user v2c read All
hardware
connector-group 104/0/1
speed LowMixed
!
connector-group 104/0/3
speed LowMixed
!
connector-group 104/0/5
speed LowMixed
!
connector-group 104/0/6
speed LowMixed
!
!
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 fabric-priority priority 0
remap lossless-priority priority 0
!
fcoe
fabric-map default
vlan 1002
san-mode local
priority 3
virtual-fabric 128
fcmap 0E:FC:00
advertisement interval 8000
keep-alive timeout
!
!
interface Vlan 1
!
fabric route mcast rbridge-id 104
!
protocol lldp
advertise dcbx-fcoe-app-tlv
advertise dcbx-fcoe-logical-link-tlv
advertise dcbx-tlv
advertise bgp-auto-nbr-tlv
advertise optional-tlv management-address
advertise optional-tlv system-name
system-description Extreme-VDX-VCS 120
!
vlan dot1q tag native
port-profile UpgradedVlanProfile
vlan-profile
switchport
switchport mode trunk
switchport trunk allowed vlan all
!
!
port-profile default
vlan-profile
switchport
switchport mode trunk
switchport trunk native-vlan 1
!
!
port-profile-domain default
port-profile UpgradedVlanProfile
!
class-map cee
!
class-map default
!
rbridge-id 104
switch-attributes chassis-name VDX6740
switch-attributes host-name LEAF4
vrf mgmt-vrf
address-family ipv4 unicast
ip route 0.0.0.0/0 10.26.0.1
!
address-family ipv6 unicast
!
!
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 sfp 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
resource-monitor cpu enable
resource-monitor memory enable threshold 100 action raslog
resource-monitor process memory enable alarm 500 critical 600
no protocol vrrp
no protocol vrrp-extended
hardware-profile tcam default
hardware-profile route-table default maximum_paths 8 openflow off
hardware-profile kap default
fabric neighbor-discovery
clock timezone America/Los_Angeles
ag
enable
counter reliability 25
timeout fnm 120
pg 0
modes lb
rename pg0
!
!
telnet server use-vrf default-vrf
telnet server use-vrf mgmt-vrf
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
http server use-vrf default-vrf
http server use-vrf mgmt-vrf
fcoe
fcoe-enodes 0
!
!
interface Management 104/0
no tcp burstrate
ip icmp echo-reply
no ip address dhcp
ip address 10.26.7.226/17
ipv6 icmpv6 echo-reply
no ipv6 address autoconfig
no ipv6 address dhcp
vrf forwarding mgmt-vrf
no shutdown
!
interface TenGigabitEthernet 104/0/1
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/2
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/3
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/4
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/5
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/6
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/7
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/8
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/9
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/10
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/11
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/12
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/13
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/14
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/15
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/16
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/17
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/18
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/19
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/20
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/21
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/22
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/23
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/24
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/25
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/26
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/27
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/28
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/29
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/30
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/31
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/32
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/33
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/34
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/35
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/36
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/37
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/38
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/39
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/40
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/41
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/42
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/43
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/44
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/45
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/46
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/47
fabric isl enable
fabric trunk enable
no shutdown
!
interface TenGigabitEthernet 104/0/48
fabric isl enable
fabric trunk enable
no shutdown
!
interface FortyGigabitEthernet 104/0/49
fabric isl enable
fabric trunk enable
no shutdown
!
interface FortyGigabitEthernet 104/0/50
fabric isl enable
fabric trunk enable
no shutdown
!
interface FortyGigabitEthernet 104/0/51
fabric isl enable
fabric trunk enable
no shutdown
!
interface FortyGigabitEthernet 104/0/52
fabric isl enable
fabric trunk enable
no shutdown
!

View file

@ -0,0 +1,17 @@
Network Operating System Software
Network Operating System Version: 7.2.0
Copyright (c) 1995-2017 Brocade Communications Systems, Inc.
Firmware name: 7.2.0
Build Time: 10:52:47 Jul 10, 2017
Install Time: 01:32:03 Jan 5, 2018
Kernel: 2.6.34.6
BootProm: 1.0.1
Control Processor: e500mc with 4096 MB of memory
Slot Name Primary/Secondary Versions Status
---------------------------------------------------------------------------
SW/0 NOS 7.2.0 ACTIVE*
7.2.0
SW/1 NOS 7.2.0 STANDBY
7.2.0

View file

@ -0,0 +1,136 @@
#
# (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 <http://www.gnu.org/licenses/>.
#
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 nos
FIXTURE_DIR = b'%s/fixtures/nos' % (
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 TestPluginCLIConfNOS(unittest.TestCase):
""" Test class for NOS CLI Conf Methods
"""
def setUp(self):
self._mock_connection = MagicMock()
self._mock_connection.send.side_effect = _connection_side_effect
self._cliconf = nos.Cliconf(self._mock_connection)
self.maxDiff = None
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': 'nos',
'network_os_model': 'BR-VDX6740',
'network_os_version': '7.2.0',
}
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)
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',
'enable_response_logging',
'disable_response_logging'
],
'device_info': {
'network_os_model': 'BR-VDX6740',
'network_os_version': '7.2.0',
'network_os': 'nos'
}
}
self.assertEqual(
mock_capabilities,
capabilities
)