nova-api源碼分析(WSGI server的創建及啟動)


源碼版本:H版

一、前奏

       nova api本身作為一個WSGI服務器,對外提供HTTP請求服務,對內調用nova的其他模塊響應相應的HTTP請求。分為兩大部分,一是服務器本身的啟動與運行,一是加載的app,這個用來處理請求。

   目錄結構如下:

      

      首先,nova api是作為一個WSGI服務,肯定要查看它的啟動過程,查看啟動腳本/etc/init.d/openstack-nova-api(使用service命令實際在調用/etc/init.d文件夾下的腳本文件)。在/etc/init.d/openstack-nova-api文件中,start選項代表調用腳本/usr/bin/nova-api,如下:

 /usr/bin/nova-api

import sys
from nova.cmd.api import main
if __name__ == "__main__":
  sys.exit(main())

接着,分析如下:

/nova/cmd/api.py (文件位置為/usr/lib/python2.6/site-packages,此處及下面均省略)

def main():
    config.parse_args(sys.argv)
    logging.setup("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':
            """為了與ec2兼容"""
            server = service.WSGIService(api, use_ssl=should_use_ssl,
                                         max_url_len=16384) 
        else:
            server = service.WSGIService(api, use_ssl=should_use_ssl) 【1
        launcher.launch_service(server, workers=server.workers or 1)【2】
    """當前進程處於等待狀態"""
  launcher.wait()

 二、分析【1】處:主要是WSGIService對象的創建

/nova/service.py

WSGIService類:
def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
    self.name = name
    self.manager = self._get_manager()
    self.loader = loader or wsgi.Loader()
    self.app = self.loader.load_app(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' % name, None)
    self.use_ssl = use_ssl
    """封裝了wsgi.py中的server"""
    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

/nova/wsgi.py

Loader類:
def __init__(self, config_path=None):
  """試圖尋找api-paste.ini配置文件"""
  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):
  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 as err:
    LOG.error(err)
    raise exception.PasteAppNotFound(name=name, path=self.config_path)    

(此處關於app的具體加載過程見后文。)

三、分析【2】處:主要是WSGIService的啟動

/nova/openstack/common/service.py

ProcessLauncher類:
def launch_service(self, service, workers=1):
  wrap = ServiceWrapper(service, workers)

  LOG.info(_('Starting %d workers'), wrap.workers)
  """循環啟動workers數目的子進程"""
  while self.running and len(wrap.children) < wrap.workers:
    self._start_child(wrap)

def _start_child(self, wrap):
  ...
  """創建子進程"""
  pid = os.fork()
  if pid == 0:
            
    status = 0
    try:
      """子進程進行處理"""
      self._child_process(wrap.service)
    except SignalExit as exc:
      signame = {signal.SIGTERM: 'SIGTERM',
                    signal.SIGINT: 'SIGINT'}[exc.signo]
      LOG.info(_('Caught %s, exiting'), signame)
      status = exc.code
    except SystemExit as exc:
      status = exc.code
    except BaseException:
      LOG.exception(_('Unhandled exception'))
      status = 2
    finally:
      """出現異常停掉服務"""
      wrap.service.stop()
    """子進程退出"""
    os._exit(status)

  LOG.info(_('Started child %d'), pid)
  wrap.children.add(pid)
  self.children[pid] = wrap
  return pid



def _child_process(self, service):
  ...
  launcher = Launcher()
  launcher.run_service(service)

/nova/openstack/common/service.py

Launcher類:
def __init__(self):
    self._services = threadgroup.ThreadGroup()
    self.backdoor_port = eventlet_backdoor.initialize_if_enabled() 


@staticmethod
def run_service(service):
    service.start() service.wait()

由於這里的service是WSGIService對象,如下:

/nova/service.py

WSGIService類:
def start(self):
if self.manager: self.manager.init_host() self.manager.pre_start_hook() if self.backdoor_port is not None: self.manager.backdoor_port = self.backdoor_port self.server.start() if self.manager: self.manager.post_start_hook() def wait(self): self.server.wait()

/nova/wsgi.py

Server類:
def start(self):
    ...
    wsgi_kwargs = {
        'func': eventlet.wsgi.server,
        'sock': self._socket,
        'site': self.app,
        'protocol': self._protocol,
        'custom_pool': self._pool,
        'log': self._wsgi_logger,
        'log_format': CONF.wsgi_log_format
        }

    if self._max_url_len:
        wsgi_kwargs['url_length_limit'] = self._max_url_len
    """創建一個green thread,運行func函數,在其中傳入后面的參數。此處為調用eventlet.wsgi.server函數,傳入sock=self._socket等參數到server函數中。其中eventlet.wsgi.server函數負責啟動一個wsgi服務器程序接受客戶端發來的請求,
可以參考:http://eventlet.net/doc/modules/wsgi.html?highlight=wsgi.server#eventlet.wsgi.server
""" self._server = eventlet.spawn(**wsgi_kwargs) def wait(self): try: """green thread阻塞等待""" self._server.wait() except greenlet.GreenletExit: LOG.info(_("WSGI server has stopped."))

總結:

      我們可以通過pstree命令查看系統中進程的子進程數,發現nova-api進程中有一個主進程,其子進程數目為一個ec2,一個metadata,有workers數目的osapi_compute(其中workers可以通過/etc/nova/nova.conf中osapi_compute_workers選項設置)。然后通過ps –Lf查看每個進程的線程數目,發現其均為單線程。由此可以看出,整個server的創建和啟動過程就是,主進程產生若干的子進程,子進程使用green thread啟動wsgi server並等待服務。

參考文檔:

http://www.choudan.net/2013/12/09/OpenStack-WSGI-APP%E5%AD%A6%E4%B9%A0.html

http://developer.openstack.org/api-ref-compute-v2.html(nova的API文檔)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM