mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
use same master eval logic for Minion and SMinion
This fixes https://github.com/saltstack/salt/issues/24354 There was inconsistent behavior between salt-minion (`Minion` under the hood) and salt-call (`SMinion` under the hood) with regard to how the master was determined from the minion config. `SMinion` had its own hard-coded master evaluation in its `__init__` method that was not congruent with documentation. The "standard" master evaluation logic existed in `master_eval`, a method of `Minion`. This was resolved by moving `master_eval` into `MinionBase` (which `Minion` inherits from) and making `SMinion` also subclass `MinionBase`, which otherwise only consists of a couple of methods that `SMinion` won't access.
This commit is contained in:
parent
7cd2081eb8
commit
80b1d60f4c
386
salt/minion.py
386
salt/minion.py
@ -326,72 +326,6 @@ def load_args_and_kwargs(func, args, data=None):
|
||||
return _args, _kwargs
|
||||
|
||||
|
||||
class SMinion(object):
|
||||
'''
|
||||
Create an object that has loaded all of the minion module functions,
|
||||
grains, modules, returners etc. The SMinion allows developers to
|
||||
generate all of the salt minion functions and present them with these
|
||||
functions for general use.
|
||||
'''
|
||||
def __init__(self, opts):
|
||||
# Late setup of the opts grains, so we can log from the grains module
|
||||
opts['grains'] = salt.loader.grains(opts)
|
||||
self.opts = opts
|
||||
|
||||
# Clean out the proc directory (default /var/cache/salt/minion/proc)
|
||||
if (self.opts.get('file_client', 'remote') == 'remote'
|
||||
or self.opts.get('use_master_when_local', False)):
|
||||
if isinstance(self.opts['master'], list):
|
||||
masters = self.opts['master']
|
||||
if self.opts['random_master'] is True:
|
||||
shuffle(masters)
|
||||
connected_master = False
|
||||
for master in masters:
|
||||
self.opts['master'] = master
|
||||
self.opts.update(resolve_dns(opts))
|
||||
try:
|
||||
self.gen_modules()
|
||||
connected_master = True
|
||||
break
|
||||
except SaltClientError:
|
||||
log.warning(('Attempted to authenticate with master '
|
||||
'{0} and failed'.format(master)))
|
||||
continue
|
||||
# if we are out of masters, lets raise an exception
|
||||
if not connected_master:
|
||||
raise SaltClientError('Unable to connect to any master')
|
||||
else:
|
||||
if self.opts['random_master'] is True:
|
||||
log.warning('random_master is True but there is only one master specified. Ignoring.')
|
||||
self.opts.update(resolve_dns(opts))
|
||||
self.gen_modules(initial_load=True)
|
||||
else:
|
||||
self.gen_modules(initial_load=True)
|
||||
|
||||
def gen_modules(self, initial_load=False):
|
||||
'''
|
||||
Load all of the modules for the minion
|
||||
'''
|
||||
self.opts['pillar'] = salt.pillar.get_pillar(
|
||||
self.opts,
|
||||
self.opts['grains'],
|
||||
self.opts['id'],
|
||||
self.opts['environment'],
|
||||
pillarenv=self.opts.get('pillarenv'),
|
||||
).compile_pillar()
|
||||
self.utils = salt.loader.utils(self.opts)
|
||||
self.functions = salt.loader.minion_mods(self.opts, utils=self.utils,
|
||||
include_errors=True)
|
||||
self.proxy = salt.loader.proxy(self.opts, None)
|
||||
# TODO: remove
|
||||
self.function_errors = {} # Keep the funcs clean
|
||||
self.returners = salt.loader.returners(self.opts, self.functions)
|
||||
self.states = salt.loader.states(self.opts, self.functions)
|
||||
self.rend = salt.loader.render(self.opts, self.functions)
|
||||
self.matcher = Matcher(self.opts, self.functions)
|
||||
self.functions['sys.reload_modules'] = self.gen_modules
|
||||
|
||||
|
||||
class MinionBase(object):
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
@ -424,6 +358,187 @@ class MinionBase(object):
|
||||
return self.beacons.process(b_conf)
|
||||
return []
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def eval_master(self,
|
||||
opts,
|
||||
timeout=60,
|
||||
safe=True,
|
||||
failed=False):
|
||||
'''
|
||||
Evaluates and returns a tuple of the current master address and the pub_channel.
|
||||
|
||||
In standard mode, just creates a pub_channel with the given master address.
|
||||
|
||||
With master_type=func evaluates the current master address from the given
|
||||
module and then creates a pub_channel.
|
||||
|
||||
With master_type=failover takes the list of masters and loops through them.
|
||||
The first one that allows the minion to create a pub_channel is then
|
||||
returned. If this function is called outside the minions initialization
|
||||
phase (for example from the minions main event-loop when a master connection
|
||||
loss was detected), 'failed' should be set to True. The current
|
||||
(possibly failed) master will then be removed from the list of masters.
|
||||
'''
|
||||
# check if master_type was altered from its default
|
||||
if opts['master_type'] != 'str' and opts['__role'] != 'syndic':
|
||||
# check for a valid keyword
|
||||
if opts['master_type'] == 'func':
|
||||
# split module and function and try loading the module
|
||||
mod, fun = opts['master'].split('.')
|
||||
try:
|
||||
master_mod = salt.loader.raw_mod(opts, mod, fun)
|
||||
if not master_mod:
|
||||
raise TypeError
|
||||
# we take whatever the module returns as master address
|
||||
opts['master'] = master_mod[mod + '.' + fun]()
|
||||
except TypeError:
|
||||
msg = ('Failed to evaluate master address from '
|
||||
'module \'{0}\''.format(opts['master']))
|
||||
log.error(msg)
|
||||
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
|
||||
log.info('Evaluated master from module: {0}'.format(master_mod))
|
||||
|
||||
# if failover is set, master has to be of type list
|
||||
elif opts['master_type'] == 'failover':
|
||||
if isinstance(opts['master'], list):
|
||||
log.info('Got list of available master addresses:'
|
||||
' {0}'.format(opts['master']))
|
||||
if opts['master_shuffle']:
|
||||
shuffle(opts['master'])
|
||||
# if opts['master'] is a str and we have never created opts['master_list']
|
||||
elif isinstance(opts['master'], str) and ('master_list' not in opts):
|
||||
# We have a string, but a list was what was intended. Convert.
|
||||
# See issue 23611 for details
|
||||
opts['master'] = [opts['master']]
|
||||
elif opts['__role'] == 'syndic':
|
||||
log.info('Syndic setting master_syndic to \'{0}\''.format(opts['master']))
|
||||
|
||||
# if failed=True, the minion was previously connected
|
||||
# we're probably called from the minions main-event-loop
|
||||
# because a master connection loss was detected. remove
|
||||
# the possibly failed master from the list of masters.
|
||||
elif failed:
|
||||
log.info('Removing possibly failed master {0} from list of'
|
||||
' masters'.format(opts['master']))
|
||||
# create new list of master with the possibly failed one removed
|
||||
opts['master'] = [x for x in opts['master_list'] if opts['master'] != x]
|
||||
|
||||
else:
|
||||
msg = ('master_type set to \'failover\' but \'master\' '
|
||||
'is not of type list but of type '
|
||||
'{0}'.format(type(opts['master'])))
|
||||
log.error(msg)
|
||||
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
|
||||
# If failover is set, minion have to failover on DNS errors instead of retry DNS resolve.
|
||||
# See issue 21082 for details
|
||||
if opts['retry_dns']:
|
||||
msg = ('\'master_type\' set to \'failover\' but \'retry_dns\' is not 0. '
|
||||
'Setting \'retry_dns\' to 0 to failover to the next master on DNS errors.')
|
||||
log.critical(msg)
|
||||
opts['retry_dns'] = 0
|
||||
else:
|
||||
msg = ('Invalid keyword \'{0}\' for variable '
|
||||
'\'master_type\''.format(opts['master_type']))
|
||||
log.error(msg)
|
||||
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
|
||||
|
||||
# Specify kwargs for the channel factory so that SMinion doesn't need to define an io_loop
|
||||
# (The channel factories will set a default if the kwarg isn't passed)
|
||||
factory_kwargs = {'timeout': timeout, 'safe': safe}
|
||||
if getattr(self, 'io_loop', None):
|
||||
factory_kwargs['io_loop'] = self.io_loop
|
||||
|
||||
# if we have a list of masters, loop through them and be
|
||||
# happy with the first one that allows us to connect
|
||||
if isinstance(opts['master'], list):
|
||||
conn = False
|
||||
# shuffle the masters and then loop through them
|
||||
local_masters = copy.copy(opts['master'])
|
||||
|
||||
for master in local_masters:
|
||||
opts['master'] = master
|
||||
opts.update(prep_ip_port(opts))
|
||||
opts.update(resolve_dns(opts))
|
||||
self.opts = opts
|
||||
|
||||
# on first run, update self.opts with the whole master list
|
||||
# to enable a minion to re-use old masters if they get fixed
|
||||
if 'master_list' not in opts:
|
||||
opts['master_list'] = local_masters
|
||||
|
||||
try:
|
||||
pub_channel = salt.transport.client.AsyncPubChannel.factory(opts, **factory_kwargs)
|
||||
yield pub_channel.connect()
|
||||
conn = True
|
||||
break
|
||||
except SaltClientError:
|
||||
msg = ('Master {0} could not be reached, trying '
|
||||
'next master (if any)'.format(opts['master']))
|
||||
log.info(msg)
|
||||
continue
|
||||
|
||||
if not conn:
|
||||
self.connected = False
|
||||
msg = ('No master could be reached or all masters denied '
|
||||
'the minions connection attempt.')
|
||||
log.error(msg)
|
||||
else:
|
||||
self.tok = pub_channel.auth.gen_token('salt')
|
||||
self.connected = True
|
||||
raise tornado.gen.Return((opts['master'], pub_channel))
|
||||
|
||||
# single master sign in
|
||||
else:
|
||||
opts.update(prep_ip_port(opts))
|
||||
opts.update(resolve_dns(opts))
|
||||
pub_channel = salt.transport.client.AsyncPubChannel.factory(self.opts, **factory_kwargs)
|
||||
yield pub_channel.connect()
|
||||
self.tok = pub_channel.auth.gen_token('salt')
|
||||
self.connected = True
|
||||
raise tornado.gen.Return((opts['master'], pub_channel))
|
||||
|
||||
|
||||
class SMinion(MinionBase):
|
||||
'''
|
||||
Create an object that has loaded all of the minion module functions,
|
||||
grains, modules, returners etc. The SMinion allows developers to
|
||||
generate all of the salt minion functions and present them with these
|
||||
functions for general use.
|
||||
'''
|
||||
def __init__(self, opts):
|
||||
# Late setup of the opts grains, so we can log from the grains module
|
||||
opts['grains'] = salt.loader.grains(opts)
|
||||
self.opts = opts
|
||||
|
||||
# Clean out the proc directory (default /var/cache/salt/minion/proc)
|
||||
if (self.opts.get('file_client', 'remote') == 'remote'
|
||||
or self.opts.get('use_master_when_local', False)):
|
||||
self.eval_master(self.opts, failed=True)
|
||||
self.gen_modules(initial_load=True)
|
||||
|
||||
def gen_modules(self, initial_load=False):
|
||||
'''
|
||||
Load all of the modules for the minion
|
||||
'''
|
||||
self.opts['pillar'] = salt.pillar.get_pillar(
|
||||
self.opts,
|
||||
self.opts['grains'],
|
||||
self.opts['id'],
|
||||
self.opts['environment'],
|
||||
pillarenv=self.opts.get('pillarenv'),
|
||||
).compile_pillar()
|
||||
self.utils = salt.loader.utils(self.opts)
|
||||
self.functions = salt.loader.minion_mods(self.opts, utils=self.utils,
|
||||
include_errors=True)
|
||||
self.proxy = salt.loader.proxy(self.opts, None)
|
||||
# TODO: remove
|
||||
self.function_errors = {} # Keep the funcs clean
|
||||
self.returners = salt.loader.returners(self.opts, self.functions)
|
||||
self.states = salt.loader.states(self.opts, self.functions)
|
||||
self.rend = salt.loader.render(self.opts, self.functions)
|
||||
self.matcher = Matcher(self.opts, self.functions)
|
||||
self.functions['sys.reload_modules'] = self.gen_modules
|
||||
|
||||
|
||||
class MasterMinion(object):
|
||||
'''
|
||||
@ -674,146 +789,7 @@ class Minion(MinionBase):
|
||||
|
||||
self.grains_cache = self.opts['grains']
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def eval_master(self,
|
||||
opts,
|
||||
timeout=60,
|
||||
safe=True,
|
||||
failed=False):
|
||||
'''
|
||||
Evaluates and returns a tuple of the current master address and the pub_channel.
|
||||
|
||||
In standard mode, just creates a pub_channel with the given master address.
|
||||
|
||||
With master_type=func evaluates the current master address from the given
|
||||
module and then creates a pub_channel.
|
||||
|
||||
With master_type=failover takes the list of masters and loops through them.
|
||||
The first one that allows the minion to create a pub_channel is then
|
||||
returned. If this function is called outside the minions initialization
|
||||
phase (for example from the minions main event-loop when a master connection
|
||||
loss was detected), 'failed' should be set to True. The current
|
||||
(possibly failed) master will then be removed from the list of masters.
|
||||
'''
|
||||
# check if master_type was altered from its default
|
||||
if opts['master_type'] != 'str' and opts['__role'] != 'syndic':
|
||||
# check for a valid keyword
|
||||
if opts['master_type'] == 'func':
|
||||
# split module and function and try loading the module
|
||||
mod, fun = opts['master'].split('.')
|
||||
try:
|
||||
master_mod = salt.loader.raw_mod(opts, mod, fun)
|
||||
if not master_mod:
|
||||
raise TypeError
|
||||
# we take whatever the module returns as master address
|
||||
opts['master'] = master_mod[mod + '.' + fun]()
|
||||
except TypeError:
|
||||
msg = ('Failed to evaluate master address from '
|
||||
'module \'{0}\''.format(opts['master']))
|
||||
log.error(msg)
|
||||
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
|
||||
log.info('Evaluated master from module: {0}'.format(master_mod))
|
||||
|
||||
# if failover is set, master has to be of type list
|
||||
elif opts['master_type'] == 'failover':
|
||||
if isinstance(opts['master'], list):
|
||||
log.info('Got list of available master addresses:'
|
||||
' {0}'.format(opts['master']))
|
||||
if opts['master_shuffle']:
|
||||
shuffle(opts['master'])
|
||||
# if opts['master'] is a str and we have never created opts['master_list']
|
||||
elif isinstance(opts['master'], str) and ('master_list' not in opts):
|
||||
# We have a string, but a list was what was intended. Convert.
|
||||
# See issue 23611 for details
|
||||
opts['master'] = [opts['master']]
|
||||
elif opts['__role'] == 'syndic':
|
||||
log.info('Syndic setting master_syndic to \'{0}\''.format(opts['master']))
|
||||
|
||||
# if failed=True, the minion was previously connected
|
||||
# we're probably called from the minions main-event-loop
|
||||
# because a master connection loss was detected. remove
|
||||
# the possibly failed master from the list of masters.
|
||||
elif failed:
|
||||
log.info('Removing possibly failed master {0} from list of'
|
||||
' masters'.format(opts['master']))
|
||||
# create new list of master with the possibly failed one removed
|
||||
opts['master'] = [x for x in opts['master_list'] if opts['master'] != x]
|
||||
|
||||
else:
|
||||
msg = ('master_type set to \'failover\' but \'master\' '
|
||||
'is not of type list but of type '
|
||||
'{0}'.format(type(opts['master'])))
|
||||
log.error(msg)
|
||||
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
|
||||
# If failover is set, minion have to failover on DNS errors instead of retry DNS resolve.
|
||||
# See issue 21082 for details
|
||||
if opts['retry_dns']:
|
||||
msg = ('\'master_type\' set to \'failover\' but \'retry_dns\' is not 0. '
|
||||
'Setting \'retry_dns\' to 0 to failover to the next master on DNS errors.')
|
||||
log.critical(msg)
|
||||
opts['retry_dns'] = 0
|
||||
else:
|
||||
msg = ('Invalid keyword \'{0}\' for variable '
|
||||
'\'master_type\''.format(opts['master_type']))
|
||||
log.error(msg)
|
||||
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
|
||||
|
||||
# if we have a list of masters, loop through them and be
|
||||
# happy with the first one that allows us to connect
|
||||
if isinstance(opts['master'], list):
|
||||
conn = False
|
||||
# shuffle the masters and then loop through them
|
||||
local_masters = copy.copy(opts['master'])
|
||||
|
||||
for master in local_masters:
|
||||
opts['master'] = master
|
||||
opts.update(prep_ip_port(opts))
|
||||
opts.update(resolve_dns(opts))
|
||||
super(Minion, self).__init__(opts) # TODO: only run init once?? This will run once per attempt
|
||||
|
||||
# on first run, update self.opts with the whole master list
|
||||
# to enable a minion to re-use old masters if they get fixed
|
||||
if 'master_list' not in opts:
|
||||
opts['master_list'] = local_masters
|
||||
|
||||
try:
|
||||
pub_channel = salt.transport.client.AsyncPubChannel.factory(opts,
|
||||
timeout=timeout,
|
||||
safe=safe,
|
||||
io_loop=self.io_loop,
|
||||
)
|
||||
yield pub_channel.connect()
|
||||
conn = True
|
||||
break
|
||||
except SaltClientError:
|
||||
msg = ('Master {0} could not be reached, trying '
|
||||
'next master (if any)'.format(opts['master']))
|
||||
log.info(msg)
|
||||
continue
|
||||
|
||||
if not conn:
|
||||
self.connected = False
|
||||
msg = ('No master could be reached or all masters denied '
|
||||
'the minions connection attempt.')
|
||||
log.error(msg)
|
||||
else:
|
||||
self.tok = pub_channel.auth.gen_token('salt')
|
||||
self.connected = True
|
||||
raise tornado.gen.Return((opts['master'], pub_channel))
|
||||
|
||||
# single master sign in
|
||||
else:
|
||||
opts.update(prep_ip_port(opts))
|
||||
opts.update(resolve_dns(opts))
|
||||
pub_channel = salt.transport.client.AsyncPubChannel.factory(self.opts,
|
||||
timeout=timeout,
|
||||
safe=safe,
|
||||
io_loop=self.io_loop,
|
||||
)
|
||||
yield pub_channel.connect()
|
||||
self.tok = pub_channel.auth.gen_token('salt')
|
||||
self.connected = True
|
||||
raise tornado.gen.Return((opts['master'], pub_channel))
|
||||
|
||||
def _prep_mod_opts(self):
|
||||
'''
|
||||
|
Loading…
Reference in New Issue
Block a user