因为/etc/salt下面好个配置文件,按照一般来说,master端应该只读取一个配置文件
/etc/salt/master
但是/etc/salt/下还有roster等文件不知道会不会读取,所以想干脆去瞅瞅源代码....
这一看就是个大坑
看了大半天saltstack,终于看明白了,到最后还是参考了
难点主要在于,saltsack的几个类初始化的使用使用python的元类
参考http://blog.jobbole.com/21351/
按照上面的说法
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
所以,所以要看懂saltstack,必须看下元类的大概作用,最后我还是参考了http://www.cnblogs.com/pping/p/3989699.html的文章才完全梳理清楚的
简单讲解下saltstack的启动过程
salt-master的init脚本就是调用/usr/bin/salt-master start
salt-master就是调用scripts.py里的salt_master方法
里面
master = salt.cli.daemons.Master()
下面坑就开始了,跟过去就看见
点击(此处)折叠或打开
class MasterOptionParser(OptionParser, ConfigDirMixIn, MergeConfigMixIn,
LogLevelMixIn, RunUserMixin, DaemonMixIn,
PidfileMixin, SaltfileMixIn):
__metaclass__ = OptionParserMeta
description = 'The Salt master, used to control the Salt minions.'
# ConfigDirMixIn config filename attribute
_config_filename_ = 'master'
# LogLevelMixIn attributes
_default_logging_logfile_ = os.path.join(syspaths.LOGS_DIR, 'master')
def setup_config(self):
return config.master_config(self.get_config_file_path())
草...老子第一次看多重继承的方法....整个都傻了
当然,等大致都看明白的时候,就知道为什么要这么写了
这一堆类只有OptionParser直接有init方法,不明白元类之都看傻了都不知道这些类是干什么的
其实只要大致知道一点,就好理解了——python在init之前还有个__new__方法,这个都藏在每个类开始的__metaclass__里
操作__metaclass__同样可以初始化一些东西
MasterOptionParser类是
__metaclass__ = OptionParserMeta
OptionParser类没设置__metaclass__
其余类都是
__metaclass__ = MixInMeta
点击(此处)折叠或打开
class MixInMeta(type):
# This attribute here won't actually do anything. But, if you need to
# specify an order or a dependency within the mix-ins, please define the
# attribute on your own MixIn
_mixin_prio_ = 0
def __new__(mcs, name, bases, attrs):
instance = super(MixInMeta, mcs).__new__(mcs, name, bases, attrs)
if not hasattr(instance, '_mixin_setup'):
raise RuntimeError(
'Don\'t subclass {0} in {1} if you\'re not going to use it '
'as a salt parser mix-in.'.format(mcs.__name__, name)
)
return instance
看代码会发现,只要是__metaclass__ = MixInMeta都会有_mixin_setup方法
所以__metaclass__ = MixInMeta用来确保继承的类必须含有名为_mixin_setup的方法
我们再看有__metaclass__类MasterOptionParser
http://www.cnblogs.com/pping/p/3989704.html这里有详细点的注释
__metaclass__ = OptionParserMeta
点击(此处)折叠或打开
class OptionParserMeta(MixInMeta):
def __new__(mcs, name, bases, attrs):
instance = super(OptionParserMeta, mcs).__new__(mcs,
name,
bases,
attrs)
if not hasattr(instance, '_mixin_setup_funcs'):
instance._mixin_setup_funcs = []
if not hasattr(instance, '_mixin_process_funcs'):
instance._mixin_process_funcs = []
if not hasattr(instance, '_mixin_after_parsed_funcs'):
instance._mixin_after_parsed_funcs = []
for base in _sorted(bases + (instance,)):
func = getattr(base, '_mixin_setup', None)
if func is not None and func not in instance._mixin_setup_funcs:
instance._mixin_setup_funcs.append(func)
func = getattr(base, '_mixin_after_parsed', None)
if func is not None and func not in \
instance._mixin_after_parsed_funcs:
instance._mixin_after_parsed_funcs.append(func)
# Mark process_ functions with the base priority for sorting
for func in dir(base):
if not func.startswith('process_'):
continue
func = getattr(base, func)
if getattr(func, '_mixin_prio_', None) is not None:
# Function already has the attribute set, don't override it
continue
func.__func__._mixin_prio_ = getattr(
base, '_mixin_prio_
结合上面说的大概就明白了
MasterOptionParser的__metaclass__ = OptionParserMeta
会把ConfigDirMixIn, MergeConfigMixIn,LogLevelMixIn, RunUserMixin, DaemonMixIn,PidfileMixin, SaltfileMixIn
这几个类的
_mixin_setup函数塞入_mixin_setup_funcs里,也就是MasterOptionParser._mixin_setup_funcs
_mixin_process_funcs函数塞入_mixin_process_funcs里,也就是MasterOptionParser._mixin_process_funcs
_mixin_after_parsed_funcs函数塞入_mixin_after_parsed_funcs里,也就是MasterOptionParser._mixin_after_parsed_funcs
于是当master=MasterOptionParser()的时候
master._mixin_setup/_mixin_process_funcs/_mixin_after_parsed_funcs里就有好多个函数了
上个测试代码看看
点击(此处)折叠或打开
import salt
from salt.utils import parsers, print_cli
class Stest(parsers.OptionParser,parsers.TimeoutMixIn,parsers.ConfigDirMixIn):
__metaclass__ = parsers.OptionParserMeta
default_timeout = 10
loli = Stest()
print len(loli._mixin_setup_funcs)
print loli._mixin_setup
结果是
2
>
再来看没有自定义__metaclass__的OptionParser类
msater类的init方法就在OptionParser里,OptionParser就是我们常用的继承类
OptionParser继承自python库optarse.OptionParser(optarse个库是用来处理参数的)
master类调用了自己的init方法以后,还调用了基类的init方法
optparse.OptionParser.__init__(self, *args, **kwargs)
optparse.OptionParser的init方法里有调用
self._populate_option_list(option_list,add_help=add_help_option)
由于master是继承自parsers.OptionParser的类
所以实际的_populate_option_list不是optarse.OptionParser里的,而是master自己(parsers.OptionParser)的_populate_option_list
这里调用了optarse.OptionParser里原来的同名函数后再调用所有_mixin_setup函数
点击(此处)折叠或打开
def _populate_option_list(self, option_list, add_help=True):
optparse.OptionParser._populate_option_list(
self, option_list, add_help=add_help
)
for mixin_setup_func in self._mixin_setup_funcs:
mixin_setup_func(self)
看到这里我我们就知道为什么要继承那么多类了
salt的各类对象继承不同的几个类类封装成对应的类
这样继承相当工厂化,几个零件拼在一起就成为一个新对象
元类是为了方便初始化,不然init里对应不同的封装会不好写成一样的代码。
master类创建好以后
master守护进程启动前会先调用parse_args函数
读取配置文件就在parse_args里
点击(此处)折叠或打开
for option_key in options.__dict__:
process_option_func = getattr(
self, 'process_{0}'.format(option_key), None
)
if process_option_func is not None:
process_option_funcs.append(process_option_func)
for process_option_func in _sorted(process_option_funcs):
try:
process_option_func()
except Exception as err:
logging.getLogger(__name__).exception(err)
self.error(
'Error while processing {0}: {1}'.format(
process_option_func, traceback.format_exc(err)
)
)
options.__dict__就是"config_dir"、"log_level"、"log_file"(字符串内容来源好找就不跟过去了)
所以上面就是分别执行process_config_dir、process_log_level、process_log_file
process_config_dir就在class ConfigDirMixIn里
点击(此处)折叠或打开
class ConfigDirMixIn(object):
__metaclass__ = MixInMeta
_mixin_prio_ = -10
_config_filename_ = None
def _mixin_setup(self):
config_dir = os.environ.get('SALT_CONFIG_DIR', None)
if not config_dir:
config_dir = syspaths.CONFIG_DIR
self.add_option(
'-c', '--config-dir', default=config_dir,
help=('Pass in an alternative configuration directory. Default: '
'%default')
)
def process_config_dir(self):
if not os.path.isdir(self.options.config_dir):
# No logging is configured yet
sys.stderr.write(
'WARNING: {0!r} directory does not exist.\n'.format(
self.options.config_dir
)
)
# Make sure we have an absolute path
self.options.config_dir = os.path.abspath(self.options.config_dir)
if hasattr(self, 'setup_config'):
if not hasattr(self, 'config'):
self.config = {}
try:
self.config.update(self.setup_config())
except (IOError, OSError) as exc:
self.error(
'Failed to load configuration: {0}'.format(exc)
)
def get_config_file_path(self, configfile=None):
if configfile is None:
configfile = self._config_filename_
return os.path.join(self.options.config_dir, configfile)
self.config = {}这个就是配置文件你内容存放的字典
然后self.config.update(self.setup_config())
setup_config又直接在class MasterOptionParser,看前面代码
点击(此处)折叠或打开
def setup_config(self):
return config.master_config(self.get_config_file_path())
get_config_file_path()又在class ConfigDirMixIn里(有点绕)
最终config.master_config获取到了文件路径,解析后
self.config这个字典里就存有了配置文件的内容
master类会更具self.config的内容做一些初始化后,并根据配置文件生产一个也叫master的实例
zmq的用
self.master = salt.master.Master(self.config)
新的ret上面的用
self.master = salt.daemons.flo.IofloMaster(self.config)
实例生成后
通过self.daemonize_if_required()
点击(此处)折叠或打开
class DaemonMixIn(object):
__metaclass__ = MixInMeta
_mixin_prio_ = 30
def _mixin_setup(self):
self.add_option(
'-d', '--daemon',
default=False,
action='store_true',
help='Run the {0} as a daemon'.format(self.get_prog_name())
)
def daemonize_if_required(self):
if self.options.daemon:
# Late import so logging works correctly
import salt.utils
salt.utils.daemonize()
成为守护进程,启动完毕
守护进程salt.utils.daemonize()的代码比较简单,就fork两次,默认把0、1、2 重定向到/dev/null
这里就不贴了
看懂这个其实还是很有意义的
salt的类都是在这个类工厂里封装的
比如说,默认超时时间是5秒,这个体现在代码的哪里.
默认超时时间就在TimeoutMixIn里,大家又兴趣可以自己看