Ansible 2.5 feature support for dellos9 (#34880)

* Add ansible-2.5 support for dellos9

* Fix ansible-test issues and update copyright

* fix ansible-test errors in dellos9_config

* fix izip attribute error in python3

* fix python3 dict.keys() issue

* Remove waitfor aliases in dellos9_command
This commit is contained in:
Dhivyap 2018-01-23 19:29:27 +05:30 committed by John R Barker
commit 19ff2f4e8c
23 changed files with 2356 additions and 155 deletions

View file

@ -123,8 +123,7 @@ def load_config(module, commands):
for command in to_list(commands):
if command == 'end':
continue
cmd = {'command': command, 'prompt': WARNING_PROMPTS_RE, 'answer': 'yes'}
rc, out, err = exec_command(module, module.jsonify(cmd))
rc, out, err = exec_command(module, command)
if rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), command=command, rc=rc)

View file

@ -20,7 +20,7 @@ author: "Dhivya P (@dhivyap)"
short_description: Run commands on remote devices running Dell OS9
description:
- Sends arbitrary commands to a Dell OS9 node and returns the results
read from the device. This module includes an
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.
@ -44,6 +44,19 @@ options:
See examples.
required: false
default: null
version_added: "2.2"
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']
version_added: "2.5"
retries:
description:
- Specifies the number of retries a command should be tried
@ -72,33 +85,21 @@ notes:
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
vars:
cli:
host: "{{ inventory_hostname }}"
username: admin
password: admin
transport: cli
tasks:
- name: run show version on remote devices
dellos9_command:
commands: show version
provider: "{{ cli }}"
- name: run show version and check to see if output contains OS9
dellos9_command:
commands: show version
wait_for: result[0] contains OS9
provider: "{{ cli }}"
- name: run multiple commands on remote nodes
dellos9_command:
commands:
- show version
- show interfaces
provider: "{{ cli }}"
- name: run multiple commands and evaluate the output
dellos9_command:
@ -108,7 +109,6 @@ tasks:
wait_for:
- result[0] contains OS9
- result[1] contains Loopback
provider: "{{ cli }}"
"""
RETURN = """
@ -225,11 +225,11 @@ def main():
msg = 'One or more conditional statements have not be satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result = {
result.update({
'changed': False,
'stdout': responses,
'stdout_lines': list(to_lines(responses))
}
})
module.exit_json(**result)

View file

