Bug fixes- Dellos modules (#63272)

This commit is contained in:
Dhivyap 2019-10-22 14:06:34 +05:30 committed by Ganesh Nalawade
parent 4745d45711
commit 2e65c1ebb7
10 changed files with 295 additions and 165 deletions

View file

@ -29,12 +29,12 @@
# 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 re
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.connection import exec_command
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible.module_utils.network.common.config import NetworkConfig, ConfigLine
_DEVICE_CONFIGS = {}
@ -71,6 +71,35 @@ dellos10_top_spec = {
dellos10_argument_spec.update(dellos10_top_spec)
def get_provider_argspec():
return dellos10_provider_spec
def get_connection(module):
if hasattr(module, '_dellos10_connection'):
return module._dellos10_connection
capabilities = get_capabilities(module)
network_api = capabilities.get('network_api')
if network_api == 'cliconf':
module._dellos10_connection = Connection(module._socket_path)
else:
module.fail_json(msg='Invalid connection type %s' % network_api)
return module._dellos10_connection
def get_capabilities(module):
if hasattr(module, '_dellos10_capabilities'):
return module._dellos10_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._dellos10_capabilities = json.loads(capabilities)
return module._dellos10_capabilities
def check_args(module, warnings):
pass
@ -78,7 +107,7 @@ def check_args(module, warnings):
def get_config(module, flags=None):
flags = [] if flags is None else flags
cmd = 'show running-config '
cmd = 'show running-configuration '
cmd += ' '.join(flags)
cmd = cmd.strip()
@ -93,26 +122,12 @@ def get_config(module, flags=None):
return cfg
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):
responses = list()
commands = to_commands(module, to_list(commands))
for cmd in commands:
cmd = module.jsonify(cmd)
rc, out, err = exec_command(module, cmd)
if check_rc and rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc)
responses.append(to_text(out, errors='surrogate_or_strict'))
return responses
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):

View file

@ -30,11 +30,12 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import re
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.connection import exec_command
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible.module_utils.network.common.config import NetworkConfig, ConfigLine, ignore_line
_DEVICE_CONFIGS = {}
@ -71,6 +72,35 @@ dellos6_top_spec = {
dellos6_argument_spec.update(dellos6_top_spec)
def get_provider_argspec():
return dellos6_provider_spec
def get_connection(module):
if hasattr(module, '_dellos6_connection'):
return module._dellos6_connection
capabilities = get_capabilities(module)
network_api = capabilities.get('network_api')
if network_api == 'cliconf':
module._dellos6_connection = Connection(module._socket_path)
else:
module.fail_json(msg='Invalid connection type %s' % network_api)
return module._dellos6_connection
def get_capabilities(module):
if hasattr(module, '_dellos6_capabilities'):
return module._dellos6_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._dellos6_capabilities = json.loads(capabilities)
return module._dellos6_capabilities
def check_args(module, warnings):
pass
@ -93,26 +123,12 @@ def get_config(module, flags=None):
return cfg
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):
responses = list()
commands = to_commands(module, to_list(commands))
for cmd in commands:
cmd = module.jsonify(cmd)
rc, out, err = exec_command(module, cmd)
if check_rc and rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc)
responses.append(to_text(out, errors='surrogate_or_strict'))
return responses
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):
@ -152,16 +168,16 @@ def os6_parse(lines, indent=None, comment_tokens=None):
re.compile(r'datacenter-bridging.*$'),
re.compile(r'line (console|telnet|ssh).*$'),
re.compile(r'ip ssh !(server).*$'),
re.compile(r'(ip|mac|management|arp) access-list.*$'),
re.compile(r'ip dhcp pool.*$'),
re.compile(r'ip vrf !(forwarding).*$'),
re.compile(r'(ip|mac|management|arp) access-list.*$'),
re.compile(r'ipv6 (dhcp pool|router).*$'),
re.compile(r'mail-server.*$'),
re.compile(r'vpc domain.*$'),
re.compile(r'router.*$'),
re.compile(r'route-map.*$'),
re.compile(r'policy-map.*$'),
re.compile(r'class-map match-all.*$'),
re.compile(r'((class-map match-(all|any))|(class\s)).*$'),
re.compile(r'captive-portal.*$'),
re.compile(r'admin-profile.*$'),
re.compile(r'link-dependency group.*$'),
@ -172,7 +188,9 @@ def os6_parse(lines, indent=None, comment_tokens=None):
re.compile(r'address-family.*$'),
re.compile(r'spanning-tree mst configuration.*$'),
re.compile(r'logging (?!.*(cli-command|buffered|console|email|facility|file|monitor|protocol|snmp|source-interface|traps|web-session)).*$'),
re.compile(r'(radius-server|tacacs-server) host.*$')]
re.compile(r'(radius-server|tacacs-server) host.*$'),
re.compile(r'radius server (auth|acct).*$'),
re.compile(r'aaa server radius dynamic-author.*$')]
childline = re.compile(r'^exit$')
config = list()
@ -184,8 +202,6 @@ def os6_parse(lines, indent=None, comment_tokens=None):
cfg = ConfigLine(text)
cfg.raw = line
if not text or ignore_line(text, comment_tokens):
parent = list()
children = []
continue
else:

