mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge remote-tracking branch 'upstream/2014.7' into merge-forward-2015.2
Conflicts: doc/topics/topology/syndic.rst salt/client/__init__.py salt/daemons/masterapi.py salt/minion.py salt/modules/dockerio.py
This commit is contained in:
commit
2b1f400ab4
@ -66,6 +66,7 @@ starting the other Salt daemons.
|
|||||||
|
|
||||||
Topology
|
Topology
|
||||||
========
|
========
|
||||||
|
|
||||||
The ``salt-syndic`` is little more than a command and event forwarder. When a
|
The ``salt-syndic`` is little more than a command and event forwarder. When a
|
||||||
command is issued from a higher-level master, it will be received by the
|
command is issued from a higher-level master, it will be received by the
|
||||||
configured syndics on lower-level masters, and propagated to to their minions,
|
configured syndics on lower-level masters, and propagated to to their minions,
|
||||||
@ -82,4 +83,23 @@ connected to an upper-level master will have both the ``salt-master`` and the
|
|||||||
Nodes on the lowest points of the hierarchy (minions which do not propagate
|
Nodes on the lowest points of the hierarchy (minions which do not propagate
|
||||||
data to another level) will only have the ``salt-minion`` daemon running. There
|
data to another level) will only have the ``salt-minion`` daemon running. There
|
||||||
is no need for either ``salt-master`` or ``salt-syndic`` to be running on a
|
is no need for either ``salt-master`` or ``salt-syndic`` to be running on a
|
||||||
standard minion.
|
standard minion.
|
||||||
|
|
||||||
|
Syndic and the CLI
|
||||||
|
==================
|
||||||
|
|
||||||
|
In order for the high-level master to return information from minions that are
|
||||||
|
below the syndic(s), the CLI requires a short wait time in order to allow the
|
||||||
|
syndic(s) to gather responses from their minions. This value is defined in the
|
||||||
|
``syndic_wait` and has a default of five seconds.
|
||||||
|
|
||||||
|
While it is possible to run a syndic without a minion installed on the same machine,
|
||||||
|
it is recommended, for a faster CLI response time, to do so. Without a minion
|
||||||
|
installed on the syndic, the timeout value of ``syndic_wait`` increases
|
||||||
|
significantly - about three-fold. With a minion installed on the syndic, the CLI
|
||||||
|
timeout resides at the value defined in ``syndic_wait``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
To reduce the amount of time the CLI waits for minions to respond, install a minion
|
||||||
|
on the syndic or tune the value of the ``syndic_wait`` configuration.
|
||||||
|
@ -805,6 +805,14 @@ class LocalClient(object):
|
|||||||
event bus for non-return data, such as minion lists returned from
|
event bus for non-return data, such as minion lists returned from
|
||||||
syndics.
|
syndics.
|
||||||
'''
|
'''
|
||||||
|
tag_search = []
|
||||||
|
tag_search.append(jid)
|
||||||
|
if isinstance(additional_tags, str):
|
||||||
|
tag_search.append(additional_tags)
|
||||||
|
elif isinstance(additional_tags, list):
|
||||||
|
for tag in additional_tags:
|
||||||
|
tag_search.append(tag)
|
||||||
|
|
||||||
if event is None:
|
if event is None:
|
||||||
event = self.event
|
event = self.event
|
||||||
|
|
||||||
|
@ -2301,6 +2301,7 @@ class MultiSyndic(MinionBase):
|
|||||||
self.syndic_mode = self.opts.get('syndic_mode', 'sync')
|
self.syndic_mode = self.opts.get('syndic_mode', 'sync')
|
||||||
|
|
||||||
self._has_master = threading.Event()
|
self._has_master = threading.Event()
|
||||||
|
self.jid_forward_cache = set()
|
||||||
|
|
||||||
# create all of the syndics you need
|
# create all of the syndics you need
|
||||||
self.master_syndics = {}
|
self.master_syndics = {}
|
||||||
@ -2534,10 +2535,19 @@ class MultiSyndic(MinionBase):
|
|||||||
jdict['__fun__'] = event['data'].get('fun')
|
jdict['__fun__'] = event['data'].get('fun')
|
||||||
jdict['__jid__'] = event['data']['jid']
|
jdict['__jid__'] = event['data']['jid']
|
||||||
jdict['__load__'] = {}
|
jdict['__load__'] = {}
|
||||||
fstr = '{0}.get_jid'.format(self.opts['master_job_cache'])
|
fstr = '{0}.get_load'.format(self.opts['master_job_cache'])
|
||||||
jdict['__load__'].update(
|
# Only need to forward each load once. Don't hit the disk
|
||||||
self.mminion.returners[fstr](event['data']['jid'])
|
# for every minion return!
|
||||||
)
|
if event['data']['jid'] not in self.jid_forward_cache:
|
||||||
|
jdict['__load__'].update(
|
||||||
|
self.mminion.returners[fstr](event['data']['jid'])
|
||||||
|
)
|
||||||
|
self.jid_forward_cache.add(event['data']['jid'])
|
||||||
|
if len(self.jid_forward_cache) > self.opts['syndic_jid_forward_cache_hwm']:
|
||||||
|
# Pop the oldest jid from the cache
|
||||||
|
tmp = sorted(list(self.jid_forward_cache))
|
||||||
|
tmp.pop(0)
|
||||||
|
self.jid_forward_cache = set(tmp)
|
||||||
if 'master_id' in event['data']:
|
if 'master_id' in event['data']:
|
||||||
# __'s to make sure it doesn't print out on the master cli
|
# __'s to make sure it doesn't print out on the master cli
|
||||||
jdict['__master_id__'] = event['data']['master_id']
|
jdict['__master_id__'] = event['data']['master_id']
|
||||||
|
@ -392,29 +392,22 @@ def get_containers(all=True,
|
|||||||
status['host'] = {}
|
status['host'] = {}
|
||||||
status['host']['interfaces'] = __salt__['network.interfaces']()
|
status['host']['interfaces'] = __salt__['network.interfaces']()
|
||||||
|
|
||||||
containers = ret = client.containers(all=all,
|
containers = client.containers(all=all,
|
||||||
trunc=trunc,
|
trunc=trunc,
|
||||||
since=since,
|
since=since,
|
||||||
before=before,
|
before=before,
|
||||||
limit=limit)
|
limit=limit)
|
||||||
|
|
||||||
# Optionally for each container get more granular information from them
|
# Optionally for each container get more granular information from them
|
||||||
# by inspecting the container
|
# by inspecting the container
|
||||||
if inspect:
|
if inspect:
|
||||||
ret = []
|
|
||||||
for container in containers:
|
for container in containers:
|
||||||
container_id = container.get('Id')
|
container_id = container.get('Id')
|
||||||
if container_id:
|
if container_id:
|
||||||
inspect = _get_container_infos(container_id)
|
inspect = _get_container_infos(container_id)
|
||||||
container['detail'] = {}
|
container['detail'] = inspect.copy()
|
||||||
for key, value in inspect.items():
|
|
||||||
container['detail'][key] = value
|
|
||||||
ret.append(container)
|
|
||||||
|
|
||||||
if ret:
|
_valid(status, comment='All containers in out', out=containers)
|
||||||
_valid(status, comment='All containers in out', out=ret)
|
|
||||||
else:
|
|
||||||
_invalid(status)
|
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ def enable_server(name, backend, socket='/var/run/haproxy.sock'):
|
|||||||
ha_conn = _get_conn(socket)
|
ha_conn = _get_conn(socket)
|
||||||
ha_cmd = haproxy.cmds.enableServer(server=name, backend=backend)
|
ha_cmd = haproxy.cmds.enableServer(server=name, backend=backend)
|
||||||
ha_conn.sendCmd(ha_cmd)
|
ha_conn.sendCmd(ha_cmd)
|
||||||
return list_servers(backend)
|
return list_servers(backend, socket=socket)
|
||||||
|
|
||||||
|
|
||||||
def disable_server(name, backend, socket='/var/run/haproxy.sock'):
|
def disable_server(name, backend, socket='/var/run/haproxy.sock'):
|
||||||
@ -107,7 +107,7 @@ def disable_server(name, backend, socket='/var/run/haproxy.sock'):
|
|||||||
ha_conn = _get_conn(socket)
|
ha_conn = _get_conn(socket)
|
||||||
ha_cmd = haproxy.cmds.disableServer(server=name, backend=backend)
|
ha_cmd = haproxy.cmds.disableServer(server=name, backend=backend)
|
||||||
ha_conn.sendCmd(ha_cmd)
|
ha_conn.sendCmd(ha_cmd)
|
||||||
return list_servers(backend)
|
return list_servers(backend, socket=socket)
|
||||||
|
|
||||||
|
|
||||||
def get_weight(name, backend, socket='/var/run/haproxy.sock'):
|
def get_weight(name, backend, socket='/var/run/haproxy.sock'):
|
||||||
|
@ -189,14 +189,13 @@ def extracted(name,
|
|||||||
tar_cmd = ['tar']
|
tar_cmd = ['tar']
|
||||||
tar_shortopts = 'x'
|
tar_shortopts = 'x'
|
||||||
tar_longopts = []
|
tar_longopts = []
|
||||||
tar_afterfile = []
|
|
||||||
|
|
||||||
for position, opt in enumerate(tar_opts):
|
for position, opt in enumerate(tar_opts):
|
||||||
if opt.startswith('-'):
|
if opt.startswith('-'):
|
||||||
tar_longopts.append(opt)
|
tar_longopts.append(opt)
|
||||||
else:
|
else:
|
||||||
if position > 0:
|
if position > 0:
|
||||||
tar_afterfile.append(opt)
|
tar_longopts.append(opt)
|
||||||
else:
|
else:
|
||||||
append_opt = opt
|
append_opt = opt
|
||||||
append_opt = append_opt.replace('x', '').replace('f', '')
|
append_opt = append_opt.replace('x', '').replace('f', '')
|
||||||
@ -205,7 +204,6 @@ def extracted(name,
|
|||||||
tar_cmd.append(tar_shortopts)
|
tar_cmd.append(tar_shortopts)
|
||||||
tar_cmd.extend(tar_longopts)
|
tar_cmd.extend(tar_longopts)
|
||||||
tar_cmd.extend(['-f', filename])
|
tar_cmd.extend(['-f', filename])
|
||||||
tar_cmd.extend(tar_afterfile)
|
|
||||||
|
|
||||||
results = __salt__['cmd.run_all'](tar_cmd, cwd=name, python_shell=False)
|
results = __salt__['cmd.run_all'](tar_cmd, cwd=name, python_shell=False)
|
||||||
if results['retcode'] != 0:
|
if results['retcode'] != 0:
|
||||||
|
@ -37,6 +37,10 @@ external_auth:
|
|||||||
- '@wheel'
|
- '@wheel'
|
||||||
- '@runner'
|
- '@runner'
|
||||||
- test.*
|
- test.*
|
||||||
|
saltops%:
|
||||||
|
- '@wheel'
|
||||||
|
- '@runner'
|
||||||
|
- 'test.*'
|
||||||
auto:
|
auto:
|
||||||
saltdev_auto:
|
saltdev_auto:
|
||||||
- '@wheel'
|
- '@wheel'
|
||||||
|
@ -7,18 +7,37 @@
|
|||||||
# Import python libs
|
# Import python libs
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
|
import grp
|
||||||
import random
|
import random
|
||||||
|
|
||||||
# Import Salt Testing libs
|
# Import Salt Testing libs
|
||||||
|
from salttesting import skipIf
|
||||||
from salttesting.helpers import (
|
from salttesting.helpers import (
|
||||||
ensure_in_syspath,
|
ensure_in_syspath,
|
||||||
destructiveTest)
|
destructiveTest)
|
||||||
ensure_in_syspath('../../')
|
ensure_in_syspath('../../')
|
||||||
|
|
||||||
# Import salt libs
|
# Import salt libs
|
||||||
|
from salt.utils.pycrypto import gen_hash
|
||||||
import integration
|
import integration
|
||||||
|
|
||||||
from salttesting import skipIf
|
|
||||||
|
def gen_password():
|
||||||
|
'''
|
||||||
|
generate a password and hash it
|
||||||
|
'''
|
||||||
|
alphabet = ('abcdefghijklmnopqrstuvwxyz'
|
||||||
|
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||||
|
password = ''
|
||||||
|
# generate password
|
||||||
|
for _ in range(20):
|
||||||
|
next_index = random.randrange(len(alphabet))
|
||||||
|
password += alphabet[next_index]
|
||||||
|
|
||||||
|
# hash the password
|
||||||
|
hashed_pwd = gen_hash('salt', password, 'sha512')
|
||||||
|
|
||||||
|
return (password, hashed_pwd)
|
||||||
|
|
||||||
|
|
||||||
class AuthTest(integration.ShellCase):
|
class AuthTest(integration.ShellCase):
|
||||||
@ -28,38 +47,38 @@ class AuthTest(integration.ShellCase):
|
|||||||
|
|
||||||
_call_binary_ = 'salt'
|
_call_binary_ = 'salt'
|
||||||
|
|
||||||
is_root = os.geteuid() != 0
|
is_not_root = os.geteuid() != 0
|
||||||
|
|
||||||
|
userA = 'saltdev'
|
||||||
|
userB = 'saltadm'
|
||||||
|
group = 'saltops'
|
||||||
|
|
||||||
@destructiveTest
|
@destructiveTest
|
||||||
@skipIf(is_root, 'You must be logged in as root to run this test')
|
@skipIf(is_not_root, 'You must be logged in as root to run this test')
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# This is a little wasteful but shouldn't be a problem
|
# This is a little wasteful but shouldn't be a problem
|
||||||
|
for user in (self.userA, self.userB):
|
||||||
|
try:
|
||||||
|
pwd.getpwnam(user)
|
||||||
|
except KeyError:
|
||||||
|
self.run_call('user.add {0} createhome=False'.format(user))
|
||||||
|
|
||||||
|
# only put userB into the group for the group auth test
|
||||||
try:
|
try:
|
||||||
pwd.getpwnam('saltdev')
|
grp.getgrnam(self.group)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.run_call('user.add saltdev createhome=False')
|
self.run_call('group.add {0}'.format(self.group))
|
||||||
|
self.run_call('user.chgroups {0} {1} True'.format(self.userB, self.group))
|
||||||
|
|
||||||
def test_pam_auth_valid_user(self):
|
def test_pam_auth_valid_user(self):
|
||||||
'''
|
'''
|
||||||
test pam auth mechanism is working with a valid user
|
test pam auth mechanism is working with a valid user
|
||||||
'''
|
'''
|
||||||
alphabet = ('abcdefghijklmnopqrstuvwxyz'
|
password, hashed_pwd = gen_password()
|
||||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
self.run_call("shadow.set_password {0} '{1}'".format(self.userA, hashed_pwd))
|
||||||
self.password = ''
|
|
||||||
# generate password
|
|
||||||
for _ in range(20):
|
|
||||||
next_index = random.randrange(len(alphabet))
|
|
||||||
self.password = self.password + alphabet[next_index]
|
|
||||||
|
|
||||||
# hash the password
|
|
||||||
from salt.utils.pycrypto import gen_hash
|
|
||||||
|
|
||||||
pwd = gen_hash('salt', self.password, 'sha512')
|
|
||||||
self.run_call("shadow.set_password saltdev '{0}'".format(pwd))
|
|
||||||
cmd = ('-a pam "*"'
|
|
||||||
' test.ping --username {0}'
|
|
||||||
' --password {1}'.format('saltdev', self.password))
|
|
||||||
|
|
||||||
|
cmd = ('-a pam "*" test.ping '
|
||||||
|
'--username {0} --password {1}'.format(self.userA, password))
|
||||||
resp = self.run_salt(cmd)
|
resp = self.run_salt(cmd)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'minion:' in resp
|
'minion:' in resp
|
||||||
@ -69,19 +88,35 @@ class AuthTest(integration.ShellCase):
|
|||||||
'''
|
'''
|
||||||
test pam auth mechanism errors for an invalid user
|
test pam auth mechanism errors for an invalid user
|
||||||
'''
|
'''
|
||||||
cmd = ('-a pam'
|
cmd = ('-a pam "*" test.ping '
|
||||||
' * test.ping --username nouser'
|
'--username nouser --password {0}'.format('abcd1234'))
|
||||||
' --password {0}'.format('abcd1234'))
|
|
||||||
resp = self.run_salt(cmd)
|
resp = self.run_salt(cmd)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
'Failed to authenticate' in ''.join(resp)
|
'Failed to authenticate' in ''.join(resp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_pam_auth_valid_group(self):
|
||||||
|
'''
|
||||||
|
test pam auth mechanism success for a valid group
|
||||||
|
'''
|
||||||
|
password, hashed_pwd = gen_password()
|
||||||
|
self.run_call("shadow.set_password {0} '{1}'".format(self.userB, hashed_pwd))
|
||||||
|
|
||||||
|
cmd = ('-a pam "*" test.ping '
|
||||||
|
'--username {0} --password {1}'.format(self.userB, password))
|
||||||
|
resp = self.run_salt(cmd)
|
||||||
|
self.assertTrue(
|
||||||
|
'minion:' in resp
|
||||||
|
)
|
||||||
|
|
||||||
@destructiveTest
|
@destructiveTest
|
||||||
@skipIf(is_root, 'You must be logged in as root to run this test')
|
@skipIf(is_not_root, 'You must be logged in as root to run this test')
|
||||||
def test_zzzz_tearDown(self):
|
def test_zzzz_tearDown(self):
|
||||||
if pwd.getpwnam('saltdev'):
|
for user in (self.userA, self.userB):
|
||||||
self.run_call('user.delete saltdev')
|
if pwd.getpwnam(user):
|
||||||
|
self.run_call('user.delete {0}'.format(user))
|
||||||
|
if grp.getgrnam(self.group):
|
||||||
|
self.run_call('group.delete {0}'.format(self.group))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
81
tests/unit/states/archive_test.py
Normal file
81
tests/unit/states/archive_test.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
unit tests for the archive state
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Import Python Libs
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# Import Salt Libs
|
||||||
|
from salt.states import archive
|
||||||
|
|
||||||
|
# Import Salt Testing Libs
|
||||||
|
from salttesting import skipIf, TestCase
|
||||||
|
from salttesting.helpers import ensure_in_syspath
|
||||||
|
from salttesting.mock import (
|
||||||
|
NO_MOCK,
|
||||||
|
NO_MOCK_REASON,
|
||||||
|
MagicMock,
|
||||||
|
patch
|
||||||
|
)
|
||||||
|
|
||||||
|
ensure_in_syspath('../../')
|
||||||
|
|
||||||
|
archive.__opts__ = {}
|
||||||
|
archive.__salt__ = {}
|
||||||
|
archive.__env__ = 'test'
|
||||||
|
|
||||||
|
|
||||||
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||||
|
class ArchiveTest(TestCase):
|
||||||
|
'''
|
||||||
|
Validate the archive state
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_extracted_tar(self):
|
||||||
|
'''
|
||||||
|
archive.extracted tar options
|
||||||
|
'''
|
||||||
|
|
||||||
|
source = 'file.tar.gz'
|
||||||
|
tmp_dir = os.path.join(tempfile.gettempdir(), 'test_archive')
|
||||||
|
test_tar_opts = [
|
||||||
|
'--no-anchored foo',
|
||||||
|
'v -p --opt',
|
||||||
|
'-v -p',
|
||||||
|
'--long-opt -z',
|
||||||
|
'z -v -weird-long-opt arg',
|
||||||
|
]
|
||||||
|
ret_tar_opts = [
|
||||||
|
['tar', 'x', '--no-anchored', 'foo', '-f'],
|
||||||
|
['tar', 'xv', '-p', '--opt', '-f'],
|
||||||
|
['tar', 'x', '-v', '-p', '-f'],
|
||||||
|
['tar', 'x', '--long-opt', '-z', '-f'],
|
||||||
|
['tar', 'xz', '-v', '-weird-long-opt', 'arg', '-f'],
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_true = MagicMock(return_value=True)
|
||||||
|
mock_false = MagicMock(return_value=False)
|
||||||
|
ret = {'stdout': ['saltines', 'cheese'], 'stderr': 'biscuits', 'retcode': '31337', 'pid': '1337'}
|
||||||
|
mock_run = MagicMock(return_value=ret)
|
||||||
|
|
||||||
|
with patch('os.path.exists', mock_true):
|
||||||
|
with patch.dict(archive.__opts__, {'test': False,
|
||||||
|
'cachedir': tmp_dir}):
|
||||||
|
with patch.dict(archive.__salt__, {'file.directory_exists': mock_false,
|
||||||
|
'file.file_exists': mock_false,
|
||||||
|
'file.makedirs': mock_true,
|
||||||
|
'cmd.run_all': mock_run}):
|
||||||
|
for test_opts, ret_opts in zip(test_tar_opts, ret_tar_opts):
|
||||||
|
ret = archive.extracted(tmp_dir,
|
||||||
|
source,
|
||||||
|
'tar',
|
||||||
|
tar_options=test_opts)
|
||||||
|
ret_opts.append(os.path.join(tmp_dir, 'files/test/_tmp_test_archive.tar'))
|
||||||
|
mock_run.assert_called_with(ret_opts, cwd=tmp_dir, python_shell=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from integration import run_tests
|
||||||
|
run_tests(ArchiveTest)
|
@ -3,7 +3,7 @@
|
|||||||
:codeauthor: :email:`Nicole Thomas (nicole@saltstack.com)`
|
:codeauthor: :email:`Nicole Thomas (nicole@saltstack.com)`
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Import Pyhton Libs
|
# Import Python Libs
|
||||||
from inspect import ArgSpec
|
from inspect import ArgSpec
|
||||||
|
|
||||||
# Import Salt Libs
|
# Import Salt Libs
|
||||||
|
Loading…
Reference in New Issue
Block a user