diff --git a/lib/ansible/module_utils/network/edgeswitch/__init__.py b/lib/ansible/module_utils/network/edgeswitch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/edgeswitch/edgeswitch.py b/lib/ansible/module_utils/network/edgeswitch/edgeswitch.py new file mode 100644 index 00000000000..b00cfd62dae --- /dev/null +++ b/lib/ansible/module_utils/network/edgeswitch/edgeswitch.py @@ -0,0 +1,167 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2018 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +import json +import re + +from copy import deepcopy + +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, ConnectionError +from ansible.module_utils.network.common.utils import remove_default_spec + +_DEVICE_CONFIGS = {} + + +def build_aggregate_spec(element_spec, required, *extra_spec): + aggregate_spec = deepcopy(element_spec) + for elt in required: + aggregate_spec[elt] = dict(required=True) + remove_default_spec(aggregate_spec) + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec) + ) + argument_spec.update(element_spec) + argument_spec.update(*extra_spec) + return argument_spec + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + d = item.copy() + obj.append(d) + else: + obj.append(module.params) + + return obj + + +def get_connection(module): + if hasattr(module, '_edgeswitch_connection'): + return module._edgeswitch_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._edgeswitch_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._edgeswitch_connection + + +def get_capabilities(module): + if hasattr(module, '_edgeswitch_capabilities'): + return module._edgeswitch_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._edgeswitch_capabilities = json.loads(capabilities) + return module._edgeswitch_capabilities + + +def get_defaults_flag(module): + connection = get_connection(module) + try: + out = connection.get_defaults_flag() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + return to_text(out, errors='surrogate_then_replace').strip() + + +def get_config(module, flags=None): + flag_str = ' '.join(to_list(flags)) + + try: + return _DEVICE_CONFIGS[flag_str] + except KeyError: + connection = get_connection(module) + try: + out = connection.get_config(flags=flags) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + cfg = to_text(out, errors='surrogate_then_replace').strip() + _DEVICE_CONFIGS[flag_str] = cfg + return cfg + + +def get_interfaces_config(module): + config = get_config(module) + lines = config.split('\n') + interfaces = {} + interface = None + for line in lines: + if line == 'exit': + if interface: + interfaces[interface[0]] = interface + interface = None + elif interface: + interface.append(line) + else: + match = re.match(r'^interface (.*)$', line) + if match: + interface = list() + interface.append(line) + + return interfaces + + +def to_commands(module, commands): + spec = { + 'command': dict(key=True), + 'prompt': dict(), + 'answer': dict() + } + transform = ComplexList(spec, module) + return transform(commands) + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def load_config(module, commands): + 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)) diff --git a/lib/ansible/modules/network/edgeswitch/__init__.py b/lib/ansible/modules/network/edgeswitch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/modules/network/edgeswitch/edgeswitch_facts.py b/lib/ansible/modules/network/edgeswitch/edgeswitch_facts.py new file mode 100644 index 00000000000..305cba68234 --- /dev/null +++ b/lib/ansible/modules/network/edgeswitch/edgeswitch_facts.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = """ +--- +module: edgeswitch_facts +version_added: "2.8" +author: "Frederic Bor (@f-bor)" +short_description: Collect facts from remote devices running Edgeswitch +description: + - Collects a base set of device facts from a remote device that + is running Ubiquiti Edgeswitch. This module prepends all of the + base network fact keys with C(ansible_net_). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +notes: + - Tested against Edgeswitch 1.7.4 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + required: false + default: '!config' +""" + +EXAMPLES = """ +# Collect all facts from the device +- edgeswitch_facts: + gather_subset: all + +# Collect only the config and default facts +- edgeswitch_facts: + gather_subset: + - config + +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: string +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: string +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: string +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: string + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: string + +# interfaces +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +""" +import re + +from ansible.module_utils.network.edgeswitch.edgeswitch import run_commands +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ['show version', 'show sysinfo'] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = self.parse_version(data) + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts['model'] = self.parse_model(data) + self.facts['hostname'] = self.parse_hostname(self.responses[1]) + + def parse_version(self, data): + match = re.search(r'Software Version\.+ (.*)', data) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'System Name\.+ (.*)', data) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'Machine Model\.+ (.*)', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + match = re.search(r'Serial Number\.+ (.*)', data) + if match: + return match.group(1) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces description', + 'show interfaces status all' + ] + + def populate(self): + super(Interfaces, self).populate() + + interfaces = {} + + data = self.responses[0] + self.parse_interfaces_description(data, interfaces) + + data = self.responses[1] + self.parse_interfaces_status(data, interfaces) + + self.facts['interfaces'] = interfaces + + def parse_interfaces_description(self, data, interfaces): + for line in data.split('\n'): + match = re.match(r'(\d\/\d+)\s+(\w+)\s+(\w+)', line) + if match: + name = match.group(1) + interface = {} + interface['operstatus'] = match.group(2) + interface['lineprotocol'] = match.group(3) + interface['description'] = line[30:] + interfaces[name] = interface + + def parse_interfaces_status(self, data, interfaces): + for line in data.split('\n'): + match = re.match(r'(\d\/\d+)', line) + if match: + name = match.group(1) + interface = interfaces[name] + interface['physicalstatus'] = line[61:71].strip() + interface['mediatype'] = line[73:91].strip() + + +FACT_SUBSETS = dict( + default=Default, + config=Config, + interfaces=Interfaces, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/cliconf/edgeswitch.py b/lib/ansible/plugins/cliconf/edgeswitch.py new file mode 100644 index 00000000000..ddc7b2a259b --- /dev/null +++ b/lib/ansible/plugins/cliconf/edgeswitch.py @@ -0,0 +1,134 @@ +# +# (c) 2018 Red Hat 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 time +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.network.common.config import dumps +from ansible.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode +from ansible.module_utils.common._collections_compat import Mapping + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'edgeswitch' + reply = self.get(command='show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Software Version\.+ (.*)', data) + if match: + device_info['network_os_version'] = match.group(1).strip(',') + + match = re.search(r'^Machine Model\.+ (.*)', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + match = re.search(r'System Name\.+ (.*)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + @enable_mode + def get_config(self, source='running', flags=None): + if source not in ('running', 'startup'): + raise ValueError("fetching configuration from %s is not supported" % source) + + if source == 'running': + cmd = 'show running-config ' + else: + cmd = 'show startup-config ' + + if flags: + cmd += ' '.join(to_list(flags)) + cmd = cmd.strip() + + return self.send_command(cmd) + + @enable_mode + def edit_config(self, commands): + resp = {} + + results = [] + requests = [] + self.send_command('configure') + for line in to_list(commands): + if not isinstance(line, Mapping): + line = {'command': line} + + cmd = line['command'] + if cmd != 'end' and cmd[0] != '!': + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command('end') + + resp['request'] = requests + resp['response'] = results + return resp + + def get(self, command=None, prompt=None, answer=None, sendonly=False, output=None, check_all=False): + if not command: + raise ValueError('must provide value of command to execute') + if output: + raise ValueError("'output' value %s is not supported for get" % output) + + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, check_all=check_all) + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + ['run_commands'] + result['network_api'] = 'cliconf' + result['device_info'] = self.get_device_info() + return json.dumps(result) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', e) + + responses.append(out) + + return responses diff --git a/lib/ansible/plugins/terminal/edgeswitch.py b/lib/ansible/plugins/terminal/edgeswitch.py new file mode 100644 index 00000000000..27ac674d85a --- /dev/null +++ b/lib/ansible/plugins/terminal/edgeswitch.py @@ -0,0 +1,87 @@ +# +# (c) 2018 Red Hat 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 +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"\(([^\(\)]+)\) [>#]$"), + re.compile(br"\(([^\(\)]+)\) \(([^\(\)]+)\)#$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error"), + re.compile(br"% ?Bad secret"), + 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"An invalid") + ] + + def on_open_shell(self): + return + + def on_become(self, passwd=None): + prompt = self._get_prompt() + if prompt and prompt.endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + cmd[u'prompt'] = to_text(r"[\r\n]?[Pp]assword: ?$", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.endswith(b'#'): + raise AnsibleConnectionFailure('failed to elevate privilege to enable mode still at prompt [%s]' % prompt) + + cmd = {u'command': u'terminal length 0'} + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + prompt = self._get_prompt() + if prompt is None or not prompt.endswith(b'#'): + raise AnsibleConnectionFailure('failed to setup terminal in enable mode') + + except AnsibleConnectionFailure as e: + prompt = self._get_prompt() + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if b'(Config' in prompt: + self._exec_cli_command(b'end') + self._exec_cli_command(b'exit') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'exit') diff --git a/test/units/modules/network/edgeswitch/__init__.py b/test/units/modules/network/edgeswitch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/units/modules/network/edgeswitch/edgeswitch_module.py b/test/units/modules/network/edgeswitch/edgeswitch_module.py new file mode 100644 index 00000000000..1731bc6817e --- /dev/null +++ b/test/units/modules/network/edgeswitch/edgeswitch_module.py @@ -0,0 +1,86 @@ +# (c) 2018 Red Hat 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 f: + data = f.read() + + try: + data = json.loads(data) + except: + pass + + fixture_data[path] = data + return data + + +class TestEdgeswitchModule(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/edgeswitch/fixtures/edgeswitch_facts_show_interfaces_description b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_interfaces_description new file mode 100644 index 00000000000..b6249b78e10 --- /dev/null +++ b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_interfaces_description @@ -0,0 +1,26 @@ +Interface Admin Link Description +--------- --------- ------ ---------------------------------------------------------------- +0/1 Enable Up VMOTION ESX1 +0/2 Enable Up DATA ESX1 +0/3 Enable Up VMOTION ESX2 +0/4 Enable Up DATA ESX2 +0/5 Enable Up VMOTION ESX3 +0/6 Enable Up DATA ESX3 +0/7 Enable Up VMOTION ESX4 +0/8 Enable Up DATA ESX4 +0/9 Enable Up SAVE +0/10 Enable Down +0/11 Enable Down +0/12 Enable Down +0/13 Enable Down +0/14 Enable Down +0/15 Enable Up UPLINK VIDEO WITH A VERY LONG DESCRIPTION THAT HELPS NO ONE +0/16 Enable Up UPLINK LOCAL +3/1 Enable Down +3/2 Enable Down +3/3 Enable Down +3/4 Enable Down +3/5 Enable Down +3/6 Enable Down +4/1 Enable Up + diff --git a/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_interfaces_status_all b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_interfaces_status_all new file mode 100644 index 00000000000..eeddc6ea3d6 --- /dev/null +++ b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_interfaces_status_all @@ -0,0 +1,31 @@ + Link Physical Physical Media Flow Control +Port Name State Mode Status Type Status +--------- ---------------------------- ------ ---------- ---------- ------------------ ------------ +0/1 VMOTION ESX1 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/2 DATA ESX1 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/3 VMOTION ESX2 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/4 DATA ESX2 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/5 VMOTION ESX3 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/6 DATA ESX3 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/7 VMOTION ESX4 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/8 DATA ESX4 Up Auto D 10G Full 2.5G-BaseFX Inactive +0/9 SAVE Up Auto D 10G Full 2.5G-BaseFX Inactive +0/10 Down Auto D 2.5G-BaseFX Inactive +0/11 Down Auto D 2.5G-BaseFX Inactive +0/12 Down Auto D 2.5G-BaseFX Inactive +0/13 Down Auto D 2.5G-BaseFX Inactive +0/14 Down Auto Unknown Inactive +0/15 Down Auto Unknown Inactive +0/15 UPLINK VIDEO WITH A VERY LON Up Auto 1000 Full Unknown Inactive +0/16 UPLINK LOCAL Up Auto 1000 Full Unknown Inactive +3/1 Down +3/2 Down +3/3 Down +3/4 Down +3/5 Down +3/6 Down +4/1 Up 10 Half 10 Half + +Flow Control:Disabled + + diff --git a/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_sysinfo b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_sysinfo new file mode 100644 index 00000000000..c51f2eee724 --- /dev/null +++ b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_sysinfo @@ -0,0 +1,7 @@ +System Description............................. EdgeSwitch 16-Port 10G, 1.7.4.5075842, Linux 3.6.5, 1.0.0.4872137 +System Name.................................... sw_test_1 +System Location................................ +System Contact................................. +System Object ID............................... 1.3.6.1.4.1.4413 +System Up Time................................. 174 days 19 hrs 0 mins 51 secs +Current SNTP Synchronized Time................. Oct 20 22:53:01 2018 UTC diff --git a/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_version b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_version new file mode 100644 index 00000000000..334abc75f24 --- /dev/null +++ b/test/units/modules/network/edgeswitch/fixtures/edgeswitch_facts_show_version @@ -0,0 +1,8 @@ +Switch: 1 + +System Description............................. EdgeSwitch 16-Port 10G, 1.7.4.5075842, Linux 3.6.5, 1.0.0.4872137 +Machine Type................................... EdgeSwitch 16-Port 10G +Machine Model.................................. ES-16-XG +Serial Number.................................. F09FC2EFD310 +Burned In MAC Address.......................... F0:9F:C2:EF:D3:10 +Software Version............................... 1.7.4.5075842 diff --git a/test/units/modules/network/edgeswitch/test_edgeswitch_facts.py b/test/units/modules/network/edgeswitch/test_edgeswitch_facts.py new file mode 100644 index 00000000000..712af25a8bd --- /dev/null +++ b/test/units/modules/network/edgeswitch/test_edgeswitch_facts.py @@ -0,0 +1,73 @@ +# (c) 2018 Red Hat 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 units.compat.mock import patch +from ansible.modules.network.edgeswitch import edgeswitch_facts +from units.modules.utils import set_module_args +from .edgeswitch_module import TestEdgeswitchModule, load_fixture + + +class TestEdgeswitchFactsModule(TestEdgeswitchModule): + + module = edgeswitch_facts + + def setUp(self): + super(TestEdgeswitchFactsModule, self).setUp() + self.mock_run_commands = patch('ansible.modules.network.edgeswitch.edgeswitch_facts.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestEdgeswitchFactsModule, self).tearDown() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + module = args + commands = kwargs['commands'] + output = list() + + for command in commands: + filename = str(command).split(' | ')[0].replace(' ', '_') + output.append(load_fixture('edgeswitch_facts_%s' % filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_edgeswitch_facts_default(self): + set_module_args(dict(gather_subset=['all', '!interfaces', '!config'])) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 5) + self.assertEqual(facts['ansible_net_hostname'], 'sw_test_1') + self.assertEqual(facts['ansible_net_serialnum'], 'F09FC2EFD310') + self.assertEqual(facts['ansible_net_version'], '1.7.4.5075842') + + def test_edgeswitch_facts_interfaces(self): + set_module_args(dict(gather_subset='interfaces')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 6) + self.assertEqual(facts['ansible_net_interfaces']['0/1']['operstatus'], 'Enable') + self.assertEqual(facts['ansible_net_interfaces']['0/2']['mediatype'], '2.5G-BaseFX') + self.assertEqual(facts['ansible_net_interfaces']['0/3']['physicalstatus'], '10G Full') + self.assertEqual(facts['ansible_net_interfaces']['0/4']['lineprotocol'], 'Up') + self.assertEqual(facts['ansible_net_interfaces']['0/15']['description'], 'UPLINK VIDEO WITH A VERY LONG DESCRIPTION THAT HELPS NO ONE')