View file

@ -29,12 +29,12 @@
# 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 re
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.connection import exec_command
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible.module_utils.network.common.config import NetworkConfig, ConfigLine
_DEVICE_CONFIGS = {}
@ -71,6 +71,35 @@ dellos9_top_spec = {
dellos9_argument_spec.update(dellos9_top_spec)
def get_provider_argspec():
return dellos9_provider_spec
def get_connection(module):
if hasattr(module, '_dellos9_connection'):
return module._dellos9_connection
capabilities = get_capabilities(module)
network_api = capabilities.get('network_api')
if network_api == 'cliconf':
module._dellos9_connection = Connection(module._socket_path)
else:
module.fail_json(msg='Invalid connection type %s' % network_api)
return module._dellos9_connection
def get_capabilities(module):
if hasattr(module, '_dellos9_capabilities'):
return module._dellos9_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._dellos9_capabilities = json.loads(capabilities)
return module._dellos9_capabilities
def check_args(module, warnings):
pass
@ -93,26 +122,12 @@ def get_config(module, flags=None):
return cfg
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):
responses = list()
commands = to_commands(module, to_list(commands))
for cmd in commands:
cmd = module.jsonify(cmd)
rc, out, err = exec_command(module, cmd)
if check_rc and rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc)
responses.append(to_text(out, errors='surrogate_or_strict'))
return responses
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):

View file

