From 9d4a3599b8a288af522343a07e5c1c61885bb355 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Thu, 16 Feb 2017 20:26:48 -0500 Subject: [PATCH] bug fixes and updates for eos connections (#21534) * refactors supports_sessions to a property * exposes supports_sessions as a toplevel function * adds open_shell() to network_cli * implements open_shell() in eos action plugin --- lib/ansible/module_utils/eos.py | 30 +++++++++++++------ lib/ansible/plugins/action/eos.py | 28 +++++++++++++---- lib/ansible/plugins/connection/network_cli.py | 7 +++-- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/ansible/module_utils/eos.py b/lib/ansible/module_utils/eos.py index f283fe4cbbc..622331cb8da 100644 --- a/lib/ansible/module_utils/eos.py +++ b/lib/ansible/module_utils/eos.py @@ -94,6 +94,15 @@ class Cli: def __init__(self, module): self._module = module self._device_configs = {} + self._session_support = None + + @property + def supports_sessions(self): + if self._session_support is not None: + return self._session_support + rc, out, err = self.exec_command('show configuration sessions') + self._session_support = rc == 0 + return self._session_support def exec_command(self, command): if isinstance(command, dict): @@ -195,7 +204,7 @@ class Cli: except ValueError: pass - if not all((bool(use_session), self.supports_sessions())): + if not all((bool(use_session), self.supports_sessions)): return configure(self, commands) conn = get_connection(self) @@ -255,6 +264,14 @@ class Eapi: else: self._enable = 'enable' + @property + def supports_sessions(self): + if self._session_support: + return self._session_support + response = self.send_request(['show configuration sessions']) + self._session_support = 'error' not in response + return self._session_support + def _request_builder(self, commands, output, reqid=None): params = dict(version=1, cmds=commands, format=output) return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params) @@ -344,13 +361,6 @@ class Eapi: self._device_configs[cmd] = cfg return cfg - def supports_sessions(self): - if self._session_support: - return self._session_support - response = self.send_request(['show configuration sessions']) - self._session_support = 'error' not in response - return self._session_support - def configure(self, commands): """Sends the ordered set of commands to the device """ @@ -371,7 +381,7 @@ class Eapi: fallback to using configure() to load the commands. If that happens, there will be no returned diff or session values """ - if not supports_sessions(): + if not self.supports_sessions: return configure(self, commands) session = 'ansible_%s' % int(time.time()) @@ -406,6 +416,8 @@ class Eapi: is_json = lambda x: str(x).endswith('| json') is_text = lambda x: not str(x).endswith('| json') +supports_sessions = lambda x: get_connection(module).supports_sessions + def get_config(module, flags=[]): conn = get_connection(module) return conn.get_config(flags) diff --git a/lib/ansible/plugins/action/eos.py b/lib/ansible/plugins/action/eos.py index 9b6d1484bb7..ef067392c57 100644 --- a/lib/ansible/plugins/action/eos.py +++ b/lib/ansible/plugins/action/eos.py @@ -31,20 +31,28 @@ from ansible.module_utils.eos import eos_argument_spec from ansible.module_utils.basic import AnsibleFallbackNotFound from ansible.module_utils._text import to_bytes +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + display = Display() + class ActionModule(_ActionModule): def run(self, tmp=None, task_vars=None): if self._play_context.connection != 'local': return dict( - fail=True, + failed=True, msg='invalid connection specified, expected connection=local, ' 'got %s' % self._play_context.connection ) provider = self.load_provider() - transport = provider['transport'] + transport = provider['transport'] or 'cli' + + display.vvv('transport is %s' % transport, self._play_context.remote_addr) - if not transport or 'cli' in transport: + if transport == 'cli': pc = copy.deepcopy(self._play_context) pc.connection = 'network_cli' pc.network_os = 'eos' @@ -53,12 +61,14 @@ class ActionModule(_ActionModule): pc.become = provider['authorize'] or False pc.become_pass = provider['auth_pass'] + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin) socket_path = self._get_socket_path(pc) if not os.path.exists(socket_path): # start the connection if it isn't started - connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin) - connection.exec_command('EXEC: show version') + rc, out, err = connection.exec_command('open_shell()') + if not rc == 0: + return {'failed': True, 'msg': 'unable to open shell', 'rc': rc} task_vars['ansible_socket'] = socket_path @@ -80,7 +90,13 @@ class ActionModule(_ActionModule): self._play_context.become = False self._play_context.become_method = None - return super(ActionModule, self).run(tmp, task_vars) + result = super(ActionModule, self).run(tmp, task_vars) + + if transport == 'cli': + display.vvv('closing cli shell connection', self._play_context.remote_addr) + rc, out, err = connection.exec_command('close_shell()') + + return result def _get_socket_path(self, play_context): ssh = connection_loader.get('ssh', class_only=True) diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py index 1a283cb87a3..ef3d2017b91 100644 --- a/lib/ansible/plugins/connection/network_cli.py +++ b/lib/ansible/plugins/connection/network_cli.py @@ -99,7 +99,6 @@ class Connection(_Connection): @ensure_connect def open_shell(self): - """Opens the vty shell on the connection""" self._shell = self.ssh.invoke_shell() self._shell.settimeout(self._play_context.timeout) @@ -112,6 +111,8 @@ class Connection(_Connection): auth_pass = self._play_context.become_pass self._terminal.on_authorize(passwd=auth_pass) + return (0, 'ok', '') + def close(self): display.vvv('closing connection', host=self._play_context.remote_addr) self.close_shell() @@ -127,7 +128,7 @@ class Connection(_Connection): self._shell.close() self._shell = None - return (0, 'shell closed', '') + return (0, 'ok', '') def receive(self, obj=None): """Handles receiving of output from command""" @@ -233,6 +234,8 @@ class Connection(_Connection): if obj['command'] == 'close_shell()': return self.close_shell() + elif obj['command'] == 'open_shell()': + return self.open_shell() elif obj['command'] == 'prompt()': return (0, self._matched_prompt, '') elif obj['command'] == 'history()':