Merge remote-tracking branch 'saltstack/develop' into develop

This commit is contained in:
tsclausing 2012-11-26 22:41:53 -06:00
commit 84ce8160cf
15 changed files with 383 additions and 226 deletions

7
debian/rules vendored
View File

@ -1,6 +1,13 @@
#!/usr/bin/make -f
ifeq ($(shell dpkg-vendor --is Ubuntu && echo yes),yes)
UPSTART := pkg/*.upstart
endif
PKGFILES := pkg/*.service $(UPSTART)
%:
dh $@
cp $(PKGFILES) debian
make -C doc html
# overrides require debuilder >= 7.0.50
# http://pkg-perl.alioth.debian.org/debhelper.html#forcing_special_tests

View File

@ -1,10 +0,0 @@
[Unit]
Description=The Salt Master Server
After=syslog.target network.target
[Service]
Type=simple
ExecStart=/usr/bin/salt-master -d
[Install]
WantedBy=multi-user.target

View File

@ -1,10 +0,0 @@
[Unit]
Description=The Salt Minion
After=syslog.target network.target
[Service]
Type=simple
ExecStart=/usr/bin/salt-minion -d
[Install]
WantedBy=multi-user.target

View File

@ -1,10 +0,0 @@
[Unit]
Description=The Salt Master Server
After=syslog.target network.target
[Service]
Type=simple
ExecStart=/usr/bin/salt-syndic -d
[Install]
WantedBy=multi-user.target

View File

@ -15,8 +15,9 @@ class Batch(object):
'''
Manage the execution of batch runs
'''
def __init__(self, opts):
def __init__(self, opts, quiet=False):
self.opts = opts
self.quiet = quiet
self.local = salt.client.LocalClient(opts['conf_file'])
self.minions = self.__gather_minions()
@ -39,6 +40,7 @@ class Batch(object):
fret = []
for ret in self.local.cmd_iter(*args):
for minion in ret:
if not self.quiet:
print('{0} Detected for this batch run'.format(minion))
fret.append(minion)
return sorted(fret)
@ -58,6 +60,7 @@ class Batch(object):
else:
return int(self.opts['batch'])
except ValueError:
if not quiet:
print(('Invalid batch data sent: {0}\nData must be in the form'
'of %10, 10% or 3').format(self.opts['batch']))
@ -90,6 +93,7 @@ class Batch(object):
active += next_
args[0] = next_
if next_:
if not quiet:
print('\nExecuting run on {0}\n'.format(next_))
iters.append(
self.local.cmd_iter_no_block(*args))
@ -114,14 +118,15 @@ class Batch(object):
pass
for minion, data in parts.items():
active.remove(minion)
yield data['ret']
ret[minion] = data['ret']
data[minion] = data.pop('ret')
if 'out' in data:
out = data.pop('out')
else:
out = None
if not self.quiet:
salt.output.display_output(
data,
out,
self.opts)
return ret

View File

@ -216,6 +216,7 @@ def minion_config(path, check_dns=True):
'update_url': False,
'update_restart_services': [],
'retry_dns': 30,
'recon_max': 30000,
}

View File

@ -630,6 +630,7 @@ class Minion(object):
socket = context.socket(zmq.SUB)
socket.setsockopt(zmq.SUBSCRIBE, '')
socket.setsockopt(zmq.IDENTITY, self.opts['id'])
socket.setsockopt(zmq.RECONNECT_IVL_MAX, self.opts['recon_max'])
socket.connect(self.master_pub)
poller.register(socket, zmq.POLLIN)
epoller.register(epull_sock, zmq.POLLIN)

View File

@ -173,7 +173,11 @@ def cache_file(path, env='base'):
salt '*' cp.cache_file salt://path/to/file
'''
client = salt.fileclient.get_file_client(__opts__)
return client.cache_file(path, env)
result = client.cache_file(path, env)
if not result:
log.error('Unable to cache file "{0}" from env '
'"{1}".'.format(path,env))
return result
def cache_files(paths, env='base'):

View File

@ -617,6 +617,8 @@ def patch(originalfile, patchfile, options='', dry_run=False):
dry_run_opt = ' -C'
else:
dry_run_opt = ' --dry-run'
else:
dry_run_opt = ''
cmd = 'patch {0}{1} {2} {3}'.format(
options, dry_run_opt, originalfile, patchfile)
return __salt__['cmd.run_all'](cmd)

84
salt/modules/keyboard.py Normal file
View File

@ -0,0 +1,84 @@
'''
Module for managing keyboards on posix-like systems.
'''
import logging
log = logging.getLogger(__name__)
def __virtual__():
'''
Only work on posix-like systems
'''
# Disable on these platorms, specific service modules exist:
disable = [
'Windows',
]
if __grains__['os'] in disable:
return False
return 'keyboard'
def get_sys():
'''
Get current system keyboard setting
CLI Example::
salt '*' keyboard.get_sys
'''
cmd = ''
if 'Arch' in __grains__['os_family']:
cmd = 'grep KEYMAP /etc/rc.conf | grep -vE "^#"'
elif 'RedHat' in __grains__['os_family']:
cmd = 'grep LAYOUT /etc/sysconfig/keyboard | grep -vE "^#"'
elif 'Debian' in __grains__['os_family']:
cmd = 'grep XKBLAYOUT /etc/default/keyboard | grep -vE "^#"'
out = __salt__['cmd.run'](cmd).split('=')
ret = out[1].replace('"', '')
return ret
def set_sys(layout):
'''
Set current system keyboard setting
CLI Example::
salt '*' keyboard.set_sys dvorak
'''
if 'Arch' in __grains__['os_family']:
__salt__['file.sed']('/etc/rc.conf', '^KEYMAP=.*', 'KEYMAP={0}'.format(layout))
elif 'RedHat' in __grains__['os_family']:
__salt__['file.sed']('/etc/sysconfig/keyboard', '^LAYOUT=.*', 'LAYOUT={0}'.format(layout))
elif 'Debian' in __grains__['os_family']:
__salt__['file.sed']('/etc/default/keyboard', '^XKBLAYOUT=.*', 'XKBLAYOUT={0}'.format(layout))
return layout
def get_x():
'''
Get current X keyboard setting
CLI Example::
salt '*' keyboard.get_x
'''
cmd = 'setxkbmap -query | grep layout'
out = __salt__['cmd.run'](cmd).split(':')
return out[1].strip()
def set_x(layout):
'''
Set current X keyboard setting
CLI Example::
salt '*' keyboard.set_x dvorak
'''
cmd = 'setxkbmap {0}'.format(layout)
__salt__['cmd.run'](cmd)
return layout

View File

@ -29,43 +29,42 @@ def _list_removed(old, new):
return pkgs
def _compare_versions(old, new):
def _write_adminfile(kwargs):
'''
Returns a dict that that displays old and new versions for a package after
install/upgrade of package.
Create a temporary adminfile based on the keyword arguments passed to
pkg.install.
'''
pkgs = {}
for npkg in new:
if npkg in old:
if old[npkg] == new[npkg]:
# no change in the package
continue
else:
# the package was here before and the version has changed
pkgs[npkg] = {'old': old[npkg],
'new': new[npkg]}
else:
# the package is freshly installed
pkgs[npkg] = {'old': '',
'new': new[npkg]}
return pkgs
# Set the adminfile default variables
email = kwargs.get('email', '')
instance = kwargs.get('instance', 'quit')
partial = kwargs.get('partial', 'nocheck')
runlevel = kwargs.get('runlevel', 'nocheck')
idepend = kwargs.get('idepend', 'nocheck')
rdepend = kwargs.get('rdepend', 'nocheck')
space = kwargs.get('space', 'nocheck')
setuid = kwargs.get('setuid', 'nocheck')
conflict = kwargs.get('conflict', 'nocheck')
action = kwargs.get('action', 'nocheck')
basedir = kwargs.get('basedir', 'default')
# Make tempfile to hold the adminfile contents.
fd, adminfile = salt.utils.mkstemp(prefix="salt-", close_fd=False)
def _get_pkgs():
'''
Get a full list of the package installed on the machine
'''
pkg = {}
cmd = '/usr/bin/pkginfo -x'
# Write to file then close it.
os.write(fd, 'email={0}\n'.format(email))
os.write(fd, 'instance={0}\n'.format(instance))
os.write(fd, 'partial={0}\n'.format(partial))
os.write(fd, 'runlevel={0}\n'.format(runlevel))
os.write(fd, 'idepend={0}\n'.format(idepend))
os.write(fd, 'rdepend={0}\n'.format(rdepend))
os.write(fd, 'space={0}\n'.format(space))
os.write(fd, 'setuid={0}\n'.format(setuid))
os.write(fd, 'conflict={0}\n'.format(conflict))
os.write(fd, 'action={0}\n'.format(action))
os.write(fd, 'basedir={0}\n'.format(basedir))
os.close(fd)
line_count = 0
for line in __salt__['cmd.run'](cmd).splitlines():
if line_count % 2 == 0:
namever = line.split()[0].strip()
if line_count % 2 == 1:
pkg[namever] = line.split()[1].strip()
line_count = line_count + 1
return pkg
return adminfile
def list_pkgs():
@ -78,7 +77,17 @@ def list_pkgs():
salt '*' pkg.list_pkgs
'''
return _get_pkgs()
pkg = {}
cmd = '/usr/bin/pkginfo -x'
line_count = 0
for line in __salt__['cmd.run'](cmd).splitlines():
if line_count % 2 == 0:
namever = line.split()[0].strip()
if line_count % 2 == 1:
pkg[namever] = line.split()[1].strip()
line_count = line_count + 1
return pkg
def version(name):
@ -109,7 +118,7 @@ def available_version(name):
return version(name)
def install(name, refresh=False, **kwargs):
def install(name=None, refresh=False, sources=None, **kwargs):
'''
Install the passed package. Can install packages from the following
sources::
@ -127,18 +136,18 @@ def install(name, refresh=False, **kwargs):
CLI Example, installing a datastream pkg that already exists on the
minion::
salt '*' pkg.install <package name once installed> source=/dir/on/minion/<package filename>
salt '*' pkg.install SMClgcc346 source=/var/spool/pkg/gcc-3.4.6-sol10-sparc-local.pkg
salt '*' pkg.install sources='[{"<pkg name>": "/dir/on/minion/<pkg filename>"}]'
salt '*' pkg.install sources='[{"SMClgcc346": "/var/spool/pkg/gcc-3.4.6-sol10-sparc-local.pkg"}]'
CLI Example, installing a datastream pkg that exists on the salt master::
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>'
salt '*' pkg.install SMClgcc346 source='salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg'
salt '*' pkg.install sources='[{"<pkg name>": "salt://pkgs/<pkg filename>"}]'
salt '*' pkg.install sources='[{"SMClgcc346": "salt://pkgs/gcc-3.4.6-sol10-sparc-local.pkg"}]'
CLI Example, installing a datastream pkg that exists on a HTTP server::
salt '*' pkg.install <package name once installed> source='http://packages.server.com/<package filename>'
salt '*' pkg.install SMClgcc346 source='http://packages.server.com/gcc-3.4.6-sol10-sparc-local.pkg'
salt '*' pkg.install sources='[{"<pkg name>": "http://packages.server.com/<pkg filename>"}]'
salt '*' pkg.install sources='[{"SMClgcc346": "http://packages.server.com/gcc-3.4.6-sol10-sparc-local.pkg"}]'
If working with solaris zones and you want to install a package only in the
global zone you can pass 'current_zone_only=True' to salt to have the
@ -149,7 +158,7 @@ def install(name, refresh=False, **kwargs):
CLI Example, installing a datastream package only in the global zone::
salt 'global_zone' pkg.install SMClgcc346 source=/var/spool/pkg/gcc-3.4.6-sol10-sparc-local.pkg current_zone_only=True
salt 'global_zone' pkg.install sources='[{"SMClgcc346": "/var/spool/pkg/gcc-3.4.6-sol10-sparc-local.pkg"}]' current_zone_only=True
By default salt automatically provides an adminfile, to automate package
installation, with these options set:
@ -179,97 +188,72 @@ def install(name, refresh=False, **kwargs):
CLI Example - Overriding the 'instance' adminfile option when calling the
module directly::
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>' instance="overwrite"
salt '*' pkg.install sources='[{"<pkg name>": "salt://pkgs/<pkg filename>"}]' instance="overwrite"
CLI Example - Overriding the 'instance' adminfile option when used in a
state::
SMClgcc346:
pkg.installed:
- source: salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg
- sources:
- SMClgcc346: salt://srv/salt/pkgs/gcc-3.4.6-sol10-sparc-local.pkg
- instance: overwrite
Note: the ID declaration is ignored, as the package name is read from the
"sources" parameter.
CLI Example - Providing your own adminfile when calling the module
directly::
salt '*' pkg.install <package name once installed> source='salt://srv/salt/pkgs/<package filename>' admin_source='salt://srv/salt/pkgs/<adminfile filename>'
salt '*' pkg.install sources='[{"<pkg name>": "salt://pkgs/<pkg filename>"}]' admin_source='salt://pkgs/<adminfile filename>'
CLI Example - Providing your own adminfile when using states::
<package name once installed>:
<pkg name>:
pkg.installed:
- source: salt://srv/salt/pkgs/<package filename>
- admin_source: salt://srv/salt/pkgs/<adminfile filename>
- sources:
- <pkg name>: salt://pkgs/<pkg filename>
- admin_source: salt://pkgs/<adminfile filename>
Note: the ID declaration is ignored, as the package name is read from the
"sources" parameter.
'''
if not 'source' in kwargs:
return 'source option required with solaris pkg installs'
else:
if (kwargs['source']).startswith('salt://') \
or (kwargs['source']).startswith('http://') \
or (kwargs['source']).startswith('https://') \
or (kwargs['source']).startswith('ftp://'):
pkgname = __salt__['cp.cache_file'](kwargs['source'])
else:
pkgname = (kwargs['source'])
pkg_params,pkg_type = __salt__['pkg_resource.parse_targets'](
name,kwargs.get('pkgs'),sources)
if pkg_params is None or len(pkg_params) == 0:
return {}
if 'admin_source' in kwargs:
adminfile = __salt__['cp.cache_file'](kwargs['admin_source'])
else:
# Set the adminfile default variables
email = kwargs.get('email', '')
instance = kwargs.get('instance', 'quit')
partial = kwargs.get('partial', 'nocheck')
runlevel = kwargs.get('runlevel', 'nocheck')
idepend = kwargs.get('idepend', 'nocheck')
rdepend = kwargs.get('rdepend', 'nocheck')
space = kwargs.get('space', 'nocheck')
setuid = kwargs.get('setuid', 'nocheck')
conflict = kwargs.get('conflict', 'nocheck')
action = kwargs.get('action', 'nocheck')
basedir = kwargs.get('basedir', 'default')
# Make tempfile to hold the adminfile contents.
fd, adminfile = salt.utils.mkstemp(prefix="salt-", close_fd=False)
# Write to file then close it.
os.write(fd, 'email={0}\n'.format(email))
os.write(fd, 'email={instance={0}\n'.format(instance))
os.write(fd, 'email={partial={0}\n'.format(partial))
os.write(fd, 'email={runlevel={0}\n'.format(runlevel))
os.write(fd, 'email={idepend={0}\n'.format(idepend))
os.write(fd, 'email={rdepend={0}\n'.format(rdepend))
os.write(fd, 'email={space={0}\n'.format(space))
os.write(fd, 'email={setuid={0}\n'.format(setuid))
os.write(fd, 'email={conflict={0}\n'.format(conflict))
os.write(fd, 'email={action={0}\n'.format(action))
os.write(fd, 'email={basedir={0}\n'.format(basedir))
os.close(fd)
adminfile = _write_adminfile(kwargs)
# Get a list of the packages before install so we can diff after to see
# what got installed.
old = _get_pkgs()
old = list_pkgs()
cmd = '/usr/sbin/pkgadd -n -a {0} '.format(adminfile)
# Global only?
# Only makes sense in a global zone but works fine in non-globals.
if kwargs.get('current_zone_only') == 'True':
cmd += '-G '
cmd += '-d {0} \'all\''.format(pkgname)
# Install the package
__salt__['cmd.retcode'](cmd)
for pkg in pkg_params:
temp_cmd = cmd + '-d {0} "all"'.format(pkg)
# Install the package{s}
stderr = __salt__['cmd.run_all'](temp_cmd).get('stderr','')
if stderr:
log.error(stderr)
# Get a list of the packages again, including newly installed ones.
new = _get_pkgs()
new = list_pkgs()
# Remove the temp adminfile
if not 'admin_source' in kwargs:
os.unlink(adminfile)
# Return a list of the new package installed.
return _compare_versions(old, new)
return __salt__['pkg_resource.find_changes'](old,new)
def remove(name, **kwargs):
@ -345,7 +329,7 @@ def remove(name, **kwargs):
os.close(fd)
# Get a list of the currently installed pkgs.
old = _get_pkgs()
old = list_pkgs()
# Remove the package
cmd = '/usr/sbin/pkgrm -n -a {0} {1}'.format(adminfile, name)
@ -356,7 +340,7 @@ def remove(name, **kwargs):
os.unlink(adminfile)
# Get a list of the packages after the uninstall
new = _get_pkgs()
new = list_pkgs()
# Compare the pre and post remove package objects and report the
# uninstalled pkgs.

View File

@ -14,6 +14,11 @@ try:
except ImportError:
pass
import logging
log = logging.getLogger(__name__)
def __virtual__():
'''
Set the virtual pkg module if the os is Windows
@ -40,7 +45,7 @@ def available_version(name):
salt '*' pkg.available_version <package name>
'''
return 'Not implemented on Windows yet'
return 'pkg.available_version not implemented on Windows yet'
def upgrade_available(name):
@ -51,7 +56,8 @@ def upgrade_available(name):
salt '*' pkg.upgrade_available <package name>
'''
return 'Not implemented on Windows yet'
log.warning('pkg.upgrade_available not implemented on Windows yet')
return False
def list_upgrades():
@ -62,7 +68,8 @@ def list_upgrades():
salt '*' pkg.list_upgrades
'''
return 'Not implemented on Windows yet'
log.warning('pkg.list_upgrades not implemented on Windows yet')
return {}
def version(name):
@ -263,10 +270,11 @@ def refresh_db():
salt '*' pkg.refresh_db
'''
return 'Not implemented on Windows yet'
log.warning('pkg.refresh_db not implemented on Windows yet')
return {}
def install(name, refresh=False, **kwargs):
def install(name=None, refresh=False, **kwargs):
'''
Install the passed package
@ -279,7 +287,8 @@ def install(name, refresh=False, **kwargs):
salt '*' pkg.install <package name>
'''
return 'Not implemented on Windows yet'
log.warning('pkg.install not implemented on Windows yet')
return {}
def upgrade():
@ -295,7 +304,8 @@ def upgrade():
salt '*' pkg.upgrade
'''
return 'Not implemented on Windows yet'
log.warning('pkg.upgrade not implemented on Windows yet')
return {}
def remove(name):
@ -308,7 +318,8 @@ def remove(name):
salt '*' pkg.remove <package name>
'''
return 'Not implemented on Windows yet'
log.warning('pkg.remove not implemented on Windows yet')
return []
def purge(name):
@ -322,4 +333,5 @@ def purge(name):
salt '*' pkg.purge <package name>
'''
return 'Not implemented on Windows yet'
log.warning('pkg.purge not implemented on Windows yet')
return []

View File

@ -175,7 +175,18 @@ def version(name):
salt '*' pkg.version <package name>
'''
# since list_pkgs is used to support matching complex versions
# we can search for a digit in the name and if one doesn't exist
# then just use the dbMatch function, which is 1000 times quicker
m = re.search("[0-9]", name)
if m:
pkgs = list_pkgs(name)
else:
ts = rpm.TransactionSet()
mi = ts.dbMatch('name', name)
pkgs = {}
for h in mi:
pkgs[h['name']] = "-".join([h['version'], h['release']])
if name in pkgs:
return pkgs[name]
else:

View File

@ -230,6 +230,10 @@ else:
'scripts/salt-call',
'scripts/salt-run',
'scripts/salt']
# Distutils does not know what these are and throws warnings.
# Stop the warning.
setup_kwargs.pop('install_requires')
setup_kwargs.pop('zip_safe')
if __name__ == '__main__':
setup(**setup_kwargs)

View File

@ -56,7 +56,6 @@ SYS_TMP_DIR = tempfile.gettempdir()
TMP = os.path.join(SYS_TMP_DIR, 'salt-tests-tmpdir')
FILES = os.path.join(INTEGRATION_TEST_DIR, 'files')
MOCKBIN = os.path.join(INTEGRATION_TEST_DIR, 'mockbin')
MINIONS_CONNECT_TIMEOUT = MINIONS_SYNC_TIMEOUT = 60
def print_header(header, sep='~', top=True, bottom=True, inline=False,
@ -128,6 +127,7 @@ class TestDaemon(object):
'''
Set up the master and minion daemons, and run related cases
'''
MINIONS_CONNECT_TIMEOUT = MINIONS_SYNC_TIMEOUT = 60
def __init__(self, opts=None):
self.opts = opts
@ -250,33 +250,9 @@ class TestDaemon(object):
os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf', 'master')
)
evt_minions_connect = multiprocessing.Event()
evt_minions_sync = multiprocessing.Event()
minion_targets = set(['minion', 'sub_minion'])
# Wait for minions to connect back
wait_minions_connection = multiprocessing.Process(
target=self.__wait_for_minions_connections,
args=(evt_minions_connect, minion_targets)
)
wait_minions_connection.start()
if evt_minions_connect.wait(MINIONS_CONNECT_TIMEOUT) is False:
print('WARNING: Minions failed to connect back. Tests requiring '
'them WILL fail')
wait_minions_connection.terminate()
del(evt_minions_connect, wait_minions_connection)
# Wait for minions to "sync_all"
sync_minions = multiprocessing.Process(
target=self.__sync_minions,
args=(evt_minions_sync, minion_targets)
)
sync_minions.start()
if evt_minions_sync.wait(MINIONS_SYNC_TIMEOUT) is False:
print('WARNING: Minions failed to sync. Tests requiring the '
'testing `runtests_helper` module WILL fail')
sync_minions.terminate()
del(evt_minions_sync, sync_minions)
self.minion_targets = set(['minion', 'sub_minion'])
self.pre_setup_minions()
self.setup_minions()
if self.opts.sysinfo:
from salt import version
@ -292,7 +268,10 @@ class TestDaemon(object):
print_header('', sep='=', inline=True)
try:
return self
finally:
self.post_setup_minions()
def __exit__(self, type, value, traceback):
'''
@ -306,6 +285,43 @@ class TestDaemon(object):
self._exit_mockbin()
self._clean()
def pre_setup_minions(self):
"""
Subclass this method for additional minion setups.
"""
def setup_minions(self):
# Wait for minions to connect back
wait_minion_connections = multiprocessing.Process(
target=self.wait_for_minion_connections,
args=(self.minion_targets, self.MINIONS_CONNECT_TIMEOUT)
)
wait_minion_connections.start()
wait_minion_connections.join()
wait_minion_connections.terminate()
if wait_minion_connections.exitcode > 0:
return False
del(wait_minion_connections)
# Wait for minions to "sync_all"
sync_minions = multiprocessing.Process(
target=self.sync_minions,
args=(self.minion_targets, self.MINIONS_SYNC_TIMEOUT)
)
sync_minions.start()
sync_minions.join()
if sync_minions.exitcode > 0:
return False
sync_minions.terminate()
del(sync_minions)
return True
def post_setup_minions(self):
"""
Subclass this method to execute code after the minions have been setup
"""
def _enter_mockbin(self):
path = os.environ.get('PATH', '')
path_items = path.split(os.pathsep)
@ -338,14 +354,22 @@ class TestDaemon(object):
shutil.rmtree(TMP)
def wait_for_jid(self, targets, jid, timeout=120):
time.sleep(1) # Allow some time for minions to accept jobs
now = datetime.now()
expire = now + timedelta(seconds=timeout)
job_finished = False
while now <= expire:
running = self.__client_job_running(targets, jid)
sys.stdout.write('\r' + ' ' * PNUM + '\r')
if not running:
print
if not running and job_finished is False:
# Let's not have false positives and wait one more seconds
job_finished = True
elif not running and job_finished is True:
return True
elif running and job_finished is True:
job_finished = False
if job_finished is False:
sys.stdout.write(
' * {YELLOW}[Quit in {0}]{ENDC} Waiting for {1}'.format(
'{0}'.format(expire - now).rsplit('.', 1)[0],
@ -354,11 +378,13 @@ class TestDaemon(object):
)
)
sys.stdout.flush()
timeout -= 1
time.sleep(1)
now = datetime.now()
else:
sys.stdout.write('\n * ERROR: Failed to get information back\n')
sys.stdout.write(
'\n {RED_BOLD}*{ENDC} ERROR: Failed to get information '
'back\n'.format(**self.colors)
)
sys.stdout.flush()
return False
@ -370,35 +396,71 @@ class TestDaemon(object):
k for (k, v) in running.iteritems() if v and v[0]['jid'] == jid
]
def __wait_for_minions_connections(self, evt, targets):
print_header(
'Waiting at most {0} secs for local minions to connect '
'back and another {1} secs for them to '
'"saltutil.sync_all()"'.format(
MINIONS_CONNECT_TIMEOUT, MINIONS_SYNC_TIMEOUT
), sep='=', centered=True
def wait_for_minion_connections(self, targets, timeout):
sys.stdout.write(
' {LIGHT_BLUE}*{ENDC} Waiting at most {0} for minions({1}) to '
'connect back\n'.format(
(timeout > 60 and
timedelta(seconds=timeout) or
'{0} secs'.format(timeout)),
', '.join(targets),
**self.colors
)
targets = set(['minion', 'sub_minion'])
)
sys.stdout.flush()
expected_connections = set(targets)
while True:
# If enough time passes, a timeout will be triggered by
# multiprocessing.Event, so, we can have this while True here
targets = self.client.cmd('*', 'test.ping')
for target in targets:
now = datetime.now()
expire = now + timedelta(seconds=timeout)
while now <= expire:
sys.stdout.write('\r' + ' ' * PNUM + '\r')
sys.stdout.write(
' * {YELLOW}[Quit in {0}]{ENDC} Waiting for {1}'.format(
'{0}'.format(expire - now).rsplit('.', 1)[0],
', '.join(expected_connections),
**self.colors
)
)
sys.stdout.flush()
responses = self.client.cmd(
','.join(expected_connections), 'test.ping', expr_form='list',
)
for target in responses:
if target not in expected_connections:
# Someone(minion) else "listening"?
print target
continue
expected_connections.remove(target)
print(' * {0} minion connected'.format(target))
if not expected_connections:
# All expected connections have connected
break
time.sleep(1)
evt.set()
sys.stdout.write('\r' + ' ' * PNUM + '\r')
sys.stdout.write(
' {LIGHT_GREEN}*{ENDC} {0} connected.\n'.format(
target, **self.colors
)
)
sys.stdout.flush()
def __sync_minions(self, evt, targets):
if not expected_connections:
return
time.sleep(1)
now = datetime.now()
else:
print(
'\n {RED_BOLD}*{ENDC} WARNING: Minions failed to connect '
'back. Tests requiring them WILL fail'.format(**self.colors)
)
print_header('=', sep='=', inline=True)
raise SystemExit()
def sync_minions(self, targets, timeout=120):
# Let's sync all connected minions
print(' * Syncing local minion\'s dynamic data(saltutil.sync_all)')
print(
' {LIGHT_BLUE}*{ENDC} Syncing minion\'s dynamic '
'data(saltutil.sync_all)'.format(
', '.join(targets),
**self.colors
)
)
syncing = set(targets)
jid_info = self.client.run_job(
','.join(targets), 'saltutil.sync_all',
@ -406,23 +468,33 @@ class TestDaemon(object):
timeout=9999999999999999,
)
if self.wait_for_jid(targets, jid_info['jid']) is False:
evt.set()
return
if self.wait_for_jid(targets, jid_info['jid'], timeout) is False:
print(
' {RED_BOLD}*{ENDC} WARNING: Minions failed to sync. Tests '
'requiring custom modules WILL fail'.format(**self.colors)
)
raise SystemExit()
while syncing:
rdata = self.client.get_returns(jid_info['jid'], syncing, 1)
if rdata:
for idx, (name, output) in enumerate(rdata.iteritems()):
print(' * Synced {0}: {1}'.format(name, output))
for name, output in rdata.iteritems():
print(
' {LIGHT_GREEN}*{ENDC} Synced {0}: modules=>{1} '
'states=>{2} grains=>{3} renderers=>{4} '
'returners=>{5}'.format(
name, *output, **self.colors
)
)
# Synced!
try:
syncing.remove(name)
except KeyError:
print(' * {0} already synced??? {1}'.format(
name, output
))
evt.set()
print(
' {RED_BOLD}*{ENDC} {0} already synced??? '
'{1}'.format(name, output, **self.colors)
)
return True
class ModuleCase(TestCase):