【原创】OpenStack Swift源码分析(三)proxy服务启动

    分析了服务启动的架构,下面看一下服务启动的源码.分析的不好,还请指教

    创建好了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方式来书写,其中的各个服务。

转载于:https://my.oschina.net/zhouxingxing/blog/82280

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值