天天看点

OpenStack Swift源码分析(2)----swift服务启动源码分析之二

感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!

如果转载,请保留作者信息。

博客地址:http://blog.csdn.net/gaoxingnengjisuan

邮箱地址:[email protected]

继续看方法def start(self, **kwargs):

def start(self, **kwargs):  
            """ 
            启动一个服务; 
            """  
            setup_env()  
            status = 0  
      
            for server in self.servers:  
                server.launch(**kwargs)  
            if not kwargs.get('daemon', True):  
                for server in self.servers:  
                    try:  
                        status += server.interact(**kwargs)  
                    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  
           

我们先来看代码:

for server in self.servers:
    server.launch(**kwargs)
           

前面分析知道,在类Manager初始化的过程中,得到:

self.server = ['proxy-server', 'account-server', 'container-server', 'object-server']

所以这个for循环实现了通过调用launch方法依次启动这四个服务;

注:示例:

kwargs = {'run_dir': '/var/run/swift', 'daemon': True, 'verbose': False, 'number': 0, 'kill_wait': 15, 'graceful': False, 'once': False, 'wait': True}

我们具体来看方法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;
        # 返回一个pids映射到pid_files的字典;
        pids = self.get_running_pids(**kwargs)

        already_started = False
        for pid, pid_file in pids.items():
            
            # 转换pid_file到相应的conf_file;
            # 返回pid_file对应的conf_file;
            conf_file = self.get_conf_file_name(pid_file)
            
            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:
            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:
                # conf_file:/etc/swift/proxy-server.conf
                # kwargs = {'run_dir':'/var/run/swift','damon':True,'verbose':False,'number':0,'kill_wait':15,'graceful':False,'once':False,'wait':True}
                pid = self.spawn(conf_file, **kwargs)
            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
           

这个方法主要实现了根据服务获取具体的配置文件,并试图通过调用方法spawn来为服务的运行启动一个子进程,方法的最后返回了这个子进程的进程号;

首先来看代码:

​conf_files = self.conf_files(**kwargs)

if not conf_files:
    return []
           

实现的是通过传入的kwargs获取服务匹配的配置文件,如果没有配置文件直接返回;

调试运行示例:

conf_files = ['/etc/swift/proxy-server.conf']

再来看代码:

# 获取运行的pids;
# 返回一个pids映射到pid_files的字典;
pids = self.get_running_pids(**kwargs)
           

调试运行示例:

pids = {10955: '/var/run/swift/proxy-server.pid'}

可见这条语句返回的是正在运行进程的pid号,和具体匹配的pid_file的路径,为后面判断要启动的服务是否已经运行做准备;

看下面一段代码,通过配置文件来判断服务是否已经启动:

already_started = False
for pid, pid_file in pids.items():
            
    # 转换pid_file到相应的conf_file;
    # 返回pid_file对应的conf_file;
    conf_file = self.get_conf_file_name(pid_file)
            
    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 []
           

前面我们通过kwargs获取了正在运行的服务的pid值和相应的pid_file,比如:

pids = {10955: '/var/run/swift/proxy-server.pid'}

这里遍历pids这个字典,做了以下几件事:

(1)通过pid_file获取对应服务的配置文件conf_file;

(2)判断获取的conf_file是否包含在已经获取的conf_files中,如果是的话,说明要启动的这个服务已经处于运行状态;

(3)如果(2)判断为假,继续判断传进来的参数kwargs中,是否包含'number',这一选项,它的值是否为0,如果是的话,也说明要启动的这个服务已经处于运行状态;

最后,如果already_started的值为真,说明服务已经运行了,所以打印输出提示后,直接返回;

继续来看代码,如何为服务来启动一个子进程:

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:
       # conf_file:/etc/swift/proxy-server.conf
       # kwargs = {'run_dir':'/var/run/swift','damon':True,'verbose':False,'number':0,'kill_wait':15,'graceful':False,'once':False,'wait':True}
       pid = self.spawn(conf_file, **kwargs)
   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
           

这段代码是遍历conf_files,根据每个配置文件来为每个服务启动一个子进程,并返回相应的进程号,最后把启动的子进程的进程号pid和相应服务的配置文件conf_file一一对应起来;

为服务启动子进程主要是通过语句:pid = self.spawn(conf_file, **kwargs)

我们具体来看方法spawn:

def spawn(self, conf_file, once=False, wait=True, daemon=True, **kwargs):
        """
        为服务启动一个子进程;
        返回衍生进程的pid;
        
        # 调用之一传进来的参数:
        # conf_file:/etc/swift/proxy-server.conf
        # kwargs = {'run_dir':'/var/run/swift','damon':True,'verbose':False,'number':0,'kill_wait':15,'graceful':False,'once':False,'wait':True}     
        """
        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')
                
        # Popen:创建新的Popen实例;
        proc = subprocess.Popen(args, stdout=re_out, stderr=re_err)
        
        # 转换conf_file到相应的pid_file;
        # 返回conf_file相应的pid_file;
        pid_file = self.get_pid_file_name(conf_file)
        
        # 写proc.pid内容到pid_file;
        write_file(pid_file, proc.pid)
        
        self.procs.append(proc)
        
        return proc.pid
           

这个方法完成的是调用方法Popen实现把一个服务放到一个子进程中去运行;

来具体看代码:

args = [self.cmd, conf_file]
        
        if once:
            args.append('once')
         
        if not daemon:
            # ask the server to log to console
            args.append('verbose')
           