@ -30,15 +30,17 @@ options:
description:
- The ordered set of commands that should be configured in the
section. The commands must be the exact same commands as found
in the device running-config. Note the configuration
command syntax as the device config parser automatically modifies some commands. This argument is mutually exclusive with I(src).
in the device running-config. Be sure to note the configuration
command syntax as some commands are automatically modified by the
device config parser. This argument is mutually exclusive with I(src).
required: false
default: null
aliases: ['commands']
parents:
description:
- The ordered set of parents that uniquely identify the section
the commands should be checked against. If you omit the parents argument, the commands are checked against the set of top
the commands should be checked against. If the parents argument
is omitted, the commands are checked against the set of top
level or global commands.
required: false
default: null
@ -47,14 +49,15 @@ options:
- Specifies the source path to the file that contains the configuration
or configuration template to load. The path to the source file can
either be the full path on the Ansible control host or a relative
path from the playbook or role root directory. This argument is mutually
exclusive with I(lines), I(parents).
path from the playbook or role root directory. This argument is
mutually exclusive with I(lines).
required: false
default: null
before:
description:
- The ordered set of commands to push on to the command stack if
a change needs to be made. The playbook designer can use this opportunity to perform configuration commands prior to pushing
a change needs to be made. This allows the playbook designer
the opportunity to perform configuration commands prior to pushing
any changes without affecting how the set of commands are matched
against the system.
required: false
@ -62,19 +65,20 @@ options:
after:
description:
- The ordered set of commands to append to the end of the command
stack if a change needs to be made. As with I(before), this
the playbook designer can append a set of commands to be
stack if a change needs to be made. Just like with I(before) this
allows the playbook designer to append a set of commands to be
executed after the command set.
required: false
default: null
match:
description:
- Instructs the module on the way to perform the matching of
the set of commands against the current device config. If you set
match to I(line), commands match line by line. If you set
match to I(strict), command lines match by position. If you set match to I(exact), command lines
must be an equal match. Finally, if you set match to I(none), the
module does not attempt to compare the source configuration with
the set of commands against the current device config. If
match is set to I(line), commands are matched line by line. If
match is set to I(strict), command lines are matched with respect
to position. If match is set to I(exact), command lines
must be an equal match. Finally, if match is set to I(none), the
module will not attempt to compare the source configuration with
the running configuration on the remote device.
required: false
default: line
@ -82,10 +86,10 @@ options:
replace:
description:
- Instructs the module on the way to perform the configuration
on the device. If you set the replace argument to I(line), then
the modified lines push to the device in configuration
mode. If you set the replace argument to I(block), then the entire
command block pushes to the device in configuration mode if any
on the device. If the replace argument is set to I(line) then
the modified lines are pushed to the device in configuration
mode. If the replace argument is set to I(block) then the entire
command block is pushed to the device in configuration mode if any
line is not correct.
required: false
default: line
@ -112,29 +116,32 @@ options:
choices: ['yes', 'no']
config:
description:
- The playbook designer can use the C(config) argument to supply
the base configuration to be used to validate necessary configuration
changes. If you specify this argument, the module
does not download the running-config from the remote node.
- The module, by default, will connect to the remote device and
retrieve the current running-config to use as a base for comparing
against the contents of source. There are times when it is not
desirable to have the task get the current running-config for
every task in a playbook. The I(config) argument allows the
implementer to pass in the configuration to use as the base
config for comparison.
required: false
default: null
backup:
description:
- This argument causes the module to create a full backup of
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
folder in the playbook root directory. If the directory does not
exist, it is created.
required: false
default: no
choices: ['yes', 'no']
type: bool
notes:
- This module requires Dell OS9 version 9.10.0.1P13 or above.
- This module requires to increase the ssh connection rate limit.
Use the following command I(ip ssh connection-rate-limit 60)
to configure the same. This can also be done with the M(dellos9_config) module.
to configure the same. This can also be done with the
M(dellos9_config) module.
"""
EXAMPLES = """
@ -152,7 +159,6 @@ EXAMPLES = """
parents: ['ip access-list extended test']
before: ['no ip access-list extended test']
match: exact
provider: "{{ cli }}"
- dellos9_config:
lines:
@ -163,30 +169,25 @@ EXAMPLES = """
parents: ['ip access-list extended test']
before: ['no ip access-list extended test']
replace: block
provider: "{{ cli }}"
"""
RETURN = """
updates:
description: The set of commands that will be pushed to the remote device.
returned: Always.
returned: always
type: list
sample: ['...', '...']
responses:
description: The set of responses from issuing the commands on the device.
returned: When not check_mode.
sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1']
commands:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['...', '...']
sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1']
saved:
description: Returns whether the configuration is saved to the startup
configuration or not.
returned: When not check_mode.
type: bool
sample: True
backup_path:
description: The full path to the backup file
returned: when backup is yes
@ -207,10 +208,23 @@ def get_candidate(module):
candidate.load(module.params['src'])
elif module.params['lines']:
parents = module.params['parents'] or list()
candidate.add(module.params['lines'], parents=parents)
commands = module.params['lines'][0]
if (isinstance(commands, dict)) and (isinstance(commands['command'], list)):
candidate.add(commands['command'], parents=parents)
elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)):
candidate.add([commands['command']], parents=parents)
else:
candidate.add(module.params['lines'], parents=parents)
return candidate
def get_running_config(module):
contents = module.params['config']
if not contents:
contents = get_config(module)
return contents
def main():
argument_spec = dict(
@ -236,7 +250,6 @@ def main():
mutually_exclusive = [('lines', 'src'),
('parents', 'src')]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
@ -253,48 +266,62 @@ def main():
candidate = get_candidate(module)
if match != 'none':
config = get_config(module)
if parents:
contents = get_sublevel_config(config, module)
config = NetworkConfig(contents=contents, indent=1)
else:
config = NetworkConfig(contents=config, indent=1)
configobjs = candidate.difference(config, match=match, replace=replace)
else:
configobjs = candidate.items
if module.params['backup']:
if not module.check_mode:
result['__backup__'] = get_config(module)
commands = list()
if configobjs:
commands = dumps(configobjs, 'commands')
commands = commands.split('\n')
if any((module.params['lines'], module.params['src'])):
if match != 'none':
config = get_running_config(module)
if parents:
contents = get_sublevel_config(config, module)
config = NetworkConfig(contents=contents, indent=1)
else:
config = NetworkConfig(contents=config, indent=1)
configobjs = candidate.difference(config, match=match, replace=replace)
else:
configobjs = candidate.items
if module.params['before']:
commands[:0] = module.params['before']
if configobjs:
commands = dumps(configobjs, 'commands')
if ((isinstance(module.params['lines'], list)) and
(isinstance(module.params['lines'][0], dict)) and
['prompt', 'answer'].issubset(module.params['lines'][0])):
if module.params['after']:
commands.extend(module.params['after'])
cmd = {'command': commands,
'prompt': module.params['lines'][0]['prompt'],
'answer': module.params['lines'][0]['answer']}
commands = [module.jsonify(cmd)]
else:
commands = commands.split('\n')
if not module.check_mode and module.params['update'] == 'merge':
load_config(module, commands)
if module.params['before']:
commands[:0] = module.params['before']
if module.params['save']:
cmd = {'command': 'copy runing-config startup-config', 'prompt': WARNING_PROMPTS_RE, 'answer': 'yes'}
run_commands(module, [cmd])
result['saved'] = True
if module.params['after']:
commands.extend(module.params['after'])
if not module.check_mode and module.params['update'] == 'merge':
load_config(module, commands)
result['changed'] = True
result['commands'] = commands
result['updates'] = commands
if module.params['save']:
result['changed'] = True
result['updates'] = commands
if not module.check_mode:
cmd = {'command': 'copy running-config startup-config',
'prompt': r'\[confirm yes/no\]:\s?$', 'answer': 'yes'}
run_commands(module, [cmd])
result['saved'] = True
else:
module.warn('Skipping command `copy running-config startup-config`'
'due to check_mode. Configuration not copied to '
'non-volatile storage')
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -23,17 +23,17 @@ description:
- Collects a base set of device facts from a remote device that
is running OS9. This module prepends all of the
base network fact keys with C(ansible_net_<fact>). The facts
module always collects a base set of facts from the device
module will always collect a base set of facts from the device
and can enable or disable collection of additional facts.
extends_documentation_fragment: dellos9
options:
gather_subset:
description:
- When supplied, this argument restricts the facts collected
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
all, hardware, config, and interfaces. You can specify a list of
values to include a larger subset. You can also use values
with an initial M(!) to specify that a specific subset should
all, hardware, 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'
@ -63,72 +63,75 @@ EXAMPLES = """
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device.
returned: Always.
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.
description: The model name returned from the device
returned: always
type: str
ansible_net_serialnum:
description: The serial number of the remote device.
returned: Always.
description: The serial number of the remote device
returned: always
type: str
ansible_net_version:
description: The operating system version running on the remote device.
returned: Always.
description: The operating system version running on the remote device
returned: always
type: str
ansible_net_hostname:
description: The configured hostname of the device.
returned: Always.
description: The configured hostname of the device
returned: always
type: string
ansible_net_image:
description: The image file the device is running.
returned: Always.
description: The image file the device is running
returned: always
type: string
# hardware
ansible_net_filesystems:
description: All file system names available on the device.
returned: When hardware is configured.
description: All file system names available on the device
returned: when hardware is configured
type: list
ansible_net_memfree_mb:
description: The available free memory on the remote device in MB.
returned: When hardware is configured.
description: The available free memory on the remote device in Mb
returned: when hardware is configured
type: int
ansible_net_memtotal_mb:
description: The total memory on the remote device in MB.
returned: When hardware is configured.
description: The total memory on the remote device in Mb
returned: when hardware is configured
type: int
# config
ansible_net_config:
description: The current active config from the device.
returned: When config is configured.
description: The current active config from the device
returned: when config is configured
type: str
# interfaces
ansible_net_all_ipv4_addresses:
description: All IPv4 addresses configured on the device.
returned: When interfaces is configured.
description: All IPv4 addresses configured on the device
returned: when interfaces is configured
type: list
ansible_net_all_ipv6_addresses:
description: All IPv6 addresses configured on the device.
returned: When interfaces is configured.
description: All IPv6 addresses configured on the device
returned: when interfaces is configured
type: list
ansible_net_interfaces:
description: A hash of all interfaces running on the system.
returned: When interfaces is configured.
description: A hash of all interfaces running on the system
returned: when interfaces is configured
type: dict
ansible_net_neighbors:
description: The list of LLDP neighbors from the remote device
returned: When interfaces is configured.
returned: when interfaces is configured
type: dict
"""
import re
import itertools
try:
from itertools import izip
except ImportError:
izip = zip
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.dellos9.dellos9 import run_commands
@ -316,7 +319,7 @@ class Interfaces(FactsBase):
self.facts['interfaces'][key]['ipv6'] = list()
addresses = re.findall(r'\s+(.+), subnet', value, re.M)
subnets = re.findall(r', subnet is (\S+)', value, re.M)
for addr, subnet in itertools.izip(addresses, subnets):
for addr, subnet in izip(addresses, subnets):
ipv6 = dict(address=addr.strip(), subnet=subnet.strip())
self.add_ip_address(addr.strip(), 'ipv6')
self.facts['interfaces'][key]['ipv6'].append(ipv6)

View file

@ -28,8 +28,8 @@ from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection
from ansible.plugins.action.normal import ActionModule as _ActionModule
from ansible.module_utils.network.dellos9.dellos9 import dellos9_provider_spec
from ansible.module_utils.network.common.utils import load_provider
from ansible.module_utils.network.dellos9.dellos9 import dellos9_provider_spec
try:
from __main__ import display
@ -41,40 +41,45 @@ except ImportError:
class ActionModule(_ActionModule):
def run(self, tmp=None, task_vars=None):
socket_path = None
if self._play_context.connection != 'local':
return dict(
failed=True,
msg='invalid connection specified, expected connection=local, '
'got %s' % self._play_context.connection
)
if self._play_context.connection == 'network_cli':
provider = self._task.args.get('provider', {})
if any(provider.values()):
display.warning('provider is unnecessary when using network_cli and will be ignored')
elif self._play_context.connection == 'local':
provider = load_provider(dellos9_provider_spec, self._task.args)
pc = copy.deepcopy(self._play_context)
pc.connection = 'network_cli'
pc.network_os = 'dellos9'
pc.remote_addr = provider['host'] or self._play_context.remote_addr
pc.port = int(provider['port'] or self._play_context.port or 22)
pc.remote_user = provider['username'] or self._play_context.connection_user
pc.password = provider['password'] or self._play_context.password
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
pc.become = provider['authorize'] or False
if pc.become:
pc.become_method = 'enable'
pc.become_pass = provider['auth_pass']
provider = load_provider(dellos9_provider_spec, self._task.args)
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
pc = copy.deepcopy(self._play_context)
pc.connection = 'network_cli'
pc.network_os = 'dellos9'
pc.remote_addr = provider['host'] or self._play_context.remote_addr
pc.port = int(provider['port'] or self._play_context.port or 22)
pc.remote_user = provider['username'] or self._play_context.connection_user
pc.password = provider['password'] or self._play_context.password
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
pc.become = provider['authorize'] or False
pc.become_pass = provider['auth_pass']
socket_path = connection.run()
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
if not socket_path:
return {'failed': True,
'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
socket_path = connection.run()
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
if not socket_path:
return {'failed': True,
'msg': 'unable to open shell. Please see: ' +
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
task_vars['ansible_socket'] = socket_path
# make sure we are in the right cli context which should be
# enable mode and not config module
if socket_path is None:
socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
@ -82,11 +87,5 @@ class ActionModule(_ActionModule):
conn.send_command('exit')
out = conn.get_prompt()
task_vars['ansible_socket'] = socket_path
if self._play_context.become_method == 'enable':
self._play_context.become = False
self._play_context.become_method = None
result = super(ActionModule, self).run(tmp, task_vars)
return result

View file

@ -0,0 +1,82 @@
#
# (c) 2017 Red Hat Inc.
#
# (c) 2017 Dell EMC.
#
# 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, enable_mode
class Cliconf(CliconfBase):
def get_device_info(self):
device_info = {}
device_info['network_os'] = 'dellos9'
reply = self.get(b'show version')
data = to_text(reply, errors='surrogate_or_strict').strip()
match = re.search(r'Software Version (\S+)', data)
if match:
device_info['network_os_version'] = match.group(1)
match = re.search(r'System Type (\S+)', data, re.M)
if match:
device_info['network_os_model'] = match.group(1)
reply = self.get(b'show running-config | grep hostname')
data = to_text(reply, errors='surrogate_or_strict').strip()
match = re.search(r'^hostname (.+)', data, re.M)
if match:
device_info['network_os_hostname'] = match.group(1)
return device_info
@enable_mode
def get_config(self, source='running'):
if source not in ('running', 'startup'):
return self.invalid_params("fetching configuration from %s is not supported" % source)
# if source == 'running':
# cmd = b'show running-config all'
else:
cmd = b'show startup-config'
return self.send_command(cmd)
@enable_mode
def edit_config(self, command):
for cmd in chain([b'configure terminal'], to_list(command), [b'end']):
self.send_command(cmd)
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

@ -37,7 +37,7 @@ class TerminalModule(TerminalBase):
]
terminal_stderr_re = [
re.compile(br"% ?Error: (?:(?!\bdoes not exist\b)(?!\balready exists\b)(?!\bHost not found\b)(?!\bnot active\b).)*$"),
re.compile(br"% ?Error: (?:(?!\bdoes not exist\b)(?!\balready exists\b)(?!\bHost not found\b)(?!\bnot active\b).)*\n"),
re.compile(br"% ?Bad secret"),
re.compile(br"invalid input", re.I),
re.compile(br"(?:incomplete|ambiguous) command", re.I),

View file

@ -0,0 +1,90 @@
# (c) 2016 Red Hat Inc.
#
# (c) 2017 Dell EMC.
#
# 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/>.
# Make coding more python3-ish
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 TestDellos9Module(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['updates']), result['updates'])
else:
self.assertEqual(commands, result['updates'], result['updates'])
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,13 @@
!
hostname router
!
interface fortyGigE 1/6
ip address 1.2.3.4/24
description test string
!
interface fortyGigE 1/7
ip address 6.7.8.9/24
description test string
shutdown
!

View file

@ -0,0 +1,12 @@
!
hostname foo
!
interface fortyGigE 1/6
no ip address
!
interface fortyGigE 1/7
ip address 6.7.8.9/24
description test string
shutdown
!

View file

@ -0,0 +1,10 @@
Size(b) Free(b) Feature Type Flags Prefixes
6429872128 5582319616 FAT32 USERFLASH rw flash:
- - unformatted USERFLASH rw fcmfs:
241172480 91893760 Unknown NFSMOUNT rw nfsmount:
- - - network rw ftp:
- - - network rw tftp:
- - - network rw scp:
- - - network rw http:
- - - network rw https:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
System Type : S6000
System Mode : 1.0
Software Version : 9.12(0.0)
Unit Type Serial Number Part Number Rev Piece Part ID Rev Svc Tag Exprs Svc Code
--------------------------------------------------------------------------------------------------------------
* 0 S6000-01-FE-32T NA 08YWFG A00 CN-08YWFG-28298-3AG-0031 A00 6BJ8VS1 137 581 490 89
0 S6000-PWR-AC NA 0T9FNW A00 CN-0T9FNW-28298-3AG-0119 A00 NA NA
0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0094 A00 NA NA
0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0096 A00 NA NA
0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0095 A00 NA NA
* - Management Unit
Software Protocol Configured
--------------------------------------------------------------
LLDP

View file

@ -0,0 +1,26 @@
fortyGigE 0/16 is down, line protocol is down
IPV6 is enabled
Link Local address: fe80::92b1:1cff:fef4:a28f
Global Unicast address(es):
2001:4898:5808:ffa2::5, subnet is 2001:4898:5808:ffa2::4/126 (MANUAL)
Remaining lifetime: infinite
Global Anycast address(es):
Joined Group address(es):
ff02::1
ff02::2
ff02::1:ff00:5
ff02::1:fff4:a28f
IP MTU is 1500 bytes
ND MTU is 0
ICMP redirects are not sent
DAD is enabled, number of DAD attempts: 3
ND reachable time is 35780 milliseconds
ND base reachable time is 30000 milliseconds
ND advertised reachable time is 0 milliseconds
ND advertised retransmit interval is 0 milliseconds
ND router advertisements are sent every 198 to 600 seconds
ND router advertisements live for 1800 seconds
ND advertised hop limit is 64
IPv6 hop limit for originated packets is 64
IPv6 unicast RPF check is not supported

View file

@ -0,0 +1,35 @@
========================================================================
Local Interface Ma 0/0 has 1 neighbor
Total Frames Out: 1747
Total Frames In: 10333
Total Neighbor information Age outs: 0
Total Multiple Neighbors Detected: 0
Total Frames Discarded: 0
Total In Error Frames: 0
Total Unrecognized TLVs: 0
Total TLVs Discarded: 0
Next packet will be sent after 17 seconds
The neighbors are given below:
-----------------------------------------------------------------------
Remote Chassis ID Subtype: Mac address (4)
Remote Chassis ID: 90:b1:1c:f4:2f:6d
Remote Port Subtype: Interface name (5)
Remote Port ID: TenGigabitEthernet 0/33
Remote Port Description: TenGigabitEthernet 0/33
Local Port ID: ManagementEthernet 0/0
Locally assigned remote Neighbor Index: 1
Remote TTL: 20
Information valid for next 17 seconds
Time since last information change of this neighbor: 14:54:48
Remote System Name: swlab1-maa-tor-A2
Remote System Desc: Dell Real Time Operating System Software. Dell
Operating System Version: 2.0. Dell Application Software Version:
9.11(2.0) Copyright (c) 1999-2017Dell Inc. All Rights Reserved.Build
Time: Tue Apr 25 21:22:59 2017
Existing System Capabilities: Repeater Bridge Router
Enabled System Capabilities: Repeater Bridge Router
Remote Port Vlan ID: 148
Port and Protocol Vlan ID: 148, Capability: Supported, Status: Enabled
---------------------------------------------------------------------------

View file

@ -0,0 +1,4 @@
===========================
Total(b) Used(b) Free(b) Lowest(b) Largest(b)
3203911680 3172120 3200739560 3200673304 3200739560

View file

@ -0,0 +1,238 @@
Current Configuration ...
! Version 9.12(0.0)
! Last configuration change at Thu Jan 11 06:53:29 2018 by admin
!
!
logging coredump stack-unit 0
logging coredump stack-unit 1
logging coredump stack-unit 2
logging coredump stack-unit 3
logging coredump stack-unit 4
logging coredump stack-unit 5
!
hostname Dell
!
protocol lldp
!
redundancy auto-synchronize full
!
enable password 7 b125455cf679b208e79b910e85789edf
!
username admin password 7 1d28e9f33f99cf5c
!
stack-unit 0 quad-port-profile 0,8,16,24,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,100,108,116,124
!
stack-unit 0 provision S6000
!
stack-unit 0 port 0 portmode quad
!
interface TenGigabitEthernet 0/0
no ip address
shutdown
!
interface TenGigabitEthernet 0/1
no ip address
shutdown
!
interface TenGigabitEthernet 0/2
no ip address
shutdown
!
interface TenGigabitEthernet 0/3
no ip address
shutdown
!
interface fortyGigE 0/4
no ip address
shutdown
!
interface fortyGigE 0/8
no ip address
shutdown
!
interface fortyGigE 0/12
no ip address
shutdown
!
interface fortyGigE 0/16
no ip address
ipv6 address 2001:4898:5808:ffa2::5/126
shutdown
!
interface fortyGigE 0/20
no ip address
switchport
ip access-group ipv6-ssh-only in
shutdown
!
interface fortyGigE 0/24
no ip address
switchport
mac access-group ssh-only-mac in
mac access-group ssh-only-mac out
shutdown
!
interface fortyGigE 0/28
no ip address
switchport
mac access-group ssh-only-mac in
mac access-group ssh-only-mac out
shutdown
!
interface fortyGigE 0/32
no ip address
switchport
ip access-group ipv6-ssh-only out
shutdown
!
interface fortyGigE 0/36
no ip address
shutdown
!
interface fortyGigE 0/40
no ip address
shutdown
!
interface fortyGigE 0/44
no ip address
shutdown
!
interface fortyGigE 0/48
no ip address
shutdown
!
interface fortyGigE 0/52
no ip address
shutdown
!
interface fortyGigE 0/56
no ip address
shutdown
!
interface fortyGigE 0/60
no ip address
shutdown
!
interface fortyGigE 0/64
no ip address
shutdown
!
interface fortyGigE 0/68
no ip address
shutdown
!
interface fortyGigE 0/72
no ip address
shutdown
!
interface fortyGigE 0/76
no ip address
shutdown
!
interface fortyGigE 0/80
no ip address
shutdown
!
interface fortyGigE 0/84
no ip address
shutdown
!
interface fortyGigE 0/88
no ip address
shutdown
!
interface fortyGigE 0/92
no ip address
shutdown
!
interface fortyGigE 0/96
no ip address
shutdown
!
interface fortyGigE 0/100
no ip address
shutdown
!
interface fortyGigE 0/104
no ip address
shutdown
!
interface fortyGigE 0/108
no ip address
shutdown
!
interface fortyGigE 0/112
no ip address
shutdown
!
interface fortyGigE 0/116
no ip address
shutdown
!
interface fortyGigE 0/120
no ip address
shutdown
!
interface fortyGigE 0/124
no ip address
shutdown
!
interface ManagementEthernet 0/0
ip address 10.16.148.71/16
no shutdown
!
interface ManagementEthernet 1/0
no shutdown
!
interface ManagementEthernet 2/0
no shutdown
!
interface ManagementEthernet 3/0
no shutdown
!
interface ManagementEthernet 4/0
no shutdown
!
interface ManagementEthernet 5/0
no shutdown
!
interface Vlan 1
!untagged fortyGigE 0/20-32
!
ipv6 access-list ipv6-ssh-only
description ipv6acl
remark 1 ipv6
seq 10 permit ipv6 2001:4898::/32 any
seq 20 permit tcp any eq 2 2404:f801::/32
seq 30 permit tcp any 2a01:110::/31 ack
seq 40 permit tcp any any
!
mac access-list extended ssh-only-mac
description macacl
remark 1 mac
seq 5 permit any any count
seq 6 deny any any
!
ip ssh server enable
!
line console 0
line vty 0
line vty 1
access-class ipv6-ssh-only ipv6
line vty 2
access-class ipv6-ssh-only ipv6
line vty 3
access-class ipv6-ssh-only ipv6
line vty 4
line vty 5
line vty 6
line vty 7
line vty 8
line vty 9
!
reload-type
boot-type normal-reload
config-scr-download enable
!
end

View file

@ -0,0 +1 @@
hostname dellos9_sw1

View file

@ -0,0 +1,18 @@
Dell Real Time Operating System Software
Dell Operating System Version: 2.0
Dell Application Software Version: 9.10(0.1P13)
Copyright (c) 1999-2016 by Dell Inc. All Rights Reserved.
Build Time: Wed Sep 7 23:48:35 2016
Build Path: /sites/eqx/work/swbuild01_1/build01/E9-10-0/SW/SRC
Dell Networking OS uptime is 12 week(s), 6 day(s), 9 hour(s), 20 minute(s)
System image file is "system://A"
System Type: S6000-ON
Control Processor: Intel Centerton with 3 Gbytes (3203911680 bytes) of memory, core(s) 2.
16G bytes of boot flash memory.
1 32-port TE/FG (SI-ON)
32 Forty GigabitEthernet/IEEE 802.3 interface(s)

View file

@ -0,0 +1,110 @@
# (c) 2016 Red Hat Inc.
#
# (c) 2017 Dell EMC.
#
# 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/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.compat.tests.mock import patch
from ansible.modules.network.dellos9 import dellos9_command
from units.modules.utils import set_module_args
from .dellos9_module import TestDellos9Module, load_fixture
class TestDellos9CommandModule(TestDellos9Module):
module = dellos9_command
def setUp(self):
super(TestDellos9CommandModule, self).setUp()
self.mock_run_commands = patch('ansible.modules.network.dellos9.dellos9_command.run_commands')
self.run_commands = self.mock_run_commands.start()
def tearDown(self):
super(TestDellos9CommandModule, 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_dellos9_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('Dell Real Time'))
def test_dellos9_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('Dell Real Time'))
def test_dellos9_command_wait_for(self):
wait_for = 'result[0] contains "Dell Real"'
set_module_args(dict(commands=['show version'], wait_for=wait_for))
self.execute_module()
def test_dellos9_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_dellos9_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_dellos9_command_match_any(self):
wait_for = ['result[0] contains "Dell Real"',
'result[0] contains "test string"']
set_module_args(dict(commands=['show version'], wait_for=wait_for, match='any'))
self.execute_module()
def test_dellos9_command_match_all(self):
wait_for = ['result[0] contains "Dell Real"',
'result[0] contains "Operating System"']
set_module_args(dict(commands=['show version'], wait_for=wait_for, match='all'))
self.execute_module()
def test_dellos9_command_match_all_failure(self):
wait_for = ['result[0] contains "Dell Real"',
'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)

View file

@ -0,0 +1,150 @@
#
# (c) 2016 Red Hat Inc.
#
# (c) 2017 Dell EMC.
#
# 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/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.compat.tests.mock import patch
from ansible.modules.network.dellos9 import dellos9_config
from units.modules.utils import set_module_args
from .dellos9_module import TestDellos9Module, load_fixture
class TestDellos9ConfigModule(TestDellos9Module):
module = dellos9_config
def setUp(self):
super(TestDellos9ConfigModule, self).setUp()
self.mock_get_config = patch('ansible.modules.network.dellos9.dellos9_config.get_config')
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.modules.network.dellos9.dellos9_config.load_config')
self.load_config = self.mock_load_config.start()
self.mock_run_commands = patch('ansible.modules.network.dellos9.dellos9_config.run_commands')
self.run_commands = self.mock_run_commands.start()
def tearDown(self):
super(TestDellos9ConfigModule, self).tearDown()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_run_commands.stop()
def load_fixtures(self, commands=None):
config_file = 'dellos9_config_config.cfg'
self.get_config.return_value = load_fixture(config_file)
self.load_config.return_value = None
def test_dellos9_config_unchanged(self):
src = load_fixture('dellos9_config_config.cfg')
set_module_args(dict(src=src))
self.execute_module()
def test_dellos9_config_src(self):
src = load_fixture('dellos9_config_src.cfg')
set_module_args(dict(src=src))
commands = ['hostname foo', 'interface fortyGigE 1/6',
'no ip address']
self.execute_module(changed=True, commands=commands)
def test_dellos9_config_backup(self):
set_module_args(dict(backup=True))
result = self.execute_module()
self.assertIn('__backup__', result)
def test_dellos9_config_save(self):
set_module_args(dict(save=True))
self.execute_module(changed=True)
self.assertEqual(self.run_commands.call_count, 1)
self.assertEqual(self.get_config.call_count, 0)
self.assertEqual(self.load_config.call_count, 0)
args = self.run_commands.call_args[0][1]
self.assertDictContainsSubset({'command': 'copy running-config startup-config'}, args[0])
# self.assertIn('copy running-config startup-config\r', args)
def test_dellos9_config_lines_wo_parents(self):
set_module_args(dict(lines=['hostname foo']))
commands = ['hostname foo']
self.execute_module(changed=True, commands=commands)
def test_dellos9_config_lines_w_parents(self):
set_module_args(dict(lines=['shutdown'], parents=['interface fortyGigE 1/6']))
commands = ['interface fortyGigE 1/6', 'shutdown']
self.execute_module(changed=True, commands=commands)
def test_dellos9_config_before(self):
set_module_args(dict(lines=['hostname foo'], before=['snmp-server contact bar']))
commands = ['snmp-server contact bar', 'hostname foo']
self.execute_module(changed=True, commands=commands, sort=False)
def test_dellos9_config_after(self):
set_module_args(dict(lines=['hostname foo'], after=['snmp-server contact bar']))
commands = ['hostname foo', 'snmp-server contact bar']
self.execute_module(changed=True, commands=commands, sort=False)
def test_dellos9_config_before_after_no_change(self):
set_module_args(dict(lines=['hostname router'],
before=['snmp-server contact bar'],
after=['snmp-server location chennai']))
self.execute_module()
def test_dellos9_config_config(self):
config = 'hostname localhost'
set_module_args(dict(lines=['hostname router'], config=config))
commands = ['hostname router']
self.execute_module(changed=True, commands=commands)
def test_dellos9_config_replace_block(self):
lines = ['description test string', 'test string']
parents = ['interface fortyGigE 1/6']
set_module_args(dict(lines=lines, replace='block', parents=parents))
commands = parents + lines
self.execute_module(changed=True, commands=commands)
def test_dellos9_config_match_none(self):
lines = ['hostname router']
set_module_args(dict(lines=lines, match='none'))
self.execute_module(changed=True, commands=lines)
def test_dellos9_config_match_none(self):
lines = ['ip address 1.2.3.4/24', 'description test string']
parents = ['interface fortyGigE 1/6']
set_module_args(dict(lines=lines, parents=parents, match='none'))
commands = parents + lines
self.execute_module(changed=True, commands=commands, sort=False)
def test_dellos9_config_match_strict(self):
lines = ['ip address 1.2.3.4/24', 'description test string',
'shutdown']
parents = ['interface fortyGigE 1/6']
set_module_args(dict(lines=lines, parents=parents, match='strict'))
commands = parents + ['shutdown']
self.execute_module(changed=True, commands=commands, sort=False)
def test_dellos9_config_match_exact(self):
lines = ['ip address 1.2.3.4/24', 'description test string',
'shutdown']
parents = ['interface fortyGigE 1/6']
set_module_args(dict(lines=lines, parents=parents, match='exact'))
commands = parents + lines
self.execute_module(changed=True, commands=commands, sort=False)

View file

@ -0,0 +1,106 @@
# (c) 2016 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 <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.compat.tests.mock import patch
from units.modules.utils import set_module_args
from .dellos9_module import TestDellos9Module, load_fixture
from ansible.modules.network.dellos9 import dellos9_facts
class TestDellos9Facts(TestDellos9Module):
module = dellos9_facts
def setUp(self):
super(TestDellos9Facts, self).setUp()
self.mock_run_command = patch(
'ansible.modules.network.dellos9.dellos9_facts.run_commands')
self.run_command = self.mock_run_command.start()
def tearDown(self):
super(TestDellos9Facts, self).tearDown()
self.mock_run_command.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 = obj['command']
except ValueError:
command = item
if '|' in command:
command = str(command).replace('|', '')
filename = str(command).replace(' ', '_')
filename = filename.replace('/', '7')
output.append(load_fixture(filename))
return output
self.run_command.side_effect = load_from_file
def test_dellos9_facts_gather_subset_default(self):
set_module_args(dict())
result = self.execute_module()
ansible_facts = result['ansible_facts']
self.assertIn('hardware', ansible_facts['ansible_net_gather_subset'])
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset'])
self.assertEquals('dellos9_sw1', ansible_facts['ansible_net_hostname'])
self.assertIn('fortyGigE 0/24', ansible_facts['ansible_net_interfaces'].keys())
self.assertEquals(3128820, ansible_facts['ansible_net_memtotal_mb'])
self.assertEquals(3125722, ansible_facts['ansible_net_memfree_mb'])
def test_dellos9_facts_gather_subset_config(self):
set_module_args({'gather_subset': 'config'})
result = self.execute_module()
ansible_facts = result['ansible_facts']
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
self.assertIn('config', ansible_facts['ansible_net_gather_subset'])
self.assertEquals('dellos9_sw1', ansible_facts['ansible_net_hostname'])
self.assertIn('ansible_net_config', ansible_facts)
def test_dellos9_facts_gather_subset_hardware(self):
set_module_args({'gather_subset': 'hardware'})
result = self.execute_module()
ansible_facts = result['ansible_facts']
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
self.assertIn('hardware', ansible_facts['ansible_net_gather_subset'])
self.assertEquals(['flash', 'fcmfs', 'nfsmount', 'ftp', 'tftp', 'scp', 'http', 'https'], ansible_facts['ansible_net_filesystems'])
self.assertEquals(3128820, ansible_facts['ansible_net_memtotal_mb'])
self.assertEquals(3125722, ansible_facts['ansible_net_memfree_mb'])
def test_dellos9_facts_gather_subset_interfaces(self):
set_module_args({'gather_subset': 'interfaces'})
result = self.execute_module()
ansible_facts = result['ansible_facts']
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset'])
self.assertIn('fortyGigE 0/24', ansible_facts['ansible_net_interfaces'].keys())
self.assertEquals(['Ma 0/0'], list(ansible_facts['ansible_net_neighbors'].keys()))
self.assertIn('ansible_net_interfaces', ansible_facts)