【OpenStack Liberty】cinder WSGIService启动流程(cinder-api)

提示:本文基于openstack liberty版本源码进行梳理


前言

上一篇文章(【OpenStack Liberty】cinder service模块启动流程)中以cinder-sechduler启动为例走读了代码。也提到scheduler、volume、backup都属于Service,而cinder-api属于WSGI Service,其实主要启动流程基本一致,本文主要偏向讲WSGIService启动流程。


一、启动流程

1.main方法

代码文件位置:cinder/cmd/api.py

...

def main():
  	
    ...
    # 1. 获取一个process launcher
    launcher = service.process_launcher()
    # 2. 创建一个名为`osapi_volume`的`WSGIService`服务对象
    server = service.WSGIService('osapi_volume')
    # 3. 加载service
    launcher.launch_service(server, workers=server.workers)
    service.wait()

可以看到跟Service服务的启动方式大致相同,只不过这里使用的Launcher和cinder-scheduler不一样。

openstack中的Launcher分为两种:

  • ServiceLauncher
  • ProcessLaunche

ServiceLauncher用来启动单进程的服务;而ProcessLauncher用来启动有多个worker子进程的服务,如各类api服务(nova-api、cinder-api)等

2.ProcessLauncher

ProcessLauncher直接继承于Object,同样也有launch_service方法

lauch_service除了接受service以外,还需要接受一个workers参数,即子进程的个数,然后调用_start_child启动多个子进程

class ProcessLauncher(object):
    def launch_service(self, service, workers=1):
       
        _check_service_base(service)
        wrap = ServiceWrapper(service, workers)
        ...
        
        LOG.info('Starting %d workers', wrap.workers)
        while self.running and len(wrap.children) < wrap.workers:
          	# 调用_start_child启动子进程
            self._start_child(wrap)  

_start_child调用了一个os.fork(),然后子进程开始运行,子进程调用_child_process

    def _start_child(self, wrap):
    
        ...
      
        pid = os.fork()
        if pid == 0:
          	# 子进程调用_child_process
            self.launcher = self._child_process(wrap.service)
            while True:
                self._child_process_handle_signal()
                status, signo = self._child_wait_for_exit_or_signal(
                    self.launcher)
                if not _is_sighup_and_daemon(signo):
                    self.launcher.wait()
                    break
                self.launcher.restart()

            os._exit(status)

        LOG.debug('Started child %d', pid)

        wrap.children.add(pid)
        self.children[pid] = wrap

        return pid

_child_process其实很简单:

  1. 创建一个Launcher
  2. 调用Laucher.launch_service方法

前面介绍过,其实ServiceLauncher继承自Launcher,也是调用的launcher_service方法,将服务启动,因此接下来的步骤可以参考前面,最终都将调用service.start方法启动服务

    def _child_process(self, service):
    
        ...
      
        launcher = Launcher(self.conf, restart_method=self.restart_method)
        launcher.launch_service(service)
        return launcher

3. WSGIService启动

通过刚才ProcessLauncher的分析,我们知道服务的启动最终调用了service的start方法,而这里的service就是我们最开始在api.py中创建的service,然后一层层传进后面的启动器中的,我们继续回到WSGIService类中的start(self)方法

    def start(self):

            if self.manager:
                self.manager.init_host()
          	# wsgi.Server
            self.server.start()
            self.port = self.server.port

联系上下文知:这里的self.serverwsgi.Server实例。

代码位置cinder/wsgi/eventlet_server.py

    def start(self):
        # The server socket object will be closed after server exits,
        # but the underlying file descriptor will remain open, and will
        # give bad file descriptor error. So duplicating the socket object,
        # to keep file descriptor usable.

        dup_socket = self._socket.dup()
        dup_socket.setsockopt(socket.SOL_SOCKET,
                              socket.SO_REUSEADDR, 1)

        # NOTE(praneshp): Call set_tcp_keepalive in oslo to set
        # tcp keepalive parameters. Sockets can hang around forever
        # without keepalive
        netutils.set_tcp_keepalive(dup_socket,
                                   CONF.tcp_keepalive,
                                   CONF.tcp_keepidle,
                                   CONF.tcp_keepalive_count,
                                   CONF.tcp_keepalive_interval)

        # ssl相关暂时忽略

        wsgi_kwargs = {
          	# 关注下这个参数
            'func': eventlet.wsgi.server,
            'sock': dup_socket,
            'site': self.app,
            'protocol': self._protocol,
            'custom_pool': self._pool,
            'log': self._logger,
            'socket_timeout': self.client_socket_timeout,
            'keepalive': CONF.wsgi_keep_alive
        }

        self._server = eventlet.spawn(**wsgi_kwargs)

注意 wsgi_kwargs 中的参数 func,它的值为 eventlet.wsgi.server,在 eventlet/wsgi.py 的定义如下:

def server(sock, site,
    ...):
  
    ...       
    
    try:
        serv.log.info('({0}) wsgi starting up on {1}'.format(serv.pid, socket_repr(sock)))
        while is_accepting:
            try:
                client_socket, client_addr = sock.accept()
                client_socket.settimeout(serv.socket_timeout)
                serv.log.debug('({0}) accepted {1!r}'.format(serv.pid, client_addr))
                connections[client_addr] = connection = [client_addr, client_socket, STATE_IDLE]
                (pool.spawn(serv.process_request, connection)
                    .link(_clean_connection, connection))
            except ACCEPT_EXCEPTIONS as e:
                if support.get_errno(e) not in ACCEPT_ERRNO:
                    raise
            except (KeyboardInterrupt, SystemExit):
                serv.log.info('wsgi exiting')
                break
    finally:
        for cs in six.itervalues(connections):
            prev_state = cs[2]
            cs[2] = STATE_CLOSE
            if prev_state == STATE_IDLE:
                greenio.shutdown_safe(cs[1])
        pool.waitall()
        serv.log.info('({0}) wsgi exited, is_accepting={1}'.format(serv.pid, is_accepting))
        ...

这里可以看到sock.accept() 监听请求,每当接收到一个新请求,调用 pool.spawn() 启动一个协程处理该请求

自此cinder-api启动完毕。

4. 流程图

main ProcessLauncher WSGIService 根据配置创建ProcessLauncher 创建WSGIService对象(osapi_volume) 创建子进程启动WSGIService 调用start,最终进行socket监听请求 main ProcessLauncher WSGIService

总结

以上就是cinde-api的启动流程,仅仅贴上主要代码及注释,中间各项细节加载可以根据这个方向去仔细看,本文不再贴出。

如有错误欢迎指教😘
后面有更好的想法再补充

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值