Merge branch 'master' of github.com:thatch45/salt

This commit is contained in:
Erik Nolte 2011-08-01 16:22:02 -06:00
commit 878e7e5846
10 changed files with 254 additions and 27 deletions

View file

@ -90,6 +90,30 @@
# The buffer size in the file server can be adjusted here:
#file_buffer_size: 1048576
##### Peer Publish settings #####
##########################################
# Salt minions can send commands to other minions, but only if the minion is
# allowed to. By default "Peer Publication" is disabled, and when enabled it
# is enabled for specific minions and specific commands. This allows secure
# compartmentalization of commands based on individual minions.
#
# The configuration uses regular expressions to match minions and then a list
# of regular expressions to match functions, the following will allow the
# minion authenticated as foo.example.com to execute functions from the test
# and pkg modules
# peer:
# foo.example.com:
# - test.*
# - pkg.*
#
# This will allow all minions to execute all commands:
# peer:
# .*:
# - .*
# This is not recomanded, since it would allow anyone who gets root on any
# single minion to instantly have root on all of the minions!
#
##### Cluster settings #####
##########################################
# Salt supports automatic clustering, salt creates a single ip address which

View file

@ -69,17 +69,9 @@
###### Thread settings #####
###########################################
# Enable multiprocessing support, by default when a minion receives a
# publication a new thread is spawned and the command is executed therein. This
# is the optimal behavior for the use case where salt is used for data queries
# and distributed system management, but not the optimal use case when salt is
# used for distributed computation. Since python threads are bad at cpu bound
# tasks salt allows for a multiprocessing process to be used for the execution
# instead. This adds more initial overhead to publications, but cpu bound
# executions will be faster. This feature requires python 2.6 or higher on the
# minion, if set to True and python 2.6 or higher is not present then it will
# fall back to python threads
#multiprocessing: False
# Disable multiprocessing support, by default when a minion receives a
# publication a new process is spawned and the command is executed therein.
#multiprocessing: True
###### Logging settings #####
###########################################

View file

