mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Move base test case modules to tests.support.case
This commit is contained in:
parent
9af2ca8823
commit
6a638620ba
2 changed files with 165 additions and 298 deletions
|
@ -30,20 +30,6 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
STATE_FUNCTION_RUNNING_RE = re.compile(
|
||||
r'''The function (?:"|')(?P<state_func>.*)(?:"|') is running as PID '''
|
||||
r'(?P<pid>[\d]+) and was started at (?P<date>.*) with jid (?P<jid>[\d]+)'
|
||||
)
|
||||
|
||||
TESTS_DIR = os.path.dirname(os.path.dirname(os.path.normpath(os.path.abspath(__file__))))
|
||||
if os.name == 'nt':
|
||||
TESTS_DIR = TESTS_DIR.replace('\\', '\\\\')
|
||||
CODE_DIR = os.path.dirname(TESTS_DIR)
|
||||
|
||||
# Let's inject CODE_DIR so salt is importable if not there already
|
||||
if CODE_DIR not in sys.path:
|
||||
sys.path.insert(0, CODE_DIR)
|
||||
|
||||
# Import salt tests support dirs
|
||||
from tests.support.paths import * # pylint: disable=wildcard-import
|
||||
from tests.support.processes import * # pylint: disable=wildcard-import
|
||||
|
@ -79,11 +65,6 @@ try:
|
|||
except ImportError:
|
||||
HAS_GITFS = False
|
||||
|
||||
try:
|
||||
from shlex import quote as _quote # pylint: disable=E0611
|
||||
except ImportError:
|
||||
from pipes import quote as _quote
|
||||
|
||||
try:
|
||||
import salt.master
|
||||
except ImportError:
|
||||
|
@ -1272,251 +1253,3 @@ class TestDaemon(object):
|
|||
def sync_minion_grains(self, targets, timeout=None):
|
||||
salt.utils.appendproctitle('SyncMinionGrains')
|
||||
self.sync_minion_modules_('grains', targets, timeout=timeout)
|
||||
|
||||
|
||||
class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
||||
'''
|
||||
Execute a module function
|
||||
'''
|
||||
|
||||
def minion_run(self, _function, *args, **kw):
|
||||
'''
|
||||
Run a single salt function on the 'minion' target and condition
|
||||
the return down to match the behavior of the raw function call
|
||||
'''
|
||||
return self.run_function(_function, args, **kw)
|
||||
|
||||
def run_function(self, function, arg=(), minion_tgt='minion', timeout=25,
|
||||
**kwargs):
|
||||
'''
|
||||
Run a single salt function and condition the return down to match the
|
||||
behavior of the raw function call
|
||||
'''
|
||||
know_to_return_none = (
|
||||
'file.chown', 'file.chgrp', 'ssh.recv_known_host'
|
||||
)
|
||||
if 'f_arg' in kwargs:
|
||||
kwargs['arg'] = kwargs.pop('f_arg')
|
||||
if 'f_timeout' in kwargs:
|
||||
kwargs['timeout'] = kwargs.pop('f_timeout')
|
||||
orig = self.client.cmd(
|
||||
minion_tgt, function, arg, timeout=timeout, kwarg=kwargs
|
||||
)
|
||||
|
||||
if minion_tgt not in orig:
|
||||
self.skipTest(
|
||||
'WARNING(SHOULD NOT HAPPEN #1935): Failed to get a reply '
|
||||
'from the minion \'{0}\'. Command output: {1}'.format(
|
||||
minion_tgt, orig
|
||||
)
|
||||
)
|
||||
elif orig[minion_tgt] is None and function not in know_to_return_none:
|
||||
self.skipTest(
|
||||
'WARNING(SHOULD NOT HAPPEN #1935): Failed to get \'{0}\' from '
|
||||
'the minion \'{1}\'. Command output: {2}'.format(
|
||||
function, minion_tgt, orig
|
||||
)
|
||||
)
|
||||
|
||||
# Try to match stalled state functions
|
||||
orig[minion_tgt] = self._check_state_return(
|
||||
orig[minion_tgt]
|
||||
)
|
||||
|
||||
return orig[minion_tgt]
|
||||
|
||||
def run_state(self, function, **kwargs):
|
||||
'''
|
||||
Run the state.single command and return the state return structure
|
||||
'''
|
||||
ret = self.run_function('state.single', [function], **kwargs)
|
||||
return self._check_state_return(ret)
|
||||
|
||||
def _check_state_return(self, ret):
|
||||
if isinstance(ret, dict):
|
||||
# This is the supposed return format for state calls
|
||||
return ret
|
||||
|
||||
if isinstance(ret, list):
|
||||
jids = []
|
||||
# These are usually errors
|
||||
for item in ret[:]:
|
||||
if not isinstance(item, six.string_types):
|
||||
# We don't know how to handle this
|
||||
continue
|
||||
match = STATE_FUNCTION_RUNNING_RE.match(item)
|
||||
if not match:
|
||||
# We don't know how to handle this
|
||||
continue
|
||||
jid = match.group('jid')
|
||||
if jid in jids:
|
||||
continue
|
||||
|
||||
jids.append(jid)
|
||||
|
||||
job_data = self.run_function(
|
||||
'saltutil.find_job', [jid]
|
||||
)
|
||||
job_kill = self.run_function('saltutil.kill_job', [jid])
|
||||
msg = (
|
||||
'A running state.single was found causing a state lock. '
|
||||
'Job details: \'{0}\' Killing Job Returned: \'{1}\''.format(
|
||||
job_data, job_kill
|
||||
)
|
||||
)
|
||||
ret.append('[TEST SUITE ENFORCED]{0}'
|
||||
'[/TEST SUITE ENFORCED]'.format(msg))
|
||||
return ret
|
||||
|
||||
|
||||
class SyndicCase(TestCase, SaltClientTestCaseMixin):
|
||||
'''
|
||||
Execute a syndic based execution test
|
||||
'''
|
||||
_salt_client_config_file_name_ = 'syndic_master'
|
||||
|
||||
def run_function(self, function, arg=()):
|
||||
'''
|
||||
Run a single salt function and condition the return down to match the
|
||||
behavior of the raw function call
|
||||
'''
|
||||
orig = self.client.cmd('minion', function, arg, timeout=25)
|
||||
if 'minion' not in orig:
|
||||
self.skipTest(
|
||||
'WARNING(SHOULD NOT HAPPEN #1935): Failed to get a reply '
|
||||
'from the minion. Command output: {0}'.format(orig)
|
||||
)
|
||||
return orig['minion']
|
||||
|
||||
|
||||
class ShellCase(ShellTestCase, AdaptedConfigurationTestCaseMixin, ScriptPathMixin):
|
||||
'''
|
||||
Execute a test for a shell command
|
||||
'''
|
||||
|
||||
_code_dir_ = CODE_DIR
|
||||
_script_dir_ = SCRIPT_DIR
|
||||
_python_executable_ = PYEXEC
|
||||
|
||||
def chdir(self, dirname):
|
||||
try:
|
||||
os.chdir(dirname)
|
||||
except OSError:
|
||||
os.chdir(INTEGRATION_TEST_DIR)
|
||||
|
||||
def run_salt(self, arg_str, with_retcode=False, catch_stderr=False, timeout=60): # pylint: disable=W0221
|
||||
'''
|
||||
Execute salt
|
||||
'''
|
||||
arg_str = '-c {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt', arg_str, with_retcode=with_retcode, catch_stderr=catch_stderr, timeout=timeout)
|
||||
|
||||
def run_ssh(self, arg_str, with_retcode=False, catch_stderr=False, timeout=60): # pylint: disable=W0221
|
||||
'''
|
||||
Execute salt-ssh
|
||||
'''
|
||||
arg_str = '-ldebug -W -c {0} -i --priv {1} --roster-file {2} --out=json localhost {3}'.format(
|
||||
self.get_config_dir(),
|
||||
os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'key_test'),
|
||||
os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'roster'),
|
||||
arg_str)
|
||||
return self.run_script('salt-ssh', arg_str, with_retcode=with_retcode, catch_stderr=catch_stderr, timeout=timeout, raw=True)
|
||||
|
||||
def run_run(self, arg_str, with_retcode=False, catch_stderr=False, async=False, timeout=60, config_dir=None):
|
||||
'''
|
||||
Execute salt-run
|
||||
'''
|
||||
arg_str = '-c {0}{async_flag} -t {timeout} {1}'.format(config_dir or self.get_config_dir(),
|
||||
arg_str,
|
||||
timeout=timeout,
|
||||
async_flag=' --async' if async else '')
|
||||
return self.run_script('salt-run', arg_str, with_retcode=with_retcode, catch_stderr=catch_stderr, timeout=60)
|
||||
|
||||
def run_run_plus(self, fun, *arg, **kwargs):
|
||||
'''
|
||||
Execute the runner function and return the return data and output in a dict
|
||||
'''
|
||||
ret = {'fun': fun}
|
||||
from_scratch = bool(kwargs.pop('__reload_config', False))
|
||||
# Have to create an empty dict and then update it, as the result from
|
||||
# self.get_config() is an ImmutableDict which cannot be updated.
|
||||
opts = {}
|
||||
opts.update(self.get_config('client_config', from_scratch=from_scratch))
|
||||
opts_arg = list(arg)
|
||||
if kwargs:
|
||||
opts_arg.append({'__kwarg__': True})
|
||||
opts_arg[-1].update(kwargs)
|
||||
opts.update({'doc': False, 'fun': fun, 'arg': opts_arg})
|
||||
with RedirectStdStreams():
|
||||
runner = salt.runner.Runner(opts)
|
||||
ret['return'] = runner.run()
|
||||
try:
|
||||
ret['jid'] = runner.jid
|
||||
except AttributeError:
|
||||
ret['jid'] = None
|
||||
|
||||
# Compile output
|
||||
# TODO: Support outputters other than nested
|
||||
opts['color'] = False
|
||||
opts['output_file'] = cStringIO()
|
||||
try:
|
||||
salt.output.display_output(ret['return'], opts=opts)
|
||||
ret['out'] = opts['output_file'].getvalue().splitlines()
|
||||
finally:
|
||||
opts['output_file'].close()
|
||||
|
||||
return ret
|
||||
|
||||
def run_key(self, arg_str, catch_stderr=False, with_retcode=False):
|
||||
'''
|
||||
Execute salt-key
|
||||
'''
|
||||
arg_str = '-c {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script(
|
||||
'salt-key',
|
||||
arg_str,
|
||||
catch_stderr=catch_stderr,
|
||||
with_retcode=with_retcode,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
def run_cp(self, arg_str, with_retcode=False, catch_stderr=False):
|
||||
'''
|
||||
Execute salt-cp
|
||||
'''
|
||||
arg_str = '--config-dir {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-cp', arg_str, with_retcode=with_retcode, catch_stderr=catch_stderr, timeout=60)
|
||||
|
||||
def run_call(self, arg_str, with_retcode=False, catch_stderr=False):
|
||||
'''
|
||||
Execute salt-call.
|
||||
'''
|
||||
arg_str = '--config-dir {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-call', arg_str, with_retcode=with_retcode, catch_stderr=catch_stderr, timeout=60)
|
||||
|
||||
def run_cloud(self, arg_str, catch_stderr=False, timeout=30):
|
||||
'''
|
||||
Execute salt-cloud
|
||||
'''
|
||||
arg_str = '-c {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-cloud', arg_str, catch_stderr,
|
||||
timeout=timeout)
|
||||
|
||||
|
||||
@requires_sshd_server
|
||||
class SSHCase(ShellCase):
|
||||
'''
|
||||
Execute a command via salt-ssh
|
||||
'''
|
||||
def _arg_str(self, function, arg):
|
||||
return '{0} {1}'.format(function, ' '.join(arg))
|
||||
|
||||
def run_function(self, function, arg=(), timeout=90, **kwargs):
|
||||
'''
|
||||
We use a 90s timeout here, which some slower systems do end up needing
|
||||
'''
|
||||
ret = self.run_ssh(self._arg_str(function, arg), timeout=timeout)
|
||||
try:
|
||||
return json.loads(ret)['localhost']
|
||||
except Exception:
|
||||
return ret
|
||||
|
|
|
@ -29,20 +29,14 @@ from datetime import datetime, timedelta
|
|||
|
||||
# Import salt testing libs
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.helpers import RedirectStdStreams
|
||||
from tests.support.helpers import RedirectStdStreams, requires_sshd_server
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.mixins import AdaptedConfigurationTestCaseMixin, SaltClientTestCaseMixin
|
||||
from tests.support.paths import ScriptPathMixin, INTEGRATION_TEST_DIR, CODE_DIR, PYEXEC, SCRIPT_DIR
|
||||
|
||||
# Try to import salt: needed for __salt_system_encoding__ reference
|
||||
try:
|
||||
current_module_names = sys.modules.keys()
|
||||
import salt # pylint: disable=unused-import
|
||||
|
||||
for name in sys.modules:
|
||||
if name not in current_module_names:
|
||||
del sys.modules[name]
|
||||
except ImportError:
|
||||
pass
|
||||
# Import 3rd-party libs
|
||||
import salt.ext.six as six
|
||||
from salt.ext.six.moves import cStringIO # pylint: disable=import-error
|
||||
|
||||
STATE_FUNCTION_RUNNING_RE = re.compile(
|
||||
r'''The function (?:"|')(?P<state_func>.*)(?:"|') is running as PID '''
|
||||
|
@ -413,6 +407,144 @@ class ShellTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
|
|||
pass
|
||||
|
||||
|
||||
class ShellCase(ShellTestCase, AdaptedConfigurationTestCaseMixin, ScriptPathMixin):
|
||||
'''
|
||||
Execute a test for a shell command
|
||||
'''
|
||||
|
||||
_code_dir_ = CODE_DIR
|
||||
_script_dir_ = SCRIPT_DIR
|
||||
_python_executable_ = PYEXEC
|
||||
|
||||
def chdir(self, dirname):
|
||||
try:
|
||||
os.chdir(dirname)
|
||||
except OSError:
|
||||
os.chdir(INTEGRATION_TEST_DIR)
|
||||
|
||||
def run_salt(self, arg_str, with_retcode=False, catch_stderr=False, timeout=60): # pylint: disable=W0221
|
||||
'''
|
||||
Execute salt
|
||||
'''
|
||||
arg_str = '-c {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt',
|
||||
arg_str,
|
||||
with_retcode=with_retcode,
|
||||
catch_stderr=catch_stderr,
|
||||
timeout=timeout)
|
||||
|
||||
def run_ssh(self, arg_str, with_retcode=False, catch_stderr=False, timeout=60): # pylint: disable=W0221
|
||||
'''
|
||||
Execute salt-ssh
|
||||
'''
|
||||
arg_str = '-ldebug -W -c {0} -i --priv {1} --roster-file {2} --out=json localhost {3}'.format(
|
||||
self.get_config_dir(),
|
||||
os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'key_test'),
|
||||
os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'roster'),
|
||||
arg_str)
|
||||
return self.run_script('salt-ssh',
|
||||
arg_str,
|
||||
with_retcode=with_retcode,
|
||||
catch_stderr=catch_stderr,
|
||||
timeout=timeout,
|
||||
raw=True)
|
||||
|
||||
def run_run(self, arg_str, with_retcode=False, catch_stderr=False, async=False, timeout=60, config_dir=None):
|
||||
'''
|
||||
Execute salt-run
|
||||
'''
|
||||
arg_str = '-c {0}{async_flag} -t {timeout} {1}'.format(config_dir or self.get_config_dir(),
|
||||
arg_str,
|
||||
timeout=timeout,
|
||||
async_flag=' --async' if async else '')
|
||||
return self.run_script('salt-run',
|
||||
arg_str,
|
||||
with_retcode=with_retcode,
|
||||
catch_stderr=catch_stderr,
|
||||
timeout=60)
|
||||
|
||||
def run_run_plus(self, fun, *arg, **kwargs):
|
||||
'''
|
||||
Execute the runner function and return the return data and output in a dict
|
||||
'''
|
||||
# Late import
|
||||
import salt.runner
|
||||
import salt.output
|
||||
ret = {'fun': fun}
|
||||
from_scratch = bool(kwargs.pop('__reload_config', False))
|
||||
# Have to create an empty dict and then update it, as the result from
|
||||
# self.get_config() is an ImmutableDict which cannot be updated.
|
||||
opts = {}
|
||||
opts.update(self.get_config('client_config', from_scratch=from_scratch))
|
||||
opts_arg = list(arg)
|
||||
if kwargs:
|
||||
opts_arg.append({'__kwarg__': True})
|
||||
opts_arg[-1].update(kwargs)
|
||||
opts.update({'doc': False, 'fun': fun, 'arg': opts_arg})
|
||||
with RedirectStdStreams():
|
||||
runner = salt.runner.Runner(opts)
|
||||
ret['return'] = runner.run()
|
||||
try:
|
||||
ret['jid'] = runner.jid
|
||||
except AttributeError:
|
||||
ret['jid'] = None
|
||||
|
||||
# Compile output
|
||||
# TODO: Support outputters other than nested
|
||||
opts['color'] = False
|
||||
opts['output_file'] = cStringIO()
|
||||
try:
|
||||
salt.output.display_output(ret['return'], opts=opts)
|
||||
ret['out'] = opts['output_file'].getvalue().splitlines()
|
||||
finally:
|
||||
opts['output_file'].close()
|
||||
|
||||
return ret
|
||||
|
||||
def run_key(self, arg_str, catch_stderr=False, with_retcode=False):
|
||||
'''
|
||||
Execute salt-key
|
||||
'''
|
||||
arg_str = '-c {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-key',
|
||||
arg_str,
|
||||
catch_stderr=catch_stderr,
|
||||
with_retcode=with_retcode,
|
||||
timeout=60)
|
||||
|
||||
def run_cp(self, arg_str, with_retcode=False, catch_stderr=False):
|
||||
'''
|
||||
Execute salt-cp
|
||||
'''
|
||||
arg_str = '--config-dir {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-cp',
|
||||
arg_str,
|
||||
with_retcode=with_retcode,
|
||||
catch_stderr=catch_stderr,
|
||||
timeout=60)
|
||||
|
||||
def run_call(self, arg_str, with_retcode=False, catch_stderr=False):
|
||||
'''
|
||||
Execute salt-call.
|
||||
'''
|
||||
arg_str = '--config-dir {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-call',
|
||||
arg_str,
|
||||
with_retcode=with_retcode,
|
||||
catch_stderr=catch_stderr,
|
||||
timeout=60)
|
||||
|
||||
def run_cloud(self, arg_str, catch_stderr=False, timeout=30):
|
||||
'''
|
||||
Execute salt-cloud
|
||||
'''
|
||||
arg_str = '-c {0} {1}'.format(self.get_config_dir(), arg_str)
|
||||
return self.run_script('salt-cloud',
|
||||
arg_str,
|
||||
catch_stderr,
|
||||
timeout=timeout)
|
||||
|
||||
|
||||
class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
||||
'''
|
||||
Execute a module function
|
||||
|
@ -425,8 +557,7 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
'''
|
||||
return self.run_function(_function, args, **kw)
|
||||
|
||||
def run_function(self, function, arg=(), minion_tgt='minion', timeout=25,
|
||||
**kwargs):
|
||||
def run_function(self, function, arg=(), minion_tgt='minion', timeout=25, **kwargs):
|
||||
'''
|
||||
Run a single salt function and condition the return down to match the
|
||||
behavior of the raw function call
|
||||
|
@ -434,9 +565,15 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
know_to_return_none = (
|
||||
'file.chown', 'file.chgrp', 'ssh.recv_known_host'
|
||||
)
|
||||
orig = self.client.cmd(
|
||||
minion_tgt, function, arg, timeout=timeout, kwarg=kwargs
|
||||
)
|
||||
if 'f_arg' in kwargs:
|
||||
kwargs['arg'] = kwargs.pop('f_arg')
|
||||
if 'f_timeout' in kwargs:
|
||||
kwargs['timeout'] = kwargs.pop('f_timeout')
|
||||
orig = self.client.cmd(minion_tgt,
|
||||
function,
|
||||
arg,
|
||||
timeout=timeout,
|
||||
kwarg=kwargs)
|
||||
|
||||
if minion_tgt not in orig:
|
||||
self.skipTest(
|
||||
|
@ -454,9 +591,7 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
)
|
||||
|
||||
# Try to match stalled state functions
|
||||
orig[minion_tgt] = self._check_state_return(
|
||||
orig[minion_tgt], func=function
|
||||
)
|
||||
orig[minion_tgt] = self._check_state_return(orig[minion_tgt])
|
||||
|
||||
return orig[minion_tgt]
|
||||
|
||||
|
@ -467,19 +602,16 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
ret = self.run_function('state.single', [function], **kwargs)
|
||||
return self._check_state_return(ret)
|
||||
|
||||
def _check_state_return(self, ret, func='state.single'):
|
||||
def _check_state_return(self, ret):
|
||||
if isinstance(ret, dict):
|
||||
# This is the supposed return format for state calls
|
||||
return ret
|
||||
|
||||
# Late import
|
||||
import salt._compat
|
||||
|
||||
if isinstance(ret, list):
|
||||
jids = []
|
||||
# These are usually errors
|
||||
for item in ret[:]:
|
||||
if not isinstance(item, salt._compat.string_types):
|
||||
if not isinstance(item, six.string_types):
|
||||
# We don't know how to handle this
|
||||
continue
|
||||
match = STATE_FUNCTION_RUNNING_RE.match(item)
|
||||
|
@ -492,13 +624,11 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
|
||||
jids.append(jid)
|
||||
|
||||
job_data = self.run_function(
|
||||
'saltutil.find_job', [jid]
|
||||
)
|
||||
job_data = self.run_function('saltutil.find_job', [jid])
|
||||
job_kill = self.run_function('saltutil.kill_job', [jid])
|
||||
msg = (
|
||||
'A running state.single was found causing a state lock. '
|
||||
'Job details: {0!r} Killing Job Returned: {1!r}'.format(
|
||||
'Job details: \'{0}\' Killing Job Returned: \'{1}\''.format(
|
||||
job_data, job_kill
|
||||
)
|
||||
)
|
||||
|
@ -527,15 +657,19 @@ class SyndicCase(TestCase, SaltClientTestCaseMixin):
|
|||
return orig['minion']
|
||||
|
||||
|
||||
class SSHCase(ShellTestCase):
|
||||
@requires_sshd_server
|
||||
class SSHCase(ShellCase):
|
||||
'''
|
||||
Execute a command via salt-ssh
|
||||
'''
|
||||
def _arg_str(self, function, arg):
|
||||
return '{0} {1}'.format(function, ' '.join(arg))
|
||||
|
||||
def run_function(self, function, arg=(), timeout=25, **kwargs):
|
||||
ret = self.run_ssh(self._arg_str(function, arg))
|
||||
def run_function(self, function, arg=(), timeout=90, **kwargs):
|
||||
'''
|
||||
We use a 90s timeout here, which some slower systems do end up needing
|
||||
'''
|
||||
ret = self.run_ssh(self._arg_str(function, arg), timeout=timeout)
|
||||
try:
|
||||
return json.loads(ret)['localhost']
|
||||
except Exception:
|
||||
|
|
Loading…
Add table
Reference in a new issue