@ -35,7 +35,11 @@ options:
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.
the number of retries has expired. 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).
Common answers are 'yes' or "\\r" (carriage return, must be
double quotes). See examples.
type: list
required: true
wait_for:
@ -43,7 +47,7 @@ options:
- 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 I(retries), the task fails.
within the configured number of retries, the task fails.
See examples.
type: list
version_added: "2.2"
@ -57,7 +61,7 @@ options:
satisfied.
type: str
default: all
choices: [ all, any ]
choices: [ 'all', 'any' ]
version_added: "2.5"
retries:
description:
@ -102,6 +106,13 @@ tasks:
wait_for:
- result[0] contains OS10
- result[1] contains Ethernet
- name: run commands that require answering a prompt
dellos10_command:
commands:
- command: 'reload'
prompt: '[confirm yes/no]: ?$'
answer: 'no'
"""
RETURN = """
@ -128,39 +139,26 @@ warnings:
"""
import time
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.parsing import Conditional
from ansible.module_utils.network.common.utils import transform_commands, to_lines
from ansible.module_utils.network.dellos10.dellos10 import run_commands
from ansible.module_utils.network.dellos10.dellos10 import dellos10_argument_spec, check_args
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
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 index, item in enumerate(commands):
if module.check_mode and not item['command'].startswith('show'):
warnings.append(
'only show commands are supported when using check mode, not '
'executing `%s`' % item['command']
)
elif item['command'].startswith('conf'):
module.fail_json(
msg='dellos10_command does not support running config mode '
'commands. Please use dellos10_config instead'
)
commands = transform_commands(module)
if module.check_mode:
for item in list(commands):
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
@ -191,8 +189,11 @@ def main():
result['warnings'] = warnings
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
try:
conditionals = [Conditional(c) for c in wait_for]
except AttributeError as exc:
module.fail_json(msg=to_text(exc))
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
@ -219,7 +220,6 @@ def main():
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update({
'changed': False,
'stdout': responses,
'stdout_lines': list(to_lines(responses))
})

View file

@ -34,7 +34,11 @@ options:
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.
the number of retries has expired. 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).
Common answers are 'yes' or "\\r" (carriage return, must be
double quotes). See examples.
type: list
required: true
wait_for:
@ -42,7 +46,7 @@ options:
- 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 I(retries), the task fails.
within the configured number of retries, the task fails.
See examples.
type: list
version_added: "2.2"
@ -56,7 +60,7 @@ options:
satisfied.
type: str
default: all
choices: [ all, any ]
choices: [ 'all', 'any' ]
version_added: "2.5"
retries:
description:
@ -101,6 +105,13 @@ tasks:
wait_for:
- result[0] contains Dell
- result[1] contains Access
- name: run commands that require answering a prompt
dellos6_command:
commands:
- command: 'copy running-config startup-config'
prompt: '[confirm yes/no]: ?$'
answer: 'yes'
"""
RETURN = """
@ -128,39 +139,26 @@ warnings:
import time
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.parsing import Conditional
from ansible.module_utils.network.common.utils import transform_commands, to_lines
from ansible.module_utils.network.dellos6.dellos6 import run_commands
from ansible.module_utils.network.dellos6.dellos6 import dellos6_argument_spec, check_args
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
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 index, item in enumerate(commands):
if module.check_mode and not item['command'].startswith('show'):
warnings.append(
'only show commands are supported when using check mode, not '
'executing `%s`' % item['command']
)
elif item['command'].startswith('conf'):
module.fail_json(
msg='dellos6_command does not support running config mode '
'commands. Please use dellos6_config instead'
)
commands = transform_commands(module)
if module.check_mode:
for item in list(commands):
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
@ -190,8 +188,11 @@ def main():
result['warnings'] = warnings
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
try:
conditionals = [Conditional(c) for c in wait_for]
except AttributeError as exc:
module.fail_json(msg=to_text(exc))
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
@ -218,7 +219,6 @@ def main():
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update({
'changed': False,
'stdout': responses,
'stdout_lines': list(to_lines(responses))
})

View file

@ -35,7 +35,11 @@ options:
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.
the number of retries has expired. 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).
Common answers are 'yes' or "\\r" (carriage return, must be
double quotes). See examples.
type: list
required: true
wait_for:
@ -43,7 +47,7 @@ options:
- 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 I(retries), the task fails.
within the configured number of retries, the task fails.
See examples.
type: list
version_added: "2.2"
@ -57,7 +61,7 @@ options:
satisfied.
type: str
default: all
choices: [ all, any ]
choices: [ 'all', 'any' ]
version_added: "2.5"
retries:
description:
@ -111,6 +115,13 @@ tasks:
wait_for:
- result[0] contains OS9
- result[1] contains Loopback
- name: run commands that require answering a prompt
dellos9_command:
commands:
- command: 'copy running-config startup-config'
prompt: '[confirm yes/no]: ?$'
answer: 'yes'
"""
RETURN = """
@ -137,39 +148,26 @@ warnings:
"""
import time
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.parsing import Conditional
from ansible.module_utils.network.common.utils import transform_commands, to_lines
from ansible.module_utils.network.dellos9.dellos9 import run_commands
from ansible.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args
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
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 index, item in enumerate(commands):
if module.check_mode and not item['command'].startswith('show'):
warnings.append(
'only show commands are supported when using check mode, not '
'executing `%s`' % item['command']
)
elif item['command'].startswith('conf'):
module.fail_json(
msg='dellos9_command does not support running config mode '
'commands. Please use dellos9_config instead'
)
commands = transform_commands(module)
if module.check_mode:
for item in list(commands):
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
@ -200,8 +198,11 @@ def main():
result['warnings'] = warnings
wait_for = module.params['wait_for'] or list()
conditionals = [Conditional(c) for c in wait_for]
try:
conditionals = [Conditional(c) for c in wait_for]
except AttributeError as exc:
module.fail_json(msg=to_text(exc))
retries = module.params['retries']
interval = module.params['interval']
match = module.params['match']
@ -228,7 +229,6 @@ def main():
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update({
'changed': False,
'stdout': responses,
'stdout_lines': list(to_lines(responses))
})

View file

@ -36,7 +36,9 @@ import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -87,3 +89,27 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
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', to_text(e))
responses.append(out)
return responses

View file

@ -36,7 +36,9 @@ import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -58,7 +60,7 @@ class Cliconf(CliconfBase):
if match:
device_info['network_os_model'] = match.group(1)
reply = self.get('show running-config | grep hostname')
reply = self.get('show running-config | include hostname')
data = to_text(reply, errors='surrogate_or_strict').strip()
match = re.search(r'^hostname (.+)', data, re.M)
if match:
@ -87,3 +89,27 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
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', to_text(e))
responses.append(out)
return responses

View file

@ -36,7 +36,9 @@ import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.cliconf import CliconfBase, enable_mode
@ -87,3 +89,27 @@ class Cliconf(CliconfBase):
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
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', to_text(e))
responses.append(out)
return responses

View file

@ -55,6 +55,12 @@ class TerminalModule(TerminalBase):
terminal_inital_prompt_newline = False
def on_open_shell(self):
try:
self._exec_cli_command(b'terminal length 0')
except AnsibleConnectionFailure:
raise AnsibleConnectionFailure('unable to set terminal parameters')
def on_become(self, passwd=None):
if self._get_prompt().endswith(b'#'):
return