分析了服务启动的架构,下面看一下服务启动的源码.分析的不好,还请指教
创建好了builder文件和ring文件之后,下一步的操作就是启动服务了,通常启动单独的服务会有单独的命令,例如swift-proxy-server start等,但是一般我们使用swift-init命令,因为大多数情况下所有的服务会安装在同一台机器上,或者是存储服务会安装在同一台机器上。swift-init会设计到/swift/common/manager.py中的Manager Server类,Manager类来管理命令,Server是相应服务的实例,其中包括对服务的一些操作。
swift-init源码:
USAGE = """%prog <server> [<server> ...] <command> [options]#获得命令提示
Commands:
""" + '\n'.join(["%16s: %s" % x for x in Manager.list_commands()])
def main():
parser = OptionParser(USAGE)
parser.add_option('-v', '--verbose', action="store_true",#设置参数#
default=False, help="display verbose output")
parser.add_option('-w', '--no-wait', action="store_false", dest="wait",
default=True, help="won't wait for server to start "
"before returning")
parser.add_option('-o', '--once', action="store_true",
default=False, help="only run one pass of daemon")
# this is a negative option, default is options.daemon = True
parser.add_option('-n', '--no-daemon', action="store_false", dest="daemon",
default=True, help="start server interactively")
parser.add_option('-g', '--graceful', action="store_true",
default=False, help="send SIGHUP to supporting servers")
parser.add_option('-c', '--config-num', metavar="N", type="int",
dest="number", default=0,
help="send command to the Nth server only")
options, args = parser.parse_args()
if len(args) < 2:#长度小于2,也就是输入类似swift_init proxy 没有输入命令,会提示。
parser.print_help()
print 'ERROR: specify server(s) and command'
return 1
command = args[-1] #切分命令和服务
servers = args[:-1]
# this is just a silly swap for me cause I always try to "start main"
commands = dict(Manager.list_commands()).keys()#判断命令是否合法
if command not in commands and servers[0] in commands:
servers.append(command)
command = servers.pop(0)
manager = Manager(servers)#初始化manager实例
try:
status = manager.run_command(command, **options.__dict__)#执行命令函数
except UnknownCommandError:
parser.print_help()
print 'ERROR: unknown command, %s' % command
status = 1
return 1 if status else 0
if __name__ == "__main__":
sys.exit(main())
其中使用了功能比较强大的optpartse(当然对于我这种初学者,强不强大都一样0.0),值得注意的是parse.add_option方法添加的参数,非常值得注意,因为一般情况下我们不会输入这些参数,但是它们会制止带入到后面的方法中,来执行相应的逻辑。其中manager.run_commond()来执行命令。
def run_command(self, cmd, **kwargs):
"""Find the named command and run it
:param cmd: the command name to run
"""
f = self.get_command(cmd)#返回命令,例如start,然后执行相应的方法
return f(**kwargs)
其实run_command方法就是调用相应命令对应的方法比如start().
def get_command(self, cmd):
"""Find and return the decorated method named like cmd
:param cmd: the command to get, a string, if not found raises
UnknownCommandError
"""
cmd = cmd.lower().replace('-', '_')
try:
f = getattr(self, cmd)
except AttributeError:
raise UnknownCommandError(cmd)
if not hasattr(f, 'publicly_accessible'):
raise UnknownCommandError(cmd)
return f
@classmethod #类方法,没有实例也可以执行
def list_commands(cls):
"""Get all publicly accessible commands
:returns: a list of string tuples (cmd, help), the method names who are
decorated as commands
"""
get_method = lambda cmd: getattr(cls, cmd)
return sorted([(x.replace('_', '-'), get_method(x).__doc__.strip())
for x in dir(cls) if
getattr(get_method(x), 'publicly_accessible', False)])
@command
def start(self, **kwargs):
"""starts a server
"""
setup_env()#进行初始化,
status = 0
for server in self.servers:#不同的server执行launch()方法
server.launch(**kwargs)
if not kwargs.get('daemon', True):#默认为True
for server in self.servers:
try:
status += server.interact(**kwargs)#与wait类似,收集程序返回的信息。
except KeyboardInterrupt:
print _('\nuser quit')
self.stop(**kwargs)
break
elif kwargs.get('wait', True):
for server in self.servers:
status += server.wait(**kwargs)#接受服务启动进程返回的信息
return status
start方法中会调用launch方法
def launch(self, **kwargs):
"""
Collect conf files and attempt to spawn the processes for this server
"""
conf_files = self.conf_files(**kwargs)
if not conf_files:
return []
pids = self.get_running_pids(**kwargs)#查看已经启动的服务
already_started = False
for pid, pid_file in pids.items():#做判断,如果已经启动,返回已经启动。
conf_file = self.get_conf_file_name(pid_file)
# for legacy compat you can't start other servers if one server is
# already running (unless -n specifies which one you want), this
# restriction could potentially be lifted, and launch could start
# any unstarted instances
if conf_file in conf_files:
already_started = True
print _("%s running (%s - %s)") % (self.server, pid, conf_file)
elif not kwargs.get('number', 0):
already_started = True
print _("%s running (%s - %s)") % (self.server, pid, pid_file)
if already_started:
print _("%s already started...") % self.server
return []
if self.server not in START_ONCE_SERVERS:#默认是false,如果你的服务不在START_ONCE_SERVER,但是你社会自了为True,也会被设置为False.
kwargs['once'] = False
pids = {}
for conf_file in conf_files:
if kwargs.get('once'):
msg = _('Running %s once') % self.server
else:
msg = _('Starting %s') % self.server
print '%s...(%s)' % (msg, conf_file)
try:
pid = self.spawn(conf_file, **kwargs)#最终启动进程的函数。返回pid
except OSError, e:
if e.errno == errno.ENOENT:
# TODO: should I check if self.cmd exists earlier?
print _("%s does not exist") % self.cmd
break
pids[pid] = conf_file
return pids
launch方法会调用spawn方法最终通过subproess.Popen来启动一个子进程执行命令,相当于swift-proxy-server start
def spawn(self, conf_file, once=False, wait=True, daemon=True, **kwargs):
"""Launch a subprocess for this server.
:param conf_file: path to conf_file to use as first arg
:param once: boolean, add once argument to command
:param wait: boolean, if true capture stdout with a pipe
:param daemon: boolean, if true ask server to log to console
:returns : the pid of the spawned process
"""
args = [self.cmd, conf_file]#命令和配置文件
if once:
args.append('once')
if not daemon:
# ask the server to log to console
args.append('verbose')
# figure out what we're going to do with stdio
if not daemon:
# do nothing, this process is open until the spawns close anyway
re_out = None
re_err = None
else:
re_err = subprocess.STDOUT
if wait:
# we're going to need to block on this...
re_out = subprocess.PIPE
else:
re_out = open(os.devnull, 'w+b')
proc = subprocess.Popen(args, stdout=re_out, stderr=re_err)#启动子进程,例如,如果你输入的是swift-init proxy start,实际上他会执行swift-proxy-server start 把配置文件带入
pid_file = self.get_pid_file_name(conf_file)
write_file(pid_file, proc.pid)#写入pid_file未见
self.procs.append(proc)
return proc.pid返回pid
swift-proxy-server中就两行代码,调用/swift/common/wsgi.py中的run_wsgi方法。
if __name__ == '__main__':
conf_file, options = parse_options()
run_wsgi(conf_file, 'proxy-server', default_port=8080, **options)
其实最终启动服务的是wsgi.py中的run_wsgi方法(因为实际上swift就是提供一个wsgi服务)。
def run_wsgi(conf_file, app_section, *args, **kwargs):
"""
Loads common settings from conf, then instantiates app and runs
the server using the specified number of workers.
:param conf_file: Path to paste.deploy style configuration file
:param app_section: App name from conf file to load config from
"""
try:
conf = appconfig('config:%s' % conf_file, name=app_section)#获取conf_file中关于proxy-server的配置
except Exception, e:
print "Error trying to load config %s: %s" % (conf_file, e)
return
validate_configuration()#验证是否设置HASH_PATH_SUFFIX
# pre-configure logger#日志设置
log_name = conf.get('log_name', app_section)
if 'logger' in kwargs:
logger = kwargs.pop('logger')
else:
logger = get_logger(conf, log_name,
log_to_console=kwargs.pop('verbose', False), log_route='wsgi')
# disable fallocate if desired#新版本加入的函数 ,调用系统函数,预留物理空间,目前理解,并不准确。
if conf.get('disable_fallocate', 'no').lower() in TRUE_VALUES:
disable_fallocate()
# bind to address and port#生产socket
sock = get_socket(conf, default_port=kwargs.get('default_port', 8080))
# remaining tasks should not require elevated privileges#剩下的任务不需要高级的权限。
drop_privileges(conf.get('user', 'swift'))
# Ensure the application can be loaded before proceeding.#确保应用可以被加载
loadapp('config:%s' % conf_file, global_conf={'log_name': log_name})
# redirect errors to logger and close stdio#直接将错误输入到日志中,关闭标准输出。
capture_stdio(logger)
def run_server():#启动server函数
wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
# Turn off logging requests by the underlying WSGI software.
wsgi.HttpProtocol.log_request = lambda *a: None
# Redirect logging other messages by the underlying WSGI software.
wsgi.HttpProtocol.log_message = \
lambda s, f, *a: logger.error('ERROR WSGI: ' + f % a)
wsgi.WRITE_TIMEOUT = int(conf.get('client_timeout') or 60)
eventlet.hubs.use_hub('poll')
eventlet.patcher.monkey_patch(all=False, socket=True)
monkey_patch_mimetools()
app = loadapp('config:%s' % conf_file,#取得相应的app和filter
global_conf={'log_name': log_name})
pool = GreenPool(size=1024)
try:
wsgi.server(sock, app, NullLogger(), custom_pool=pool)#利用eventlet.wsgi.server()方法 启动服务
except socket.error, err:
if err[0] != errno.EINVAL:
raise
pool.waitall()
worker_count = int(conf.get('workers', '1'))
# Useful for profiling [no forks].
if worker_count == 0:#如果worker_count等于0直接运行server
run_server()
return
#之后的程序会根据不同的方式创建,进程来执行run_server()方法
def kill_children(*args):
"""Kills the entire process group."""
logger.error('SIGTERM received')
signal.signal(signal.SIGTERM, signal.SIG_IGN)
running[0] = False
os.killpg(0, signal.SIGTERM)
def hup(*args):
"""Shuts down the server, but allows running requests to complete"""
logger.error('SIGHUP received')
signal.signal(signal.SIGHUP, signal.SIG_IGN)
running[0] = False
running = [True]
signal.signal(signal.SIGTERM, kill_children)
signal.signal(signal.SIGHUP, hup)
children = []
while running[0]:
while len(children) < worker_count:
pid = os.fork()
if pid == 0:
signal.signal(signal.SIGHUP, signal.SIG_DFL)
signal.signal(signal.SIGTERM, signal.SIG_DFL)
run_server()
logger.notice('Child %d exiting normally' % os.getpid())
return
else:
logger.notice('Started child %s' % pid)
children.append(pid)
try:
pid, status = os.wait()
if os.WIFEXITED(status) or os.WIFSIGNALED(status):
logger.error('Removing dead child %s' % pid)
children.remove(pid)
except OSError, err:
if err.errno not in (errno.EINTR, errno.ECHILD):
raise
except KeyboardInterrupt:
logger.notice('User quit')
break
greenio.shutdown_safe(sock)
sock.close()
logger.notice('Exited')
run_wsgi方法相当复杂,最终通过内部方法run_server()函数中的,wsgi.server()启动服务,其中使用eventlet库,还用进程替换,等操作,还一些日志,已经初始化的所及操作。由于我本身初学python,所以分析的是很细致。值得注意的是,swift配置文件适用使用paste.deploy方式来书写,其中的各个服务。