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
This commit is contained in:
Peter Sprygada 2017-02-16 20:26:48 -05:00 committed by GitHub
parent a01288859d
commit 9d4a3599b8
3 changed files with 48 additions and 17 deletions

View file

@ -94,6 +94,15 @@ class Cli:
def __init__(self, module): def __init__(self, module):
self._module = module self._module = module
self._device_configs = {} 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): def exec_command(self, command):
if isinstance(command, dict): if isinstance(command, dict):
@ -195,7 +204,7 @@ class Cli:
except ValueError: except ValueError:
pass pass
if not all((bool(use_session), self.supports_sessions())): if not all((bool(use_session), self.supports_sessions)):
return configure(self, commands) return configure(self, commands)
conn = get_connection(self) conn = get_connection(self)
@ -255,6 +264,14 @@ class Eapi:
else: else:
self._enable = 'enable' 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): def _request_builder(self, commands, output, reqid=None):
params = dict(version=1, cmds=commands, format=output) params = dict(version=1, cmds=commands, format=output)
return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params) return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params)
@ -344,13 +361,6 @@ class Eapi:
self._device_configs[cmd] = cfg self._device_configs[cmd] = cfg
return 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): def configure(self, commands):
"""Sends the ordered set of commands to the device """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, fallback to using configure() to load the commands. If that happens,
there will be no returned diff or session values there will be no returned diff or session values
""" """
if not supports_sessions(): if not self.supports_sessions:
return configure(self, commands) return configure(self, commands)
session = 'ansible_%s' % int(time.time()) session = 'ansible_%s' % int(time.time())
@ -406,6 +416,8 @@ class Eapi:
is_json = lambda x: str(x).endswith('| json') is_json = lambda x: str(x).endswith('| json')
is_text = lambda x: not 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=[]): def get_config(module, flags=[]):
conn = get_connection(module) conn = get_connection(module)
return conn.get_config(flags) return conn.get_config(flags)

View file

@ -31,20 +31,28 @@ from ansible.module_utils.eos import eos_argument_spec
from ansible.module_utils.basic import AnsibleFallbackNotFound from ansible.module_utils.basic import AnsibleFallbackNotFound
from ansible.module_utils._text import to_bytes 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): class ActionModule(_ActionModule):
def run(self, tmp=None, task_vars=None): def run(self, tmp=None, task_vars=None):
if self._play_context.connection != 'local': if self._play_context.connection != 'local':
return dict( return dict(
fail=True, failed=True,
msg='invalid connection specified, expected connection=local, ' msg='invalid connection specified, expected connection=local, '
'got %s' % self._play_context.connection 'got %s' % self._play_context.connection
) )
provider = self.load_provider() provider = self.load_provider()
transport = provider['transport'] transport = provider['transport'] or 'cli'
if not transport or 'cli' in transport: display.vvv('transport is %s' % transport, self._play_context.remote_addr)
if transport == 'cli':
pc = copy.deepcopy(self._play_context) pc = copy.deepcopy(self._play_context)
pc.connection = 'network_cli' pc.connection = 'network_cli'
pc.network_os = 'eos' pc.network_os = 'eos'
@ -53,12 +61,14 @@ class ActionModule(_ActionModule):
pc.become = provider['authorize'] or False pc.become = provider['authorize'] or False
pc.become_pass = provider['auth_pass'] 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) socket_path = self._get_socket_path(pc)
if not os.path.exists(socket_path): if not os.path.exists(socket_path):
# start the connection if it isn't started # start the connection if it isn't started
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin) rc, out, err = connection.exec_command('open_shell()')
connection.exec_command('EXEC: show version') if not rc == 0:
return {'failed': True, 'msg': 'unable to open shell', 'rc': rc}
task_vars['ansible_socket'] = socket_path task_vars['ansible_socket'] = socket_path
@ -80,7 +90,13 @@ class ActionModule(_ActionModule):
self._play_context.become = False self._play_context.become = False
self._play_context.become_method = None 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): def _get_socket_path(self, play_context):
ssh = connection_loader.get('ssh', class_only=True) ssh = connection_loader.get('ssh', class_only=True)

View file

@ -99,7 +99,6 @@ class Connection(_Connection):
@ensure_connect @ensure_connect
def open_shell(self): def open_shell(self):
"""Opens the vty shell on the connection"""
self._shell = self.ssh.invoke_shell() self._shell = self.ssh.invoke_shell()
self._shell.settimeout(self._play_context.timeout) self._shell.settimeout(self._play_context.timeout)
@ -112,6 +111,8 @@ class Connection(_Connection):
auth_pass = self._play_context.become_pass auth_pass = self._play_context.become_pass
self._terminal.on_authorize(passwd=auth_pass) self._terminal.on_authorize(passwd=auth_pass)
return (0, 'ok', '')
def close(self): def close(self):
display.vvv('closing connection', host=self._play_context.remote_addr) display.vvv('closing connection', host=self._play_context.remote_addr)
self.close_shell() self.close_shell()
@ -127,7 +128,7 @@ class Connection(_Connection):
self._shell.close() self._shell.close()
self._shell = None self._shell = None
return (0, 'shell closed', '') return (0, 'ok', '')
def receive(self, obj=None): def receive(self, obj=None):
"""Handles receiving of output from command""" """Handles receiving of output from command"""
@ -233,6 +234,8 @@ class Connection(_Connection):
if obj['command'] == 'close_shell()': if obj['command'] == 'close_shell()':
return self.close_shell() return self.close_shell()
elif obj['command'] == 'open_shell()':
return self.open_shell()
elif obj['command'] == 'prompt()': elif obj['command'] == 'prompt()':
return (0, self._matched_prompt, '') return (0, self._matched_prompt, '')
elif obj['command'] == 'history()': elif obj['command'] == 'history()':