这部分代码完成的是把cmd和配置文件conf_file路径以及其他一些参数整合到一起,组成命令参数args,将会用于在后面传入到方法Popen中,实现在子进程中运行args中指定的服务;

调试示例:

args = ['swift-proxy-server', '/etc/swift/proxy-server.conf']

即指定了要运行的服务和它的配置文件;

继续看代码:

# 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')
           

这段代码实现的是明确一些参数用于告诉子进程要做什么,但是具体的参数含义还没有研究;

来看最关键的一条语句:

# Popen:创建新的Popen实例;
proc = subprocess.Popen(args, stdout=re_out, stderr=re_err)
           

这里调用了Popen方法实现了把指定的服务放到一个子进程中来运行;

这里传入了参数args指定了要运行的服务和相应服务的配置文件,这是比较关键的地方;

这个方法中实际上分别调用了/usr/bin/swift-proxy-server、/usr/bin/swift-container-server、/usr/bin/swift-account-server、/usr/bin/swift-object-server等程序中的main方法,实现了对指定服务的启动运行;我们来深入跟踪一下args这个参数,来看看是如何实现调用这几个服务代码的:

class Popen(object):
    def __init__(self, args, bufsize=0, executable=None,
                 stdin=None, stdout=None, stderr=None,
                 preexec_fn=None, close_fds=False, shell=False,
                 cwd=None, env=None, universal_newlines=False,
                 startupinfo=None, creationflags=0):
        """Create new Popen instance."""

        ......

        self._execute_child(args, executable, preexec_fn, close_fds,
                            cwd, env, universal_newlines,
                            startupinfo, creationflags, shell,
                            p2cread, p2cwrite,
                            c2pread, c2pwrite,
                            errread, errwrite)
           
def _execute_child(self, args, executable, preexec_fn, close_fds,
                           cwd, env, universal_newlines,
                           startupinfo, creationflags, shell,
                           p2cread, p2cwrite,
                           c2pread, c2pwrite,
                           errread, errwrite):
    """Execute program (POSIX version)"""

    if isinstance(args, types.StringTypes):
        args = [args]
    else:
        args = list(args)

    if shell:
        args = ["/bin/sh", "-c"] + args
        if executable:
            args[0] = executable

    if executable is None:
        executable = args[0]

    ......

    try:
        try:
        
        ......

        if self.pid == 0:
            try:
                
                ......
                
                if env is None:
                    os.execvp(executable, args)
                else:
                    os.execvpe(executable, args, env)

    ......
           

调试运行示例:

executable = swift-proxy-server

args = ['swift-proxy-server', '/etc/swift/proxy-server.conf']

env = None

def execvp(file, args):
    _execvpe(file, args)
           
def _execvpe(file, args, env=None):
    if env is not None:
        func = execve
        argrest = (args, env)
    else:
        func = execv
        argrest = (args,)
        env = environ

    head, tail = path.split(file)
    if head:
        func(file, *argrest)
        return
    if 'PATH' in env:
        envpath = env['PATH']
    else:
        envpath = defpath
    PATH = envpath.split(pathsep)

    saved_exc = None
    saved_tb = None
    for dir in PATH:
        fullname = path.join(dir, file)
        try:
            func(fullname, *argrest)
    
        ......

    ......
           

调试运行示例:

argrest = (['swift-proxy-server', '/etc/swift/proxy-server.conf'],)

fullname = /usr/local/sbin/swift-proxy-server

argrest = (['swift-proxy-server', '/etc/swift/proxy-server.conf'],)

fullname = /usr/local/bin/swift-proxy-server

argrest = (['swift-proxy-server', '/etc/swift/proxy-server.conf'],)

fullname = /usr/sbin/swift-proxy-server

argrest = (['swift-proxy-server', '/etc/swift/proxy-server.conf'],)

fullname = /usr/bin/swift-proxy-server

argrest = (['swift-proxy-server', '/etc/swift/proxy-server.conf'],)

我们可以看到,程序实现了依次查找/usr/local/sbin/、 /usr/local/bin/、/usr/sbin/和/usr/bin/等路径,查找swift-proxy-server文件,并执行其中的main方法;

我会在下一篇博客中具体分析,swift-proxy-server中的main方法是怎样实现运行proxy服务的;

继续来看方法spawn的代码:

# 转换conf_file到相应的pid_file;
# 返回conf_file相应的pid_file;
pid_file = self.get_pid_file_name(conf_file)
        
# 写proc.pid内容到pid_file;
write_file(pid_file, proc.pid)
        
self.procs.append(proc)
        
return proc.pid
           

这段代码主要做了一下几件事:

(1)通过conf_file获取相应的pid_file;

(2)把新启动的子进程的pid写进到文件pid_file之中;

(3)把新启动的子进程的对象加入到procs之中;

(4)返回新启动的子进程的pid值;

到这里方法spawn分析完成,我们大体可以了解了系统是怎样启动一个子进程来运行指定的服务的;

我们回到方法lauch,方法最后返回了所启动的一系列子进程的集合pids;

我们回到方法start,方法的最后根据参数设定,来决定执行方法interact或者方法wait,来等待子进程的运行结束;

最后方法start返回子进程的运行状态给swift-init中的方法main,即解析完成方法main中的语句:

status = manager.run_command(command, **options.__dict__)
           

至此,整个swift服务的启动过程我们分析完成,我将会在下一篇博客中具体分析swift-proxy-server中的main方法是如何实现运行proxy服务的。

博文中不免有不正确的地方,欢迎朋友们不吝批评指正,谢谢大家了!

OpenStack Swift源码分析(2)----swift服务启动源码分析之二

继续阅读