@ -4,6 +4,7 @@ Make me some salt!
# Import python libs
import optparse
import os
import sys
# Import salt libs
import salt.config
@ -155,6 +156,98 @@ class Minion(object):
salt.utils.daemonize()
minion.tune_in()
class Syndic(object):
'''
Create a syndic server
'''
def __init__(self):
self.cli = self.__parse_cli()
self.opts = self.__prep_opts()
def __prep_opts(self):
'''
Generate the opts used by the syndic
'''
opts = salt.config.master_config(self.cli['master_config'])
opts['_minion_conf_file'] = opts['conf_file']
opts.update(salt.config.minion_config(self.cli['minion_config']))
if opts.has_key('syndic_master'):
opts['master'] = opts['syndic_master']
opts['_master_conf_file'] = opts['conf_file']
opts.pop('conf_file')
return opts
err = 'The syndic_master needs to be configured in the salt master'\
+ ' config, EXITING!\n'
sys.stderr.write(err)
sys.exit(2)
def __parse_cli(self):
'''
Parse the cli for options passed to a master daemon
'''
import salt.log
parser = optparse.OptionParser()
parser.add_option('-d',
'--daemon',
dest='daemon',
default=False,
action='store_true',
help='Run the master in a daemon')
parser.add_option('--master-config',
dest='master_config',
default='/etc/salt/master',
help='Pass in an alternative master configuration file')
parser.add_option('--minion-config',
dest='minion_config',
default='/etc/salt/minion',
help='Pass in an alternative minion configuration file')
parser.add_option('-l',
'--log-level',
dest='log_level',
default='warning',
choices=salt.log.LOG_LEVELS.keys(),
help='Console log level. One of %s. For the logfile settings '
'see the config file. Default: \'%%default\'.' %
', '.join([repr(l) for l in salt.log.LOG_LEVELS.keys()])
)
options, args = parser.parse_args()
salt.log.setup_console_logger(options.log_level)
cli = {'daemon': options.daemon,
'minion_config': options.minion_config,
'master_config': options.master_config,
}
return cli
def start(self):
'''
Execute this method to start up a syndic.
'''
verify_env([self.opts['pki_dir'], self.opts['cachedir'],
os.path.dirname(self.opts['log_file']),
])
import salt.log
salt.log.setup_logfile_logger(
self.opts['log_file'], self.opts['log_level']
)
for name, level in self.opts['log_granular_levels'].iteritems():
salt.log.set_logger_level(name, level)
import logging
# Late import so logging works correctly
import salt.minion
syndic = salt.minion.Syndic(self.opts)
if self.cli['daemon']:
# Late import so logging works correctly
import salt.utils
salt.utils.daemonize()
syndic.tune_in()
class Monitor(object):
'''
Create a monitor server

View file

@ -112,7 +112,7 @@ class LocalClient(object):
'''
Execute a salt command and return
'''
pub_data = self.pub(tgt, fun, arg, expr_form, ret)
pub_data = self.pub(tgt, fun, arg, expr_form, ret, timeout)
return self.get_full_returns(pub_data['jid'], pub_data['minions'], timeout)
def get_returns(self, jid, minions, timeout=5):
@ -231,7 +231,7 @@ class LocalClient(object):
'exsel': self._check_grain_minions,
}[expr_form](expr)
def pub(self, tgt, fun, arg=(), expr_form='glob', ret=''):
def pub(self, tgt, fun, arg=(), expr_form='glob', ret='', jid='', timeout=5):
'''
Take the required arguments and publish the given command.
Arguments:
@ -264,14 +264,28 @@ class LocalClient(object):
if not minions:
return {'jid': '',
'minions': minions}
package = salt.payload.format_payload('clear',
cmd='publish',
tgt=tgt,
fun=fun,
arg=arg,
key=self.key,
tgt_type=expr_form,
ret=ret)
if self.opts['order_masters']:
package = salt.payload.format_payload(
'clear',
cmd='publish',
tgt=tgt,
fun=fun,
arg=arg,
key=self.key,
tgt_type=expr_form,
ret=ret,
jid=jid,
to=timeout)
else:
package = salt.payload.format_payload(
'clear',
cmd='publish',
tgt=tgt,
fun=fun,
arg=arg,
key=self.key,
tgt_type=expr_form,
ret=ret)
# Prep zmq
context = zmq.Context()
socket = context.socket(zmq.REQ)
@ -280,4 +294,3 @@ class LocalClient(object):
payload = salt.payload.unpackage(socket.recv())
return {'jid': payload['load']['jid'],
'minions': minions}

View file

@ -62,7 +62,7 @@ def minion_config(path):
'states_dirs': [],
'render_dirs': [],
'open_mode': False,
'multiprocessing': False,
'multiprocessing': True,
'log_file': '/var/log/salt/minion',
'log_level': 'warning',
'log_granular_levels': {},
@ -141,6 +141,7 @@ def master_config(path):
'auto_accept': False,
'renderer': 'yaml_jinja',
'state_top': 'top.yml',
'order_masters': False,
'log_file': '/var/log/salt/master',
'log_level': 'warning',
'log_granular_levels': {},

View file

