diff --git a/salt/daemons/masterapi.py b/salt/daemons/masterapi.py index 8219e51a5f..95b42c4cea 100644 --- a/salt/daemons/masterapi.py +++ b/salt/daemons/masterapi.py @@ -5,9 +5,10 @@ involves preparing the three listeners and the workers needed by the master. ''' # Import python libs +import fnmatch +import logging import os import re -import logging import time try: import pwd @@ -76,7 +77,7 @@ def clean_fsbackend(opts): ''' Clean out the old fileserver backends ''' - # Clear remote fileserver backend env cache so it gets recreated + # Clear remote fileserver backend caches so they get recreated for backend in ('git', 'hg', 'svn'): if backend in opts['fileserver_backend']: env_cache = os.path.join( @@ -94,6 +95,25 @@ def clean_fsbackend(opts): .format(env_cache, exc) ) + file_lists_dir = os.path.join( + opts['cachedir'], + 'file_lists', + '{0}fs'.format(backend) + ) + try: + file_lists_caches = os.listdir(file_lists_dir) + except OSError: + continue + for file_lists_cache in fnmatch.filter(file_lists_caches, '*.p'): + cache_file = os.path.join(file_lists_dir, file_lists_cache) + try: + os.remove(cache_file) + except (IOError, OSError) as exc: + log.critical( + 'Unable to file_lists cache file {0}: {1}' + .format(cache_file, exc) + ) + def clean_expired_tokens(opts): ''' @@ -704,7 +724,7 @@ class RemoteFuncs(object): if 'jid' in minion: ret['__jid__'] = minion['jid'] for key, val in self.local.get_cache_returns(ret['__jid__']).items(): - if not key in ret: + if key not in ret: ret[key] = val if load.get('form', '') != 'full': ret.pop('__jid__') diff --git a/salt/fileserver/gitfs.py b/salt/fileserver/gitfs.py index 94f01d77ba..89d2103a94 100644 --- a/salt/fileserver/gitfs.py +++ b/salt/fileserver/gitfs.py @@ -590,6 +590,9 @@ def init(): 'hash': repo_hash, 'cachedir': rp_ }) + # Strip trailing slashes from the gitfs root as these cause + # path searches to fail. + repo_conf['root'] = repo_conf['root'].rstrip(os.path.sep) repos.append(repo_conf) except Exception as exc: diff --git a/salt/minion.py b/salt/minion.py index 3917baf989..fae236883a 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -1254,6 +1254,25 @@ class Minion(MinionBase): ).compile_pillar() self.module_refresh() + def manage_schedule(self, package): + ''' + Refresh the functions and returners. + ''' + tag, data = salt.utils.event.MinionEvent.unpack(package) + func = data.get('func', None) + + if func == 'delete': + job = data.get('job', None) + self.schedule.delete_job(job) + elif func == 'add': + name = data.get('name', None) + schedule = data.get('schedule', None) + self.schedule.add_job(name, schedule) + elif func == 'modify': + name = data.get('name', None) + schedule = data.get('schedule', None) + self.schedule.modify_job(name, schedule) + def environ_setenv(self, package): ''' Set the salt-minion main process environment according to @@ -1400,6 +1419,8 @@ class Minion(MinionBase): self.module_refresh() elif package.startswith('pillar_refresh'): self.pillar_refresh() + elif package.startswith('manage_schedule'): + self.manage_schedule(package) elif package.startswith('grains_refresh'): if self.grains_cache != self.opts['grains']: self.pillar_refresh() diff --git a/salt/modules/kmod.py b/salt/modules/kmod.py index e3f4df36cc..0f7ffe96c8 100644 --- a/salt/modules/kmod.py +++ b/salt/modules/kmod.py @@ -129,7 +129,7 @@ def available(): for root, dirs, files in os.walk(mod_dir): for fn_ in files: if '.ko' in fn_: - ret.append(fn_[:fn_.index('.ko')]) + ret.append(fn_[:fn_.index('.ko')].replace('-', '_')) return sorted(list(ret)) diff --git a/salt/modules/schedule.py b/salt/modules/schedule.py new file mode 100644 index 0000000000..4a372416c4 --- /dev/null +++ b/salt/modules/schedule.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +''' +Module for manging the Salt schedule on a minion + +.. versionadded:: Helium + +''' + +# Import Python libs +import os +import yaml + +import salt.utils + +__proxyenabled__ = ['*'] + +import logging +log = logging.getLogger(__name__) + +SCHEDULE_CONF = [ + 'function', + 'splay', + 'range', + 'when', + 'returner', + 'jid_include', + 'args', + 'kwargs', + '_seconds', + 'seconds', + 'minutes', + 'hours', + 'days' + ] + + +def list(): + ''' + List the jobs currently scheduled on the minion + + CLI Example: + + .. code-block:: bash + + salt '*' schedule.list + ''' + + schedule = __opts__['schedule'] + for job in schedule.keys(): + if job.startswith('_'): + del schedule[job] + continue + + for item in schedule[job].keys(): + if not item in SCHEDULE_CONF: + del schedule[job][item] + continue + if schedule[job][item] == 'true': + schedule[job][item] = True + if schedule[job][item] == 'false': + schedule[job][item] = False + + if '_seconds' in schedule[job].keys(): + schedule[job]['seconds'] = schedule[job]['_seconds'] + del schedule[job]['_seconds'] + + if schedule: + tmp = {'schedule': schedule} + yaml_out = yaml.safe_dump(tmp, default_flow_style=False) + return yaml_out + else: + return None + + +def purge(): + ''' + Purge all the jobs currently scheduled on the minion + + CLI Example: + + .. code-block:: bash + + salt '*' schedule.purge + ''' + + ret = {'comment': [], + 'result': True} + + schedule = __opts__['schedule'] + for job in schedule.keys(): + if job.startswith('_'): + continue + + out = __salt__['event.fire']({'job': job, 'func': 'delete'}, 'manage_schedule') + if out: + ret['comment'].append('Deleted job: {0} from schedule.'.format(job)) + else: + ret['comment'].append('Failed to delete job {0} from schedule.'.format(job)) + ret['result'] = False + return ret + + +def delete(name): + ''' + Delete a job from the minion's schedule + + CLI Example: + + .. code-block:: bash + + salt '*' schedule.delete job1 + ''' + + ret = {'comment': [], + 'result': True} + + if not name: + ret['comment'] = 'Job name is required.' + ret['result'] = False + + if name in __opts__['schedule']: + out = __salt__['event.fire']({'job': name, 'func': 'delete'}, 'manage_schedule') + if out: + ret['comment'] = 'Deleted Job {0} from schedule.'.format(name) + else: + ret['comment'] = 'Failed to delete job {0} from schedule.'.format(name) + ret['result'] = False + else: + ret['comment'] = 'Job {0} does not exist.'.format(name) + ret['result'] = False + return ret + + +def add(name, **kwargs): + ''' + Add a job to the schedule + + CLI Example: + + .. code-block:: bash + + salt '*' schedule.add job1 function='test.ping' seconds=3600 + ''' + + ret = {'comment': [], + 'result': True} + + if name in __opts__['schedule']: + ret['comment'] = 'Job {0} already exists in schedule.'.format(name) + ret['result'] = True + return ret + + if not name: + ret['comment'] = 'Job name is required.' + ret['result'] = False + + schedule = {'function': kwargs['function']} + + time_conflict = False + for item in ['seconds', 'minutes', 'hours', 'days']: + if item in kwargs and 'when' in kwargs: + time_conflict = True + + if time_conflict: + return 'Error: Unable to use "seconds", "minutes", "hours", or "days" with "when" option.' + + for item in ['seconds', 'minutes', 'hours', 'days']: + if item in kwargs: + schedule[item] = kwargs[item] + + if 'job_args' in kwargs: + schedule['args'] = kwargs['job_args'] + + if 'job_kwargs' in kwargs: + schedule['kwargs'] = kwargs['job_kwargs'] + + for item in ['splay', 'range', 'when', 'returner', 'jid_include']: + if item in kwargs: + schedule[item] = kwargs[item] + + out = __salt__['event.fire']({'name': name, 'schedule': schedule, 'func': 'add'}, 'manage_schedule') + if out: + ret['comment'] = 'Added job: {0} to schedule.'.format(name) + else: + ret['comment'] = 'Failed to modify job {0} to schedule.'.format(name) + ret['result'] = False + return ret + + +def modify(name, **kwargs): + ''' + Modify an existing job in the schedule + + CLI Example: + + .. code-block:: bash + + salt '*' schedule.modify job1 function='test.ping' seconds=3600 + ''' + + ret = {'comment': [], + 'result': True} + + if not name in __opts__['schedule']: + ret['comment'] = 'Job {0} does not exist in schedule.'.format(name) + ret['result'] = False + return ret + + schedule = {'function': kwargs['function']} + + time_conflict = False + for item in ['seconds', 'minutes', 'hours', 'days']: + if item in kwargs and 'when' in kwargs: + time_conflict = True + + if time_conflict: + return 'Error: Unable to use "seconds", "minutes", "hours", or "days" with "when" option.' + + for item in ['seconds', 'minutes', 'hours', 'days']: + if item in kwargs: + schedule[item] = kwargs[item] + + if 'job_args' in kwargs: + schedule['args'] = kwargs['job_args'] + + if 'job_kwargs' in kwargs: + schedule['kwargs'] = kwargs['job_kwargs'] + + for item in ['splay', 'range', 'when', 'returner', 'jid_include']: + if item in kwargs: + schedule[item] = kwargs[item] + + out = __salt__['event.fire']({'name': name, 'schedule': schedule, 'func': 'modify'}, 'manage_schedule') + if out: + ret['comment'] = 'Modified job: {0} in schedule.'.format(name) + else: + ret['comment'] = 'Failed to modify job {0} in schedule.'.format(name) + ret['result'] = False + return ret + + +def save(): + ''' + + CLI Example: + + .. code-block:: bash + + salt '*' schedule.save + ''' + + ret = {'comment': [], + 'result': True} + + schedule = __opts__['schedule'] + for job in schedule.keys(): + if job.startswith('_'): + del schedule[job] + continue + + for item in schedule[job].keys(): + if not item in SCHEDULE_CONF: + del schedule[job][item] + continue + if schedule[job][item] == 'true': + schedule[job][item] = True + if schedule[job][item] == 'false': + schedule[job][item] = False + + if '_seconds' in schedule[job].keys(): + schedule[job]['seconds'] = schedule[job]['_seconds'] + del schedule[job]['_seconds'] + + # move this file into an configurable opt + sfn = '{0}/{1}/schedule.conf'.format(__opts__['config_dir'], os.path.dirname(__opts__['default_include'])) + if schedule: + tmp = {'schedule': schedule} + yaml_out = yaml.safe_dump(tmp, default_flow_style=False) + else: + yaml_out = '' + + try: + with salt.utils.fopen(sfn, 'w+') as fp_: + fp_.write(yaml_out) + ret['comment'] = 'Schedule saved to {0}.'.format(sfn) + except (IOError, OSError): + ret['comment'] = 'Unable to write to schedule file at {0}. Check permissions.'.format(sfn) + ret['result'] = False + return ret diff --git a/salt/modules/shadow.py b/salt/modules/shadow.py index 3e497d2aa2..18d6896a53 100644 --- a/salt/modules/shadow.py +++ b/salt/modules/shadow.py @@ -169,6 +169,22 @@ def gen_password(password, crypt_salt=None, algorithm='sha512'): return salt.utils.pycrypto.gen_hash(crypt_salt, password, algorithm) +def del_password(name): + ''' + Delete the password from name user + + CLI Example: + + .. code-block:: bash + + salt '*' shadow.del_password username + ''' + cmd = 'passwd -d {0}'.format(name) + __salt__['cmd.run'](cmd, output_loglevel='quiet') + uinfo = info(name) + return not uinfo['passwd'] + + def set_password(name, password, use_usermod=False): ''' Set the password for a named user. The password must be a properly defined diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index 172021f3cf..bf936ac5f1 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -130,7 +130,7 @@ def _get_repo_options(**kwargs): repo_arg = '' if fromrepo: log.info('Restricting to repo {0!r}'.format(fromrepo)) - repo_arg = ('--disablerepo={0!r} --enablerepo={1!r}' + repo_arg = ('--disablerepo={0!r} --enablerepo={1!r} ' .format('*', fromrepo)) else: repo_arg = '' @@ -726,6 +726,10 @@ def install(name=None, Disable exclude from main, for a repo or for everything. (e.g., ``yum --disableexcludes='main'``) + branch + Specifies the branch on YUM server. + (e.g., ``yum --branch='test'``) + .. versionadded:: Helium @@ -785,6 +789,10 @@ def install(name=None, 'package targets') repo_arg = _get_repo_options(fromrepo=fromrepo, **kwargs) + # Support branch parameter for yum + branch = kwargs.get('branch', '') + if branch: + repo_arg += '--branch={0!r}'.format(branch) exclude_arg = _get_excludes_option(**kwargs) old = list_pkgs() diff --git a/salt/states/user.py b/salt/states/user.py index 67633bad9f..5f16797ff9 100644 --- a/salt/states/user.py +++ b/salt/states/user.py @@ -55,6 +55,7 @@ def _changes(name, createhome=True, password=None, enforce_password=True, + empty_password=False, shell=None, fullname='', roomnumber='', @@ -160,6 +161,7 @@ def present(name, createhome=True, password=None, enforce_password=True, + empty_password=False, shell=None, unique=True, system=False, @@ -229,6 +231,9 @@ def present(name, "password" field. This option will be ignored if "password" is not specified. + empty_password + Set to True to enable no password-less login for user + shell The login shell, defaults to the system default shell @@ -325,6 +330,9 @@ def present(name, if gid_from_name: gid = __salt__['file.group_to_gid'](name) + if empty_password: + __salt__['shadow.del_password'](name) + changes = _changes(name, uid, gid, @@ -335,6 +343,7 @@ def present(name, createhome, password, enforce_password, + empty_password, shell, fullname, roomnumber, @@ -360,7 +369,7 @@ def present(name, lshad = __salt__['shadow.info'](name) pre = __salt__['user.info'](name) for key, val in changes.items(): - if key == 'passwd': + if key == 'passwd' and not empty_password: __salt__['shadow.set_password'](name, password) continue if key == 'date': @@ -419,6 +428,7 @@ def present(name, createhome, password, enforce_password, + empty_password, shell, fullname, roomnumber, @@ -464,7 +474,7 @@ def present(name, ret['comment'] = 'New user {0} created'.format(name) ret['changes'] = __salt__['user.info'](name) if 'shadow.info' in __salt__ and not salt.utils.is_windows(): - if password: + if password and not empty_password: __salt__['shadow.set_password'](name, password) spost = __salt__['shadow.info'](name) if spost['passwd'] != password: diff --git a/salt/utils/schedule.py b/salt/utils/schedule.py index e1049bc360..5eeda940fe 100644 --- a/salt/utils/schedule.py +++ b/salt/utils/schedule.py @@ -189,6 +189,23 @@ class Schedule(object): return self.functions['config.merge'](opt, {}, omit_master=True) return self.opts.get(opt, {}) + def delete_job(self, name): + # ensure job exists, then delete it + if name in self.opts['schedule']: + del self.opts['schedule'][name] + + # remove from self.intervals + if name in self.intervals: + del self.intervals[name] + + def add_job(self, name, schedule): + self.opts['schedule'][name] = schedule + + def modify_job(self, name, schedule): + if name in self.opts['schedule']: + self.delete_job(name) + self.opts['schedule'][name] = schedule + def handle_func(self, func, data): ''' Execute this method in a multiprocess or thread @@ -311,6 +328,7 @@ class Schedule(object): Evaluate and execute the schedule ''' schedule = self.option('schedule') + #log.debug('calling eval {0}'.format(schedule)) if not isinstance(schedule, dict): return for job, data in schedule.items(): @@ -335,8 +353,12 @@ class Schedule(object): when = 0 seconds = 0 - # clean this up - if ('seconds' in data or 'hours' in data or 'minutes' in data or 'days' in data) and 'when' in data: + time_conflict = False + for item in ['seconds', 'minutes', 'hours', 'days']: + if item in data and 'when' in data: + time_conflict = True + + if time_conflict: log.info('Unable to use "seconds", "minutes", "hours", or "days" with "when" option. Ignoring.') continue @@ -440,6 +462,7 @@ class Schedule(object): else: if now - self.intervals[job] >= seconds: run = True + else: if 'splay' in data: if 'when' in data: diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index d0d38e7010..813215c897 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -282,7 +282,7 @@ class TestDaemon(object): self.pre_setup_minions() self.setup_minions() - if self.parser.options.ssh: + if getattr(self.parser.options, 'ssh', False): self.prep_ssh() if self.parser.options.sysinfo: diff --git a/tests/integration/fileserver/fileclient_test.py b/tests/integration/fileserver/fileclient_test.py index 1c27c8d2ff..f29e1ab960 100644 --- a/tests/integration/fileserver/fileclient_test.py +++ b/tests/integration/fileserver/fileclient_test.py @@ -7,7 +7,7 @@ from salttesting.unit import skipIf from salttesting.helpers import ensure_in_syspath from salttesting.mock import MagicMock, patch, NO_MOCK, NO_MOCK_REASON -ensure_in_syspath('../') +ensure_in_syspath('../..') # Import salt libs import integration diff --git a/tests/integration/fileserver/gitfs_test.py b/tests/integration/fileserver/gitfs_test.py index 8493a3a846..a32ba54b20 100644 --- a/tests/integration/fileserver/gitfs_test.py +++ b/tests/integration/fileserver/gitfs_test.py @@ -8,7 +8,7 @@ from salttesting import skipIf from salttesting.helpers import ensure_in_syspath from salttesting.mock import patch, NO_MOCK, NO_MOCK_REASON -ensure_in_syspath('../') +ensure_in_syspath('../..') # Import Python libs import os @@ -23,6 +23,10 @@ gitfs.__opts__ = {'gitfs_remotes': [''], 'fileserver_backend': 'gitfs', 'gitfs_base': 'master', 'fileserver_events': True, + 'transport': 'zeromq', + 'gitfs_mountpoint': '', + 'gitfs_env_whitelist': [], + 'gitfs_env_blacklist': [] } load = {'saltenv': 'base'} @@ -88,7 +92,11 @@ class GitFSTest(integration.ModuleCase): 'gitfs_remotes': ['file://' + self.tmp_repo_git], 'sock_dir': self.master_opts['sock_dir']}): ret = gitfs.find_file('testfile') - expected_ret = {'path': '/tmp/salttest/cache/gitfs/refs/master/testfile', + expected_ret = {'path': os.path.join(self.master_opts['cachedir'], + 'gitfs', + 'refs', + 'base', + 'testfile'), 'rel': 'testfile'} self.assertDictEqual(ret, expected_ret) @@ -140,8 +148,18 @@ class GitFSTest(integration.ModuleCase): ret = gitfs.serve_file(load, fnd) self.assertDictEqual({ - 'data': 'Scene 24\n\n \n OLD MAN: Ah, hee he he ha!\n ARTHUR: And this enchanter of whom you speak, he has seen the grail?\n OLD MAN: Ha ha he he he he!\n ARTHUR: Where does he live? Old man, where does he live?\n OLD MAN: He knows of a cave, a cave which no man has entered.\n ARTHUR: And the Grail... The Grail is there?\n OLD MAN: Very much danger, for beyond the cave lies the Gorge\n of Eternal Peril, which no man has ever crossed.\n ARTHUR: But the Grail! Where is the Grail!?\n OLD MAN: Seek you the Bridge of Death.\n ARTHUR: The Bridge of Death, which leads to the Grail?\n OLD MAN: Hee hee ha ha!\n\n', - 'dest': 'testfile'}, ret) + 'data': 'Scene 24\n\n \n OLD MAN: Ah, hee he he ha!\n ARTHUR: ' + 'And this enchanter of whom you speak, he has seen the grail?\n ' + 'OLD MAN: Ha ha he he he he!\n ARTHUR: Where does he live? ' + 'Old man, where does he live?\n OLD MAN: He knows of a cave, ' + 'a cave which no man has entered.\n ARTHUR: And the Grail... ' + 'The Grail is there?\n OLD MAN: Very much danger, for beyond ' + 'the cave lies the Gorge\n of Eternal Peril, which no man ' + 'has ever crossed.\n ARTHUR: But the Grail! Where is the Grail!?\n ' + 'OLD MAN: Seek you the Bridge of Death.\n ARTHUR: The Bridge of ' + 'Death, which leads to the Grail?\n OLD MAN: Hee hee ha ha!\n\n', + 'dest': 'testfile'}, + ret) if __name__ == '__main__': diff --git a/tests/integration/fileserver/roots_test.py b/tests/integration/fileserver/roots_test.py index 6a6180705c..c7823d6006 100644 --- a/tests/integration/fileserver/roots_test.py +++ b/tests/integration/fileserver/roots_test.py @@ -7,7 +7,7 @@ from salttesting import skipIf from salttesting.helpers import ensure_in_syspath from salttesting.mock import patch, NO_MOCK, NO_MOCK_REASON -ensure_in_syspath('../') +ensure_in_syspath('../..') # Import salt libs import integration @@ -22,11 +22,15 @@ import os @skipIf(NO_MOCK, NO_MOCK_REASON) class RootsTest(integration.ModuleCase): + def setUp(self): - self.master_opts['file_roots']['base'] = [os.path.join(integration.FILES, 'file', 'base')] + if integration.TMP_STATE_TREE not in self.master_opts['file_roots']['base']: + # We need to setup the file roots + self.master_opts['file_roots']['base'] = [os.path.join(integration.FILES, 'file', 'base')] def test_file_list(self): - with patch.dict(roots.__opts__, {'file_roots': self.master_opts['file_roots'], + with patch.dict(roots.__opts__, {'cachedir': self.master_opts['cachedir'], + 'file_roots': self.master_opts['file_roots'], 'fileserver_ignoresymlinks': False, 'fileserver_followsymlinks': False, 'file_ignore_regex': False, @@ -102,7 +106,10 @@ class RootsTest(integration.ModuleCase): self.assertDictEqual(ret, {'hsum': '98aa509006628302ce38ce521a7f805f', 'hash_type': 'md5'}) def test_file_list_emptydirs(self): - with patch.dict(roots.__opts__, {'file_roots': self.master_opts['file_roots'], + if integration.TMP_STATE_TREE not in self.master_opts['file_roots']['base']: + self.skipTest('This test fails when using tests/runtests.py. salt-runtests will be available soon.') + with patch.dict(roots.__opts__, {'cachedir': self.master_opts['cachedir'], + 'file_roots': self.master_opts['file_roots'], 'fileserver_ignoresymlinks': False, 'fileserver_followsymlinks': False, 'file_ignore_regex': False, @@ -111,11 +118,14 @@ class RootsTest(integration.ModuleCase): self.assertIn('empty_dir', ret) def test_dir_list(self): - with patch.dict(roots.__opts__, {'file_roots': self.master_opts['file_roots'], - 'fileserver_ignoresymlinks': False, - 'fileserver_followsymlinks': False, - 'file_ignore_regex': False, - 'file_ignore_glob': False}): + if integration.TMP_STATE_TREE not in self.master_opts['file_roots']['base']: + self.skipTest('This test fails when using tests/runtests.py. salt-runtests will be available soon.') + with patch.dict(roots.__opts__, {'cachedir': self.master_opts['cachedir'], + 'file_roots': self.master_opts['file_roots'], + 'fileserver_ignoresymlinks': False, + 'fileserver_followsymlinks': False, + 'file_ignore_regex': False, + 'file_ignore_glob': False}): ret = roots.dir_list({'saltenv': 'base'}) self.assertIn('empty_dir', ret) diff --git a/tests/integration/shell/call.py b/tests/integration/shell/call.py index 6d1f5022f7..b5809a1edb 100644 --- a/tests/integration/shell/call.py +++ b/tests/integration/shell/call.py @@ -85,34 +85,8 @@ class CallTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn): @skipIf(sys.platform.startswith('win'), 'This test does not apply on Win') def test_return(self): - config_dir = '/tmp/salttest' - minion_config_file = os.path.join(config_dir, 'minion') - minion_config = { - 'id': 'minion_test_issue_2731', - 'master': 'localhost', - 'master_port': 64506, - 'root_dir': '/tmp/salttest', - 'pki_dir': 'pki', - 'cachedir': 'cachedir', - 'sock_dir': 'minion_sock', - 'open_mode': True, - 'log_file': '/tmp/salttest/minion_test_issue_2731', - 'log_level': 'quiet', - 'log_level_logfile': 'info' - } - - # Remove existing logfile - if os.path.isfile('/tmp/salttest/minion_test_issue_2731'): - os.unlink('/tmp/salttest/minion_test_issue_2731') - - # Let's first test with a master running - open(minion_config_file, 'w').write( - yaml.dump(minion_config, default_flow_style=False) - ) - out = self.run_call('-c {0} cmd.run "echo returnTOmaster"'.format( - os.path.join(integration.INTEGRATION_TEST_DIR, 'files', 'conf'))) - jobs = [a for a in self.run_run('-c {0} jobs.list_jobs'.format( - os.path.join(integration.INTEGRATION_TEST_DIR, 'files', 'conf')))] + self.run_call('-c {0} cmd.run "echo returnTOmaster"'.format(self.get_config_dir())) + jobs = [a for a in self.run_run('-c {0} jobs.list_jobs'.format(self.get_config_dir()))] self.assertTrue(True in ['returnTOmaster' in j for j in jobs]) # lookback jid @@ -129,38 +103,43 @@ class CallTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn): assert idx > 0 assert jid master_out = [ - a for a in self.run_run('-c {0} jobs.lookup_jid {1}'.format( - os.path.join(integration.INTEGRATION_TEST_DIR, - 'files', - 'conf'), - jid))] + a for a in self.run_run('-c {0} jobs.lookup_jid {1}'.format(self.get_config_dir(), jid)) + ] self.assertTrue(True in ['returnTOmaster' in a for a in master_out]) @skipIf(sys.platform.startswith('win'), 'This test does not apply on Win') def test_issue_2731_masterless(self): - config_dir = '/tmp/salttest' + root_dir = os.path.join(integration.TMP, 'issue-2731') + config_dir = os.path.join(root_dir, 'conf') minion_config_file = os.path.join(config_dir, 'minion') + logfile = os.path.join(root_dir, 'minion_test_issue_2731') + + if not os.path.isdir(config_dir): + os.makedirs(config_dir) + + master_config = yaml.load(open(self.get_config_file_path('master')).read()) + master_root_dir = master_config['root_dir'] this_minion_key = os.path.join( - config_dir, 'pki', 'minions', 'minion_test_issue_2731' + master_root_dir, 'pki', 'minions', 'minion_test_issue_2731' ) minion_config = { 'id': 'minion_test_issue_2731', 'master': 'localhost', 'master_port': 64506, - 'root_dir': '/tmp/salttest', + 'root_dir': master_root_dir, 'pki_dir': 'pki', 'cachedir': 'cachedir', 'sock_dir': 'minion_sock', 'open_mode': True, - 'log_file': '/tmp/salttest/minion_test_issue_2731', + 'log_file': logfile, 'log_level': 'quiet', 'log_level_logfile': 'info' } # Remove existing logfile - if os.path.isfile('/tmp/salttest/minion_test_issue_2731'): - os.unlink('/tmp/salttest/minion_test_issue_2731') + if os.path.isfile(logfile): + os.unlink(logfile) start = datetime.now() # Let's first test with a master running diff --git a/tests/runtests.py b/tests/runtests.py index 3f1dcfe1ba..f1c75ae427 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -109,6 +109,13 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): action='store_true', help='Run unit tests' ) + self.test_selection_group.add_option( + '--fileserver-tests', + dest='fileserver', + default=False, + action='store_true', + help='Run Fileserver tests' + ) self.test_selection_group.add_option( '-o', '--outputter', @@ -137,7 +144,8 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): self.options.module, self.options.client, self.options.shell, self.options.unit, self.options.state, self.options.runner, self.options.loader, self.options.name, self.options.outputter, - os.geteuid() != 0, not self.options.run_destructive)): + self.options.fileserver, os.geteuid() != 0, + not self.options.run_destructive)): self.error( 'No sense in generating the tests coverage report when ' 'not running the full test suite, including the ' @@ -149,7 +157,8 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): if not any((self.options.module, self.options.client, self.options.shell, self.options.unit, self.options.state, self.options.runner, self.options.loader, - self.options.name, self.options.outputter)): + self.options.name, self.options.outputter, + self.options.fileserver)): self.options.module = True self.options.client = True self.options.shell = True @@ -158,6 +167,7 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): self.options.state = True self.options.loader = True self.options.outputter = True + self.options.fileserver = True self.start_coverage( branch=True, @@ -192,6 +202,7 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): self.options.client or self.options.loader or self.options.outputter or + self.options.fileserver or named_tests): # We're either not running any of runner, state, module and client # tests, or, we're only running unittests by passing --unit or by @@ -240,7 +251,8 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): if not any([self.options.client, self.options.module, self.options.runner, self.options.shell, self.options.state, self.options.loader, - self.options.outputter, self.options.name]): + self.options.outputter, self.options.name, + self.options.fileserver]): return status with TestDaemon(self): @@ -264,6 +276,8 @@ class SaltTestsuiteParser(SaltCoverageTestingParser): status.append(self.run_integration_suite('shell', 'Shell')) if self.options.outputter: status.append(self.run_integration_suite('output', 'Outputter')) + if self.options.fileserver: + status.append(self.run_integration_suite('fileserver', 'Fileserver')) return status def run_unit_tests(self): diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index 7a016f57db..2b50ad6f4d 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -14,7 +14,7 @@ ensure_in_syspath('../') # Import Salt libs import integration -from salt import client +from salt import client, config from salt.exceptions import EauthAuthenticationError, SaltInvocationError @@ -22,15 +22,14 @@ from salt.exceptions import EauthAuthenticationError, SaltInvocationError class LocalClientTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): def setUp(self): - if not os.path.exists('/tmp/salttest'): - # This path is hardcoded in the configuration file - os.makedirs('/tmp/salttest/cache') + master_config_path = self.get_config_file_path('master') + master_config = config.master_config(master_config_path) + if not os.path.exists(master_config['cachedir']): + os.makedirs(master_config['cachedir']) if not os.path.exists(integration.TMP_CONF_DIR): os.makedirs(integration.TMP_CONF_DIR) - self.local_client = client.LocalClient( - self.get_config_file_path('master') - ) + self.local_client = client.LocalClient(mopts=master_config) def test_create_local_client(self): local_client = client.LocalClient(self.get_config_file_path('master')) diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index b26c43e1b1..6db989104b 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -93,7 +93,7 @@ def _fopen_side_effect_etc_hosts(filename): _unhandled_mock_read(filename) -class ConfigTestCase(TestCase): +class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): def test_proper_path_joining(self): fpath = tempfile.mktemp() try: @@ -335,31 +335,28 @@ class ConfigTestCase(TestCase): shutil.rmtree(tempdir) def test_syndic_config(self): - syndic_conf_path = os.path.join( - integration.INTEGRATION_TEST_DIR, 'files', 'conf', 'syndic' - ) - minion_config_path = os.path.join( - integration.INTEGRATION_TEST_DIR, 'files', 'conf', 'minion' - ) + syndic_conf_path = self.get_config_file_path('syndic') + minion_conf_path = self.get_config_file_path('minion') syndic_opts = sconfig.syndic_config( - syndic_conf_path, minion_config_path + syndic_conf_path, minion_conf_path ) syndic_opts.update(salt.minion.resolve_dns(syndic_opts)) + root_dir = syndic_opts['root_dir'] # id & pki dir are shared & so configured on the minion side self.assertEqual(syndic_opts['id'], 'minion') - self.assertEqual(syndic_opts['pki_dir'], '/tmp/salttest/pki') + self.assertEqual(syndic_opts['pki_dir'], os.path.join(root_dir, 'pki')) # the rest is configured master side self.assertEqual(syndic_opts['master_uri'], 'tcp://127.0.0.1:54506') self.assertEqual(syndic_opts['master_port'], 54506) self.assertEqual(syndic_opts['master_ip'], '127.0.0.1') self.assertEqual(syndic_opts['master'], 'localhost') - self.assertEqual(syndic_opts['sock_dir'], '/tmp/salttest/minion_sock') - self.assertEqual(syndic_opts['cachedir'], '/tmp/salttest/cachedir') - self.assertEqual(syndic_opts['log_file'], '/tmp/salttest/osyndic.log') - self.assertEqual(syndic_opts['pidfile'], '/tmp/salttest/osyndic.pid') + self.assertEqual(syndic_opts['sock_dir'], os.path.join(root_dir, 'minion_sock')) + self.assertEqual(syndic_opts['cachedir'], os.path.join(root_dir, 'cachedir')) + self.assertEqual(syndic_opts['log_file'], os.path.join(root_dir, 'osyndic.log')) + self.assertEqual(syndic_opts['pidfile'], os.path.join(root_dir, 'osyndic.pid')) # Show that the options of localclient that repub to local master # are not merged with syndic ones - self.assertEqual(syndic_opts['_master_conf_file'], minion_config_path) + self.assertEqual(syndic_opts['_master_conf_file'], minion_conf_path) self.assertEqual(syndic_opts['_minion_conf_file'], syndic_conf_path) def test_check_dns_deprecation_warning(self):