Merge pull request #46005 from gtmanfred/ssh

pass opts to SSHClient for enable_ssh_minions
This commit is contained in:
Nicole Thomas 2018-02-22 09:10:44 -05:00 committed by GitHub
commit 391f45b5cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 119 additions and 31 deletions

View File

@ -658,7 +658,9 @@ class SSH(object):
host = next(six.iterkeys(ret)) host = next(six.iterkeys(ret))
self.cache_job(jid, host, ret[host], fun) self.cache_job(jid, host, ret[host], fun)
if self.event: if self.event:
_, data = next(six.iteritems(ret)) id_, data = next(six.iteritems(ret))
if 'id' not in data:
data['id'] = id_
data['jid'] = jid # make the jid in the payload the same as the jid in the tag data['jid'] = jid # make the jid in the payload the same as the jid in the tag
self.event.fire_event( self.event.fire_event(
data, data,
@ -769,7 +771,9 @@ class SSH(object):
outputter, outputter,
self.opts) self.opts)
if self.event: if self.event:
_, data = next(six.iteritems(ret)) id_, data = next(six.iteritems(ret))
if 'id' not in data:
data['id'] = id_
data['jid'] = jid # make the jid in the payload the same as the jid in the tag data['jid'] = jid # make the jid in the payload the same as the jid in the tag
self.event.fire_event( self.event.fire_event(
data, data,

View File

@ -1809,7 +1809,6 @@ DEFAULT_MASTER_OPTS = {
'schedule': {}, 'schedule': {},
'auth_events': True, 'auth_events': True,
'minion_data_cache_events': True, 'minion_data_cache_events': True,
'enable_ssh': False,
'enable_ssh_minions': False, 'enable_ssh_minions': False,
} }

View File

@ -2005,6 +2005,7 @@ class ClearFuncs(object):
) )
minions = _res.get('minions', list()) minions = _res.get('minions', list())
missing = _res.get('missing', list()) missing = _res.get('missing', list())
ssh_minions = _res.get('ssh_minions', False)
# Check for external auth calls and authenticate # Check for external auth calls and authenticate
auth_type, err_name, key, sensitive_load_keys = self._prep_auth_info(extra) auth_type, err_name, key, sensitive_load_keys = self._prep_auth_info(extra)
@ -2072,7 +2073,7 @@ class ClearFuncs(object):
payload = self._prep_pub(minions, jid, clear_load, extra, missing) payload = self._prep_pub(minions, jid, clear_load, extra, missing)
# Send it! # Send it!
minions.extend(self._send_ssh_pub(payload)) self._send_ssh_pub(payload, ssh_minions=ssh_minions)
self._send_pub(payload) self._send_pub(payload)
return { return {
@ -2135,20 +2136,19 @@ class ClearFuncs(object):
chan = salt.transport.server.PubServerChannel.factory(opts) chan = salt.transport.server.PubServerChannel.factory(opts)
chan.publish(load) chan.publish(load)
def _send_ssh_pub(self, load): @property
def ssh_client(self):
if not hasattr(self, '_ssh_client'):
self._ssh_client = salt.client.ssh.client.SSHClient(mopts=self.opts)
return self._ssh_client
def _send_ssh_pub(self, load, ssh_minions=False):
''' '''
Take a load and send it across the network to connected minions Take a load and send it across the network to ssh minions
''' '''
minions = [] if self.opts['enable_ssh_minions'] is True and ssh_minions is True:
if self.opts['enable_ssh_minions'] is True and isinstance(load['tgt'], six.string_types): log.debug('Send payload to ssh minions')
# The isinstances makes sure that syndics work threading.Thread(target=self.ssh_client.cmd, kwargs=load).start()
log.debug('Use SSHClient for rostered minions')
ssh = salt.client.ssh.client.SSHClient()
ssh_minions = ssh._prep_ssh(**load).targets.keys()
if ssh_minions:
minions.extend(ssh_minions)
threading.Thread(target=ssh.cmd, kwargs=load).start()
return minions
def _prep_pub(self, minions, jid, clear_load, extra, missing): def _prep_pub(self, minions, jid, clear_load, extra, missing):
''' '''

View File

@ -13,6 +13,7 @@ import logging
# Import salt libs # Import salt libs
import salt.payload import salt.payload
import salt.roster
import salt.utils.data import salt.utils.data
import salt.utils.files import salt.utils.files
import salt.utils.network import salt.utils.network
@ -682,6 +683,13 @@ class CkMinions(object):
_res = check_func(expr, delimiter, greedy) _res = check_func(expr, delimiter, greedy)
else: else:
_res = check_func(expr, greedy) _res = check_func(expr, greedy)
_res['ssh_minions'] = False
if self.opts.get('enable_ssh_minions', False) is True and isinstance('tgt', six.string_types):
roster = salt.roster.Roster(self.opts, self.opts.get('roster', 'flat'))
ssh_minions = roster.targets(expr, tgt_type)
if ssh_minions:
_res['minions'].extend(ssh_minions)
_res['ssh_minions'] = True
except Exception: except Exception:
log.exception( log.exception(
'Failed matching available minions with %s pattern: %s', 'Failed matching available minions with %s pattern: %s',

View File

@ -671,10 +671,15 @@ class TestDaemon(object):
else: else:
os.environ['SSH_DAEMON_RUNNING'] = 'True' os.environ['SSH_DAEMON_RUNNING'] = 'True'
roster_path = os.path.join(FILES, 'conf/_ssh/roster') roster_path = os.path.join(FILES, 'conf/_ssh/roster')
syndic_roster_path = os.path.join(FILES, 'conf/_ssh/syndic_roster')
shutil.copy(roster_path, RUNTIME_VARS.TMP_CONF_DIR) shutil.copy(roster_path, RUNTIME_VARS.TMP_CONF_DIR)
shutil.copy(syndic_roster_path, os.path.join(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR, 'roster'))
with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'roster'), 'a') as roster: with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'roster'), 'a') as roster:
roster.write(' user: {0}\n'.format(RUNTIME_VARS.RUNNING_TESTS_USER)) roster.write(' user: {0}\n'.format(RUNTIME_VARS.RUNNING_TESTS_USER))
roster.write(' priv: {0}/{1}'.format(RUNTIME_VARS.TMP_CONF_DIR, 'key_test')) roster.write(' priv: {0}/{1}'.format(RUNTIME_VARS.TMP_CONF_DIR, 'key_test'))
with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR, 'roster'), 'a') as roster:
roster.write(' user: {0}\n'.format(RUNTIME_VARS.RUNNING_TESTS_USER))
roster.write(' priv: {0}/{1}'.format(RUNTIME_VARS.TMP_CONF_DIR, 'key_test'))
sys.stdout.write( sys.stdout.write(
' {LIGHT_GREEN}STARTED!\n{ENDC}'.format( ' {LIGHT_GREEN}STARTED!\n{ENDC}'.format(
**self.colors **self.colors

View File

@ -19,7 +19,8 @@ class BatchTest(ShellCase):
Tests executing a simple batch command to help catch regressions Tests executing a simple batch command to help catch regressions
''' '''
ret = 'Executing run on [\'sub_minion\']' ret = 'Executing run on [\'sub_minion\']'
cmd = self.run_salt('\'*\' test.echo \'batch testing\' -b 50%')
cmd = self.run_salt('\'*minion\' test.echo \'batch testing\' -b 50%')
self.assertIn(ret, cmd) self.assertIn(ret, cmd)
def test_batch_run_number(self): def test_batch_run_number(self):
@ -28,7 +29,7 @@ class BatchTest(ShellCase):
a percentage with full batch CLI call. a percentage with full batch CLI call.
''' '''
ret = "Executing run on ['minion', 'sub_minion']" ret = "Executing run on ['minion', 'sub_minion']"
cmd = self.run_salt('\'*\' test.ping --batch-size 2') cmd = self.run_salt('\'*minion\' test.ping --batch-size 2')
self.assertIn(ret, cmd) self.assertIn(ret, cmd)
def test_batch_run_grains_targeting(self): def test_batch_run_grains_targeting(self):
@ -45,7 +46,7 @@ class BatchTest(ShellCase):
os_grain = item os_grain = item
os_grain = os_grain.strip() os_grain = os_grain.strip()
cmd = self.run_salt('-G \'os:{0}\' -b 25% test.ping'.format(os_grain)) cmd = self.run_salt('-C \'G@os:{0} and not localhost\' -b 25% test.ping'.format(os_grain))
self.assertIn(sub_min_ret, cmd) self.assertIn(sub_min_ret, cmd)
self.assertIn(min_ret, cmd) self.assertIn(min_ret, cmd)
@ -53,5 +54,5 @@ class BatchTest(ShellCase):
''' '''
Test that a failed state returns a non-zero exit code in batch mode Test that a failed state returns a non-zero exit code in batch mode
''' '''
cmd = self.run_salt(' "*" state.single test.fail_without_changes name=test_me -b 25%', with_retcode=True) cmd = self.run_salt(' "*minion" state.single test.fail_without_changes name=test_me -b 33%', with_retcode=True)
self.assertEqual(cmd[-1], 2) self.assertEqual(cmd[-1], 2)

View File

@ -0,0 +1,5 @@
syndic_localhost:
host: 127.0.0.1
port: 2827
mine_functions:
test.arg: ['itworked']

View File

@ -102,3 +102,7 @@ autosign_file: autosign_file
# disable discovery for test suite saltstack/salt-jenkins#683 # disable discovery for test suite saltstack/salt-jenkins#683
discovery: false discovery: false
# enable using ssh minions and regular minions
enable_ssh_minions: True
ignore_host_keys: True

View File

@ -23,3 +23,7 @@ tcp_master_workers: 54515
# Syndic Settings # Syndic Settings
order_masters: True order_masters: True
# enable using ssh minions and regular minions
enable_ssh_minions: True
ignore_host_keys: True

View File

@ -174,7 +174,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
# TODO: verify pub function? Maybe look at how we test the publisher # TODO: verify pub function? Maybe look at how we test the publisher
self.assertEqual(len(ret), 1) self.assertEqual(len(ret), 1)
self.assertIn('jid', ret[0]) self.assertIn('jid', ret[0])
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion'])) self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
def test_multi_local_async_post(self): def test_multi_local_async_post(self):
low = [{'client': 'local_async', low = [{'client': 'local_async',
@ -200,8 +200,8 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertEqual(len(ret), 2) self.assertEqual(len(ret), 2)
self.assertIn('jid', ret[0]) self.assertIn('jid', ret[0])
self.assertIn('jid', ret[1]) self.assertIn('jid', ret[1])
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion'])) self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion'])) self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
def test_multi_local_async_post_multitoken(self): def test_multi_local_async_post_multitoken(self):
low = [{'client': 'local_async', low = [{'client': 'local_async',
@ -235,8 +235,8 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertIn('jid', ret[0]) # the first 2 are regular returns self.assertIn('jid', ret[0]) # the first 2 are regular returns
self.assertIn('jid', ret[1]) self.assertIn('jid', ret[1])
self.assertIn('Authentication error occurred.', ret[2]) # bad auth self.assertIn('Authentication error occurred.', ret[2]) # bad auth
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion'])) self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion'])) self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
def test_simple_local_async_post_no_tgt(self): def test_simple_local_async_post_no_tgt(self):
low = [{'client': 'local_async', low = [{'client': 'local_async',
@ -267,7 +267,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
) )
response_obj = salt.utils.json.loads(response.body) response_obj = salt.utils.json.loads(response.body)
self.assertEqual(len(response_obj['return']), 1) self.assertEqual(len(response_obj['return']), 1)
self.assertEqual(set(response_obj['return'][0]), set(['minion', 'sub_minion'])) self.assertEqual(set(response_obj['return'][0]), set(['localhost', 'minion', 'sub_minion']))
# runner_async tests # runner_async tests
def test_simple_local_runner_async_post(self): def test_simple_local_runner_async_post(self):
@ -329,7 +329,7 @@ class TestMinionSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertEqual(response_obj['return'][0]['minion']['id'], 'minion') self.assertEqual(response_obj['return'][0]['minion']['id'], 'minion')
def test_post(self): def test_post(self):
low = [{'tgt': '*', low = [{'tgt': '*minion',
'fun': 'test.ping', 'fun': 'test.ping',
}] }]
response = self.fetch('/minions', response = self.fetch('/minions',
@ -351,7 +351,7 @@ class TestMinionSaltAPIHandler(_SaltnadoIntegrationTestCase):
def test_post_with_client(self): def test_post_with_client(self):
# get a token for this test # get a token for this test
low = [{'client': 'local_async', low = [{'client': 'local_async',
'tgt': '*', 'tgt': '*minion',
'fun': 'test.ping', 'fun': 'test.ping',
}] }]
response = self.fetch('/minions', response = self.fetch('/minions',

View File

@ -35,7 +35,7 @@ class NetapiClientTest(TestCase):
low.update(self.eauth_creds) low.update(self.eauth_creds)
ret = self.netapi.run(low) ret = self.netapi.run(low)
self.assertEqual(ret, {'minion': True, 'sub_minion': True}) self.assertEqual(ret, {'minion': True, 'sub_minion': True, 'localhost': True})
def test_local_async(self): def test_local_async(self):
low = {'client': 'local_async', 'tgt': '*', 'fun': 'test.ping'} low = {'client': 'local_async', 'tgt': '*', 'fun': 'test.ping'}
@ -47,7 +47,7 @@ class NetapiClientTest(TestCase):
self.assertIn('jid', ret) self.assertIn('jid', ret)
ret.pop('jid', None) ret.pop('jid', None)
ret['minions'] = sorted(ret['minions']) ret['minions'] = sorted(ret['minions'])
self.assertEqual(ret, {'minions': sorted(['minion', 'sub_minion'])}) self.assertEqual(ret, {'minions': sorted(['minion', 'sub_minion', 'localhost'])})
def test_wheel(self): def test_wheel(self):
low = {'client': 'wheel', 'fun': 'key.list_all'} low = {'client': 'wheel', 'fun': 'key.list_all'}

View File

@ -18,6 +18,7 @@ from salt.ext.six.moves import queue
from tests.support.case import ShellCase from tests.support.case import ShellCase
from tests.support.unit import skipIf from tests.support.unit import skipIf
from tests.support.paths import TMP from tests.support.paths import TMP
from tests.support.helpers import flaky
# Import Salt Libs # Import Salt Libs
import salt.utils.platform import salt.utils.platform
@ -44,6 +45,7 @@ class StateRunnerTest(ShellCase):
q.put(ret) q.put(ret)
q.task_done() q.task_done()
@flaky
def test_orchestrate_output(self): def test_orchestrate_output(self):
''' '''
Ensure the orchestrate runner outputs useful state data. Ensure the orchestrate runner outputs useful state data.

View File

@ -55,6 +55,8 @@ class CopyTest(ShellCase, ShellCaseCommonTestsMixin):
testfile_contents = fh_.read() testfile_contents = fh_.read()
for idx, minion in enumerate(minions): for idx, minion in enumerate(minions):
if 'localhost' in minion:
continue
ret = self.run_salt( ret = self.run_salt(
'--out yaml {0} file.directory_exists {1}'.format( '--out yaml {0} file.directory_exists {1}'.format(
pipes.quote(minion), TMP pipes.quote(minion), TMP
@ -138,7 +140,7 @@ class CopyTest(ShellCase, ShellCaseCommonTestsMixin):
ret = self.run_script( ret = self.run_script(
self._call_binary_, self._call_binary_,
'--out pprint --config-dir {0} \'*\' {1} {0}/{2}'.format( '--out pprint --config-dir {0} \'*minion\' {1} {0}/{2}'.format(
config_dir, config_dir,
fn_, fn_,
os.path.basename(fn_), os.path.basename(fn_),

View File

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
'''
Simple Smoke Tests for Connected SSH minions
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.helpers import requires_sshd_server
@requires_sshd_server
class SSHMasterTestCase(ModuleCase):
'''
Test minion blackout functionality
'''
def test_can_it_ping(self):
'''
Ensure the proxy can ping
'''
ret = self.run_function('test.ping', minion_tgt='localhost')
self.assertEqual(ret, True)
def test_service(self):
service = 'cron'
os_family = self.run_function('grains.get', ['os_family'], minion_tgt='localhost')
if os_family == 'RedHat':
service = 'crond'
elif os_family == 'Arch':
service = 'sshd'
ret = self.run_function('service.get_all', minion_tgt='localhost')
self.assertIn(service, ret)
self.run_function('service.stop', [service], minion_tgt='localhost')
ret = self.run_function('service.status', [service], minion_tgt='localhost')
self.assertFalse(ret)
self.run_function('service.start', [service], minion_tgt='localhost')
ret = self.run_function('service.status', [service], minion_tgt='localhost')
self.assertTrue(ret)
def test_grains_items(self):
ret = self.run_function('grains.items', minion_tgt='localhost')
self.assertEqual(ret['kernel'], 'Linux')
def test_state_apply(self):
ret = self.run_function('state.apply', ['core'], minion_tgt='localhost')
for key, value in ret.items():
self.assertTrue(value['result'])
def test_state_highstate(self):
ret = self.run_function('state.highstate', minion_tgt='localhost')
for key, value in ret.items():
self.assertTrue(value['result'])