openstack nova-api启动流程

一、预备知识

    nova-api服务启动过程比较简单,难点在于它用到的一些库不了解,建议先了解一下python paste.deploy,routes,webob这些这些模块。
    paste.deploy
       主要是通过解析/etc/nova/api-paste.ini,根据指定的name来加载wsgi app。

    webob

        WebOb是一个用于对WSGI request环境进行包装以及用于创建WSGI response的一个包。简化了wsgi app的编写。

    routes

        定义请求到具体函数的映射

nova-api启动流程

    在/usr/bin下有个nova-api脚本,这是整个过程的入口。我们可以将它的内容打印出来看看
    
import sys

from nova.cmd.api import main

if __name__=="__main__":
    sys.exit(main())
    可以看到,执行的是nova/cmd/api.py中mian。我们再去看看api.py的main函数
def main():
    config.parse_args(sys.argv)
    logging.setup(CONF, "nova")
    utils.monkey_patch()
    objects.register_all()

    gmr.TextGuruMeditation.setup_autorun(version)

    launcher = service.process_launcher()
    for api in CONF.enabled_apis:
        should_use_ssl = api in CONF.enabled_ssl_apis
        if api == 'ec2':
            server = service.WSGIService(api, use_ssl=should_use_ssl,
                                         max_url_len=16384)
        else:
            server = service.WSGIService(api, use_ssl=should_use_ssl)
        launcher.launch_service(server, workers=server.workers or 1)
    launcher.wait()
    首先是解析参数:
config.parse_args(sys.argv)
    然后设置日志
logging.setup(CONF, "nova")
    打补丁,不明白
utils.monkey_patch()
    获取服务启动器
launcher = service.process_launcher()
重头戏来了,启动服务
    for api in CONF.enabled_apis:
        should_use_ssl = api in CONF.enabled_ssl_apis
        if api == 'ec2':
            server = service.WSGIService(api, use_ssl=should_use_ssl,
                                         max_url_len=16384)
        else:
            server = service.WSGIService(api, use_ssl=should_use_ssl)
        launcher.launch_service(server, workers=server.workers or 1)
来看看service.WSGIService到底是什么东西,贴出部分WSGIService代码
class WSGIService(object):
    """Provides ability to launch API from a 'paste' configuration."""

    def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
        """Initialize, but do not start the WSGI server.

        :param name: The name of the WSGI server given to the loader.
        :param loader: Loads the WSGI application using the given name.
        :returns: None

        """
        self.name = name
        self.manager = self._get_manager()
        self.loader = loader or wsgi.Loader()
        self.app = self.loader.load_app(name)
        # inherit all compute_api worker counts from osapi_compute
        if name.startswith('openstack_compute_api'):
            wname = 'osapi_compute'
        else:
            wname = name
        self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
        self.port = getattr(CONF, '%s_listen_port' % name, 0)
        self.workers = (getattr(CONF, '%s_workers' % wname, None) or
                        processutils.get_worker_count())
        if self.workers and self.workers < 1:
            worker_name = '%s_workers' % name
            msg = (_("%(worker_name)s value of %(workers)s is invalid, "
                     "must be greater than 0") %
                   {'worker_name': worker_name,
                    'workers': str(self.workers)})
            raise exception.InvalidInput(msg)
        self.use_ssl = use_ssl
        self.server = wsgi.Server(name,
                                  self.app,
                                  host=self.host,
                                  port=self.port,
                                  use_ssl=self.use_ssl,
                                  max_url_len=max_url_len)
        # Pull back actual port used
        self.port = self.server.port
        self.backdoor_port = None
    """Provides ability to launch API from a 'paste' configuration."""
    意思是通过paste配置来生成WSGI的app。
    着重看一下这里面的loader,这是用来启动app的。
self.loader = loader or wsgi.Loader()
    看一下nova/wsgi.py中的Loader,通过paste配置来启动wsgi app
class Loader(object):
    """Used to load WSGI applications from paste configurations."""

    def __init__(self, config_path=None):
        """Initialize the loader, and attempt to find the config.

        :param config_path: Full or relative path to the paste config.
        :returns: None

        """
        self.config_path = None

        config_path = config_path or CONF.api_paste_config
        if not os.path.isabs(config_path):
            self.config_path = CONF.find_file(config_path)
        elif os.path.exists(config_path):
            self.config_path = config_path

        if not self.config_path:
            raise exception.ConfigNotFound(path=config_path)

    def load_app(self, name):
        """Return the paste URLMap wrapped WSGI application.

        :param name: Name of the application to load.
        :returns: Paste URLMap object wrapping the requested application.
        :raises: `nova.exception.PasteAppNotFound`

        """
        try:
            LOG.debug("Loading app %(name)s from %(path)s",
                      {'name': name, 'path': self.config_path})
            return deploy.loadapp("config:%s" % self.config_path, name=name)
        except LookupError:
            LOG.exception(_LE("Couldn't lookup app: %s"), name)
            raise exception.PasteAppNotFound(name=name, path=self.config_path)
    可以看到load_app是通过deploy.loadapp()来启动wsgi app
    然后是:
launcher.launch_service(server, workers=server.workers or 1)
    看看launch_service()是什么
    def launch_service(self, service, workers=1):
        wrap = ServiceWrapper(service, workers)

        LOG.info(_LI('Starting %d workers'), wrap.workers)
        while self.running and len(wrap.children) < wrap.workers:
            self._start_child(wrap)
    ServiceWrapper是一类,类似于C语言中结构体的作用
class ServiceWrapper(object):
    def __init__(self, service, workers):
        self.service = service
        self.workers = workers
        self.children = set()
        self.forktimes = []

    def _start_child(self, wrap):
        if len(wrap.forktimes) > wrap.workers:
            # Limit ourselves to one process a second (over the period of
            # number of workers * 1 second). This will allow workers to
            # start up quickly but ensure we don't fork off children that
            # die instantly too quickly.
            if time.time() - wrap.forktimes[0] < wrap.workers:
                LOG.info(_LI('Forking too fast, sleeping'))
                time.sleep(1)

            wrap.forktimes.pop(0)

        wrap.forktimes.append(time.time())

        pid = os.fork()
        if pid == 0:
            launcher = self._child_process(wrap.service)
            while True:
                self._child_process_handle_signal()
                status, signo = self._child_wait_for_exit_or_signal(launcher)
                if not _is_sighup_and_daemon(signo):
                    break
                launcher.restart()

            os._exit(status)

        LOG.info(_LI('Started child %d'), pid)

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

        return pid
    首先,os.fork()返回值可能为0,表示子进程本身,或者大于0表示父进程。
    在这里,父进程主要是创建子进程,更新warp信息,返回子进程的pid。
    当pid=0时表示子进程,子进程的话先通过launcher = self._child_process(wrap.service)启动对应的服务,然后就是while循环等待信号。如果有终止的信号量就结束,否则则重新启动进程。
    最后是    
   launcher.wait()
    它的注释是:"""Loop waiting on children to die and respawning as necessary."""。它的作用是循环监听子进程是否挂掉,如果挂掉则重启。如果收到终止的信号,则kill所有的children然后结束自己。

你可能感兴趣的:(openstack nova-api启动流程)