From 250b975704603ac5cde3455c0eeb0389f218b815 Mon Sep 17 00:00:00 2001 From: Nathaniel Case Date: Mon, 25 Apr 2016 16:04:19 -0400 Subject: [PATCH] Clarify exception handling in net modules (#15507) * Clarify exception handling in EOS Also modify to EOS to standardize modules. It makes vimdiff a lot less angry * Move IOS exception handling into Cli * Move IOS-XR exception handling into Cli * Move JUNOS exception handling into Cli * Move NXOS exception handling into Cli And reorganize to make it match the other modules * Move OpenSwitch exception handling into Cli More speculative restructuring here --- lib/ansible/module_utils/eos.py | 52 +++++++++++------------ lib/ansible/module_utils/ios.py | 47 ++++++++++----------- lib/ansible/module_utils/iosxr.py | 42 ++++++++++--------- lib/ansible/module_utils/junos.py | 33 +++++++++------ lib/ansible/module_utils/nxos.py | 48 +++++++++++++--------- lib/ansible/module_utils/openswitch.py | 57 +++++++++++++++----------- 6 files changed, 150 insertions(+), 129 deletions(-) diff --git a/lib/ansible/module_utils/eos.py b/lib/ansible/module_utils/eos.py index 0321b6e2dc9..b89ad261796 100644 --- a/lib/ansible/module_utils/eos.py +++ b/lib/ansible/module_utils/eos.py @@ -16,12 +16,11 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # -import os import re -from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.shell import Shell, Command, HAS_PARAMIKO +from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception +from ansible.module_utils.shell import Shell, ShellError, Command, HAS_PARAMIKO from ansible.module_utils.netcfg import parse from ansible.module_utils.urls import fetch_url @@ -57,6 +56,7 @@ CLI_ERRORS_RE = [ re.compile(r"[^\r\n]\/bin\/(?:ba)?sh") ] + def to_list(val): if isinstance(val, (list, tuple)): return list(val) @@ -151,12 +151,11 @@ class Cli(object): key_filename = self.module.params['ssh_keyfile'] try: - self.shell = Shell(prompts_re=CLI_PROMPTS_RE, - errors_re=CLI_ERRORS_RE) - self.shell.open(host, port=port, username=username, - password=password, key_filename=key_filename) - except Exception, exc: - msg = 'failed to connect to %s:%s - %s' % (host, port, str(exc)) + self.shell = Shell(prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE) + self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename) + except ShellError: + e = get_exception() + msg = 'failed to connect to %s:%s - %s' % (host, port, str(e)) self.module.fail_json(msg=msg) def authorize(self): @@ -164,7 +163,11 @@ class Cli(object): self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd)) def send(self, commands): - return self.shell.send(commands) + try: + return self.shell.send(commands) + except ShellError: + e = get_exception() + self.module.fail_json(msg=e.message, commands=commands) class NetworkModule(AnsibleModule): @@ -194,19 +197,18 @@ class NetworkModule(AnsibleModule): self.params[key] = value def connect(self): + cls = globals().get(str(self.params['transport']).capitalize()) try: - cls = globals().get(str(self.params['transport']).capitalize()) self.connection = cls(self) + except TypeError: + e = get_exception() + self.fail_json(msg=e.message) - self.connection.connect() - self.connection.send('terminal length 0') + self.connection.connect() + self.connection.send('terminal length 0') - if self.params['authorize']: - self.connection.authorize() - except AttributeError, exc: - self.fail_json(msg=exc.message) - except Exception, exc: - self.fail_json(msg=exc.message) + if self.params['authorize']: + self.connection.authorize() self._connected = True @@ -233,15 +235,13 @@ class NetworkModule(AnsibleModule): return self.execute(command) def execute(self, commands, **kwargs): - try: - if not self.connected: - self.connect() - return self.connection.send(commands, **kwargs) - except Exception, exc: - self.fail_json(msg=exc.message, commands=commands) + if not self.connected: + self.connect() + return self.connection.send(commands, **kwargs) def disconnect(self): self.connection.close() + self._connected = False def parse_config(self, cfg): return parse(cfg, indent=3) @@ -267,9 +267,7 @@ def get_module(**kwargs): module = NetworkModule(**kwargs) - # HAS_PARAMIKO is set by module_utils/shell.py if module.params['transport'] == 'cli' and not HAS_PARAMIKO: module.fail_json(msg='paramiko is required but does not appear to be installed') return module - diff --git a/lib/ansible/module_utils/ios.py b/lib/ansible/module_utils/ios.py index f467b7cf519..1ba6c1416e8 100644 --- a/lib/ansible/module_utils/ios.py +++ b/lib/ansible/module_utils/ios.py @@ -19,7 +19,7 @@ import re -from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception from ansible.module_utils.shell import Shell, ShellError, Command, HAS_PARAMIKO from ansible.module_utils.netcfg import parse @@ -78,11 +78,11 @@ class Cli(object): timeout = self.module.params['timeout'] try: - self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE, - errors_re=CLI_ERRORS_RE) + self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE) self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename, timeout=timeout) - except Exception, exc: - msg = 'failed to connect to %s:%s - %s' % (host, port, str(exc)) + except ShellError: + e = get_exception() + msg = 'failed to connect to %s:%s - %s' % (host, port, str(e)) self.module.fail_json(msg=msg) def authorize(self): @@ -90,7 +90,11 @@ class Cli(object): self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd)) def send(self, commands): - return self.shell.send(commands) + try: + return self.shell.send(commands) + except ShellError: + e = get_exception() + self.module.fail_json(msg=e.message, commands=commands) class NetworkModule(AnsibleModule): @@ -115,21 +119,20 @@ class NetworkModule(AnsibleModule): super(NetworkModule, self)._load_params() provider = self.params.get('provider') or dict() for key, value in provider.items(): - if key in NET_COMMON_ARGS.keys(): + if key in NET_COMMON_ARGS: if self.params.get(key) is None and value is not None: self.params[key] = value def connect(self): - try: - self.connection = Cli(self) - self.connection.connect() - self.connection.send('terminal length 0') - if self.params['authorize']: - self.connection.authorize() - self._connected = True + self.connection = Cli(self) + + self.connection.connect() + self.connection.send('terminal length 0') - except Exception, exc: - self.fail_json(msg=exc.message) + if self.params['authorize']: + self.connection.authorize() + + self._connected = True def configure(self, commands): commands = to_list(commands) @@ -139,14 +142,9 @@ class NetworkModule(AnsibleModule): return responses def execute(self, commands, **kwargs): - try: - if not self.connected: - self.connect() - return self.connection.send(commands, **kwargs) - except ShellError, exc: - self.fail_json(msg=exc.message, command=exc.command) - except Exception, exc: - self.fail_json(msg=exc.message, commands=commands) + if not self.connected: + self.connect() + return self.connection.send(commands, **kwargs) def disconnect(self): self.connection.close() @@ -172,7 +170,6 @@ def get_module(**kwargs): module = NetworkModule(**kwargs) - # HAS_PARAMIKO is set by module_utils/shell.py if not HAS_PARAMIKO: module.fail_json(msg='paramiko is required but does not appear to be installed') diff --git a/lib/ansible/module_utils/iosxr.py b/lib/ansible/module_utils/iosxr.py index 7d01b36dbac..b84a44e96b5 100644 --- a/lib/ansible/module_utils/iosxr.py +++ b/lib/ansible/module_utils/iosxr.py @@ -19,8 +19,8 @@ import re -from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.shell import Shell, HAS_PARAMIKO +from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception +from ansible.module_utils.shell import Shell, ShellError, HAS_PARAMIKO from ansible.module_utils.netcfg import parse NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) @@ -49,6 +49,7 @@ CLI_ERRORS_RE = [ re.compile(r"'[^']' +returned error code: ?\d+"), ] + def to_list(val): if isinstance(val, (list, tuple)): return list(val) @@ -57,6 +58,7 @@ def to_list(val): else: return list() + class Cli(object): def __init__(self, module): @@ -74,12 +76,18 @@ class Cli(object): try: self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE) self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename) - except Exception, exc: - msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc)) + except ShellError: + e = get_exception() + msg = 'failed to connect to %s:%s - %s' % (host, port, str(e)) self.module.fail_json(msg=msg) def send(self, commands): - return self.shell.send(commands) + try: + return self.shell.send(commands) + except ShellError: + e = get_exception() + self.module.fail_json(msg=e.message, commands=commands) + class NetworkModule(AnsibleModule): @@ -103,18 +111,16 @@ class NetworkModule(AnsibleModule): super(NetworkModule, self)._load_params() provider = self.params.get('provider') or dict() for key, value in provider.items(): - if key in NET_COMMON_ARGS.keys(): + if key in NET_COMMON_ARGS: if self.params.get(key) is None and value is not None: self.params[key] = value def connect(self): - try: - self.connection = Cli(self) - self.connection.connect() - self.connection.send('terminal length 0') - self._connected = True - except Exception, exc: - self.fail_json(msg=exc.message) + self.connection = Cli(self) + + self.connection.connect() + self.connection.send('terminal length 0') + self._connected = True def configure(self, commands): commands = to_list(commands) @@ -126,12 +132,9 @@ class NetworkModule(AnsibleModule): return responses def execute(self, commands, **kwargs): - try: - if not self.connected: - self.connect() - return self.connection.send(commands) - except ShellError, exc: - self.fail_json(msg=exc.message, command=exc.command) + if not self.connected: + self.connect() + return self.connection.send(commands, **kwargs) def disconnect(self): self.connection.close() @@ -157,4 +160,3 @@ def get_module(**kwargs): module.fail_json(msg='paramiko is required but does not appear to be installed') return module - diff --git a/lib/ansible/module_utils/junos.py b/lib/ansible/module_utils/junos.py index 5e81b55c41d..61d5ca73ce4 100644 --- a/lib/ansible/module_utils/junos.py +++ b/lib/ansible/module_utils/junos.py @@ -16,10 +16,11 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # + from distutils.version import LooseVersion -from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.shell import Shell, HAS_PARAMIKO +from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception +from ansible.module_utils.shell import Shell, ShellError, HAS_PARAMIKO from ansible.module_utils.netcfg import parse try: @@ -67,6 +68,7 @@ def to_list(val): else: return list() + def xml_to_json(val): if isinstance(val, basestring): return jxmlease.parse(val) @@ -91,13 +93,12 @@ class Cli(object): password = self.module.params['password'] key_filename = self.module.params['ssh_keyfile'] - self.shell = Shell() - try: - self.shell.open(host, port=port, username=username, - password=password, key_filename=key_filename) - except Exception, exc: - msg = 'failed to connect to %s:%s - %s' % (host, port, str(exc)) + self.shell = Shell() + self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename) + except ShellError: + e = get_exception() + msg = 'failed to connect to %s:%s - %s' % (host, port, str(e)) self.module.fail_json(msg=msg) if self.shell._matched_prompt.strip().endswith('%'): @@ -105,7 +106,11 @@ class Cli(object): self.shell.send('set cli screen-length 0') def run_commands(self, commands, **kwargs): - return self.shell.send(commands) + try: + return self.shell.send(commands) + except ShellError: + e = get_exception() + self.module.fail_json(msg=e.message, commands=commands) def configure(self, commands, **kwargs): commands = to_list(commands) @@ -281,13 +286,18 @@ class NetworkModule(AnsibleModule): super(NetworkModule, self)._load_params() provider = self.params.get('provider') or dict() for key, value in provider.items(): - if key in NET_COMMON_ARGS.keys(): + if key in NET_COMMON_ARGS: if self.params.get(key) is None and value is not None: self.params[key] = value def connect(self): cls = globals().get(str(self.params['transport']).capitalize()) - self.connection = cls(self) + try: + self.connection = cls(self) + except TypeError: + e = get_exception() + self.fail_json(msg=e.message) + self.connection.connect() msg = 'connecting to host: {username}@{host}:{port}'.format(**self.params) @@ -336,7 +346,6 @@ def get_module(**kwargs): module = NetworkModule(**kwargs) - # HAS_PARAMIKO is set by module_utils/shell.py if module.params['transport'] == 'cli' and not HAS_PARAMIKO: module.fail_json(msg='paramiko is required but does not appear to be installed') elif module.params['transport'] == 'netconf' and not HAS_PYEZ: diff --git a/lib/ansible/module_utils/nxos.py b/lib/ansible/module_utils/nxos.py index 0ff29c53bec..578c7e5f993 100644 --- a/lib/ansible/module_utils/nxos.py +++ b/lib/ansible/module_utils/nxos.py @@ -19,10 +19,10 @@ import re -from ansible.module_utils.urls import fetch_url -from ansible.module_utils.shell import Shell, HAS_PARAMIKO -from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception +from ansible.module_utils.shell import Shell, ShellError, HAS_PARAMIKO from ansible.module_utils.netcfg import parse +from ansible.module_utils.urls import fetch_url NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) @@ -60,6 +60,7 @@ CLI_ERRORS_RE = [ re.compile(r"unknown command") ] + def to_list(val): if isinstance(val, (list, tuple)): return list(val) @@ -68,6 +69,7 @@ def to_list(val): else: return list() + class Nxapi(object): def __init__(self, module): @@ -169,14 +171,19 @@ class Cli(object): key_filename = self.module.params['ssh_keyfile'] try: - self.shell = Shell(prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE, kickstart=False) + self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE) self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename) - except Exception, exc: - msg = 'failed to connect to %s:%s - %s' % (host, port, str(exc)) + except ShellError: + e = get_exception() + msg = 'failed to connect to %s:%s - %s' % (host, port, str(e)) self.module.fail_json(msg=msg) def send(self, commands, encoding='text'): - return self.shell.send(commands) + try: + return self.shell.send(commands) + except ShellError: + e = get_exception() + self.module.fail_json(msg=e.message, commands=commands) class NetworkModule(AnsibleModule): @@ -201,15 +208,17 @@ class NetworkModule(AnsibleModule): super(NetworkModule, self)._load_params() provider = self.params.get('provider') or dict() for key, value in provider.items(): - if key in NET_COMMON_ARGS.keys(): + if key in NET_COMMON_ARGS: if self.params.get(key) is None and value is not None: self.params[key] = value def connect(self): - if self.params['transport'] == 'nxapi': - self.connection = Nxapi(self) - else: - self.connection = Cli(self) + cls = globals().get(str(self.params['transport']).capitalize()) + try: + self.connection = cls(self) + except TypeError: + e = get_exception() + self.fail_json(msg=e.message) self.connection.connect() @@ -218,6 +227,12 @@ class NetworkModule(AnsibleModule): self._connected = True + def configure(self, commands): + commands = to_list(commands) + if self.params['transport'] == 'cli': + return self.configure_cli(commands) + else: + return self.execute(commands, command_type='cli_conf') def configure_cli(self, commands): commands = to_list(commands) @@ -226,13 +241,6 @@ class NetworkModule(AnsibleModule): responses.pop(0) return responses - def configure(self, commands): - commands = to_list(commands) - if self.params['transport'] == 'cli': - return self.configure_cli(commands) - else: - return self.execute(commands, command_type='cli_conf') - def execute(self, commands, **kwargs): if not self.connected: self.connect() @@ -240,6 +248,7 @@ class NetworkModule(AnsibleModule): def disconnect(self): self.connection.close() + self._connected = False def parse_config(self, cfg): return parse(cfg, indent=2) @@ -262,7 +271,6 @@ def get_module(**kwargs): module = NetworkModule(**kwargs) - # HAS_PARAMIKO is set by module_utils/shell.py if module.params['transport'] == 'cli' and not HAS_PARAMIKO: module.fail_json(msg='paramiko is required but does not appear to be installed') diff --git a/lib/ansible/module_utils/openswitch.py b/lib/ansible/module_utils/openswitch.py index 91187423572..0a9f13dcedd 100644 --- a/lib/ansible/module_utils/openswitch.py +++ b/lib/ansible/module_utils/openswitch.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # + import re import time import json @@ -29,22 +30,22 @@ try: except ImportError: HAS_OPS = False -from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.urls import fetch_url -from ansible.module_utils.shell import Shell, HAS_PARAMIKO +from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception +from ansible.module_utils.shell import Shell, ShellError, HAS_PARAMIKO from ansible.module_utils.netcfg import parse +from ansible.module_utils.urls import fetch_url NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) NET_COMMON_ARGS = dict( - host=dict(), + host=dict(required=True), port=dict(type='int'), username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), password=dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD'])), ssh_keyfile=dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), - use_ssl=dict(default=True, type='bool'), validate_certs=dict(default=True, type='bool'), transport=dict(default='ssh', choices=['ssh', 'cli', 'rest']), + use_ssl=dict(default=True, type='bool'), provider=dict(type='dict') ) @@ -159,12 +160,21 @@ class Cli(object): password = self.module.params['password'] key_filename = self.module.params['ssh_keyfile'] - self.shell = Shell() - self.shell.open(host, port=port, username=username, password=password, - key_filename=key_filename) + try: + self.shell = Shell() + self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename) + except ShellError: + e = get_exception() + msg = 'failed to connect to %s:%s - %s' % (host, port, str(e)) + self.module.fail_json(msg=msg) + + def send(self, commands, encoding='text'): + try: + return self.shell.send(commands) + except ShellError: + e = get_exception() + self.module.fail_json(msg=e.message, commands=commands) - def send(self, commands): - return self.shell.send(commands) class NetworkModule(AnsibleModule): @@ -185,38 +195,37 @@ class NetworkModule(AnsibleModule): super(NetworkModule, self)._load_params() provider = self.params.get('provider') or dict() for key, value in provider.items(): - if key in NET_COMMON_ARGS.keys(): + if key in NET_COMMON_ARGS: if self.params.get(key) is None and value is not None: self.params[key] = value def connect(self): - if self.params['transport'] == 'rest': - self.connection = Rest(self) - elif self.params['transport'] == 'cli': - self.connection = Cli(self) + cls = globals().get(str(self.params['transport']).capitalize()) + try: + self.connection = cls(self) + except TypeError: + e = get_exception() + self.fail_json(msg=e.message) self.connection.connect() - def configure(self, config): + def configure(self, commands): if self.params['transport'] == 'cli': - commands = to_list(config) + commands = to_list(commands) commands.insert(0, 'configure terminal') responses = self.execute(commands) responses.pop(0) return responses elif self.params['transport'] == 'rest': path = '/system/full-configuration' - return self.connection.put(path, data=config) + return self.connection.put(path, data=commands) else: if not self._opsidl: (self._extschema, self._opsidl) = get_opsidl() - ops.dc.write(config, self._extschema, self._opsidl) + ops.dc.write(commands, self._extschema, self._opsidl) def execute(self, commands, **kwargs): - try: - return self.connection.send(commands, **kwargs) - except Exception, exc: - self.fail_json(msg=exc.message, commands=commands) + return self.connection.send(commands, **kwargs) def disconnect(self): self.connection.close() @@ -251,7 +260,6 @@ def get_module(**kwargs): if not HAS_OPS and module.params['transport'] == 'ssh': module.fail_json(msg='could not import ops library') - # HAS_PARAMIKO is set by module_utils/shell.py if module.params['transport'] == 'cli' and not HAS_PARAMIKO: module.fail_json(msg='paramiko is required but does not appear to be installed') @@ -259,4 +267,3 @@ def get_module(**kwargs): module.connect() return module -