@ -379,6 +379,18 @@ class AESFuncs(object):
Publish a command initiated from a minion, this method executes minion
restrictions so that the minion publication will only work if it is
enabled in the config.
The configuration on the master allows minions to be matched to
salt functions, so the minions can only publish allowed salt functions
The config will look like this:
peer:
.*:
- .*
This configuration will enable all minions to execute all commands.
peer:
foo.example.com:
- test.*
This configuration will only allow the minion foo.example.com to
execute commands from the test module
'''
# Verify that the load is valid
if not self.opts.has_key('peer'):
@ -423,7 +435,7 @@ class AESFuncs(object):
'ret': clear_load['ret'],
}
expr_form = 'glob'
timeout = 5
timeout = 0
if clear_load.has_key('tgt_type'):
load['tgt_type'] = clear_load['tgt_type']
expr_form = load['tgt_type']
@ -435,11 +447,11 @@ class AESFuncs(object):
context = zmq.Context(1)
pub_sock = context.socket(zmq.PUSH)
pub_sock.connect(
'tcp://127.0.0.1:{0}publish_pull_port]'.format(self.opts)
'tcp://127.0.0.1:{0[publish_pull_port]}'.format(self.opts)
)
pub_sock.send(salt.payload.package(payload))
# Run the client get_returns method
return self.local._get_returns(
return self.local.get_returns(
jid,
self.local.check_minions(
clear_load['tgt'],
@ -602,7 +614,10 @@ class ClearFuncs(object):
'''
if not clear_load.pop('key') == self.key:
return ''
jid = prep_jid(self.opts['cachedir'], clear_load)
if clear_load.has_key('jid'):
jid = clear_load['jid']
else:
jid = prep_jid(self.opts['cachedir'], clear_load)
payload = {'enc': 'aes'}
load = {
'fun': clear_load['fun'],

View file

@ -23,6 +23,7 @@ import salt.utils
import salt.modules
import salt.returners
import salt.loader
import salt.client
log = logging.getLogger(__name__)
@ -291,6 +292,72 @@ class Minion(object):
self._handle_payload(payload)
class Syndic(salt.client.LocalClient, Minion):
'''
Make a Syndic minion, this minion wil use the minion keys on the master to
authenticate with a higher level master.
'''
def __init__(self, opts):
salt.client.LocalClient.__init__(self, opts['_master_conf_file'])
Minion.__init__(self, opts)
def _handle_aes(self, load):
'''
Takes the aes encrypted load, decrypts is and runs the encapsulated
instructions
'''
data = None
# If the AES authentication has changed, re-authenticate
try:
data = self.crypticle.loads(load)
except AuthenticationError:
self.authenticate()
data = self.crypticle.loads(load)
# Verify that the publication is valid
if not data.has_key('tgt')\
or not data.has_key('jid')\
or not data.has_key('fun')\
or not data.has_key('to')\
or not data.has_key('expr_form')\
or not data.has_key('arg'):
return
self._handle_decoded_payload(data)
def _handle_decoded_payload(self, data):
'''
Override this method if you wish to handle the decoded data differently.
'''
if self.opts['multiprocessing']:
multiprocessing.Process(
target=lambda: self.syndic_cmd(data)
).start()
else:
threading.Thread(
target=lambda: self.syndic_cmd(data)
).start()
def syndic_cmd(self, data):
'''
Take the now clear load and forward it on to the client cmd
'''
# Send out the publication
pub_data = self.pub(
data['tgt'],
data['fun'],
data['arg'],
data['expr_form']
)
# Gather the return data
ret = self.get_returns(
pub_data['jid'],
pub_data['minions'],
data['to']
)
ret['jid'] = data['jid']
ret['fun'] = data['fun']
# Return the publication data up the pipe
self._return_pub(ret)
class Matcher(object):
'''
Use to return the value for matching calls from the master

20
scripts/salt-syndic Normal file
View file

@ -0,0 +1,20 @@
#!/usr/bin/python2
'''
This script is used to kick off a salt syndic daemon
'''
import salt
import os
def main():
'''
The main function
'''
pid = os.getpid()
try:
syndic = salt.Syndic()
syndic.start()
except KeyboardInterrupt:
os.kill(pid, 15)
if __name__ == '__main__':
main()

View file

@ -98,10 +98,12 @@ setup(name=NAME,
'salt.runners',
'salt.grains',
'salt.states',
'salt.utils',
],
scripts=['scripts/salt-master',
'scripts/salt-minion',
'scripts/salt-monitor',
'scripts/salt-syndic',
'scripts/salt-key',
'scripts/salt-cp',
'scripts/salt-call',