searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

nova源代码阅读-nova-api启动过程

2023-05-23 04:28:43
125
0
       在OpenStack中,RESTful API是组件内部与外部进行信息交互的主要途径,即外部请求在到达组件请求后,首先会到达组件的API服务,在Nova中,nova-api是访问并使用Nova所提供的各种服务的公共接口。作为客户端和Nova的中间层,nova-api扮演了一个桥梁,或者说中间人的角色。nova-api把客户端的请求传达给Nova,待Nova处理完请求后再将处理结果返回给客户端。本小节将会对nova-api的关键知识点进行详细解析。

Nova-API服务的作用

       nova-api服务的主要作用是对外提供REST API服务,接收外部组件的请求并负责转发,当接收到外部请求后,nova-api首先对此请求进行认证,只有通过认证,它才会处理此请求或将此请求发送到消息队列中,供Nova内部的其他服务消费。nova-api的代码位于nova/api/目录下,其目录结构如下:
.
|-- auth.py
|-- compute_req_id.py
|-- ec2
|   |-- cloud.py
|   `-- ec2utils.py
|-- manager.py
|-- metadata
|   |-- base.py
|   |-- handler.py
|   |-- password.py
|   |-- vendordata.py
|   |-- vendordata_dynamic.py
|   |-- vendordata_json.py
|   `-- wsgi.py
|-- openstack
|   |-- api_version_request.py
|   |-- auth.py
|   |-- common.py
|   |-- compute
|   |-- identity.py
|   |-- requestlog.py
|   |-- urlmap.py
|   |-- versioned_method.py
|   |-- wsgi.py
|   `-- wsgi_app.py
|-- validation
|   |-- parameter_types.py
|   `-- validators.py
`-- wsgi.py
        metadata目录下对应的是Metadata API,这是提供给所创建的虚拟机来获得一些配置信息的API。openstack目录下对应的是Nova v2.1 API。nova-api是基于WSGI实现的,nova/api/openstack/目录下包含着WSGI基础架构的代码,其中包含一些Nova WSGI stack中需要的Middleware,以及如何解析请求与分发请求的核心代码。在nova/api/openstack/compute中可以找到对应每个API的入口点。当前nova-api使用JSON-Schema来验证输入,这些JSON-Schema都位于nova/api/openstack/schemas/目录下,并使用与相应API所在文件相同的模块名称。JSON-Schema的验证实现则位于nova/api/validateion/目录下。

Nova-API服务的启动流程

       nova-api作为Nova组件的入口,它在启动过程中会进行许多初始化的操作,如加载API、创建WSGI Server、加载策略等。可以了解到nova-api启动的是WSGI服务,因此先简单介绍一下WSGI。

WSGI

       Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。
       WSGI区分为两个部份:一为“服务器”或“网关”,另一为“应用程序”或“应用框架”。在处理一个WSGI请求时,服务器会为应用程序提供环境资讯及一个回呼函数(Callback Function)。当应用程序完成处理请求后,透过前述的回呼函数,将结果回传给服务器。所谓的 WSGI 中间件同时实现了API的两方,因此可以在WSGI服务和WSGI应用之间起调解作用:从WSGI服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。“中间件”组件可以执行以下功能:
  • 重写环境变量,根据目标URL,将请求消息路由到不同的应用对象。
  • 允许在一个进程中同时运行多个应用程序或应用框架。
  • 负载均衡和远程处理,通过在网络上转发请求和响应消息。
  • 进行内容后处理,例如应用XSLT样式表。
      WSGI将 web 组件分为三类: web服务器,web中间件,web应用程序, wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application 。
       上图中最上面的三个彩色框表示角色,中间的白色框表示操作,操作的发生顺序按照1 ~ 5进行了排序,我们直接对着上图来说明middleware是如何工作的:
  1. Server收到客户端的HTTP请求后,生成了environ_s,并且已经定义了start_response_s。
  2. Server调用Middleware的application对象,传递的参数是environ_s和start_response_s。
  3. Middleware会根据environ执行业务逻辑,生成environ_m,并且已经定义了start_response_m。
  4. Middleware决定调用Application的application对象,传递参数是environ_m和start_response_m。Application的application对象处理完成后,会调用start_response_m并且返回结果给Middleware,存放在result_m中。
  5. Middleware处理result_m,然后生成result_s,接着调用start_response_s,并返回结果result_s给Server端。Server端获取到result_s后就可以发送结果给客户端了。

WSGI Server/gateway

      wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。文字很难解释清楚wsgi server到底是什么东西,以及做些什么事情,最直观的方式还是看wsgi server的实现代码。

WSGI Application

       wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。

WSGI Middleware

       有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。
       对于OpenStack的所有组件而言,每学习一个新的组件,都可以从setup.cfg文件开始,这里被称为OpenStack组件的结构地图,从这个文件中可以找到脚本的入口:
console_scripts =
    nova-api = nova.cmd.api:main
    nova-api-metadata = nova.cmd.api_metadata:main
    nova-api-os-compute = nova.cmd.api_os_compute:main
    ......
       从上述代码可以看出,nova-api对应的代码时nova.cmd.api文件中的main()函数,即当启动nova-api服务时,首先调用的就是nova.cmd.api中的main()方法。
def main():
    # 读取用户配置信息,所有配置信息都位于/etc/nova/nova.conf
    config.parse_args(sys.argv)
    logging.setup(CONF, "nova")
    objects.register_all()
    gmr_opts.set_defaults(CONF)
    if 'osapi_compute' in CONF.enabled_apis:
        # NOTE(mriedem): This is needed for caching the nova-compute service
        # version.
        objects.Service.enable_min_version_cache()
    log = logging.getLogger(__name__)

    gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)

    launcher = service.process_launcher()
    started = 0
    for api in CONF.enabled_apis:
        should_use_ssl = api in CONF.enabled_ssl_apis
        try:
            # 声明一个WSGIService的实例,以API作为参数传入,api的值是用户
            # 通过enabled_apis设置的
            server = service.WSGIService(api, use_ssl=should_use_ssl)
            # 通过launch_service()启动WSGI Server
            launcher.launch_service(server, workers=server.workers or 1)
            started += 1
        except exception.PasteAppNotFound as ex:
            log.warning("%s. ``enabled_apis`` includes bad values. "
                        "Fix to remove this warning.", ex)

    if started == 0:
        log.error('No APIs were started. '
                  'Check the enabled_apis config option.')
        sys.exit(1)

    launcher.wait()
     nova-api启动的简单时序图如下所示:
       以上就是服务启动的一个过程,解析来对方法中比较重要的代码进行分析。
1. 加载配置项
这一部分是通过config.parse_args(sys.argv)实现的,这个方法的具体实现代码位于nova/config.py中,如下所示:
def parse_args(argv, default_config_files=None, configure_db=True,
               init_rpc=True):
    log.register_options(CONF)
    # We use the oslo.log default log levels which includes suds=INFO
    # and add only the extra levels that Nova needs
    if CONF.glance.debug:
        extra_default_log_levels = ['glanceclient=DEBUG']
    else:
        extra_default_log_levels = ['glanceclient=WARN']

    # NOTE(danms): DEBUG logging in privsep will result in some large
    # and potentially sensitive things being logged.
    extra_default_log_levels.append('oslo.privsep.daemon=INFO')

    log.set_defaults(default_log_levels=log.get_default_log_levels() +
                     extra_default_log_levels)
    rpc.set_defaults(control_exchange='nova')
    if profiler:
        profiler.set_defaults(CONF)
    middleware.set_defaults()

    CONF(argv[1:],
         project='nova',
         version=version.version_string(),
         default_config_files=default_config_files)

    if init_rpc:
        rpc.init(CONF)

    if configure_db:
        sqlalchemy_api.configure(CONF)
       这里会加载用户配置的参数,然后设置RPC中的Exchange,这里启动的是Nova的服务,因此control_exchange的值为nova。然后在初始化一个RPC Server,创建TRANSPORT和NOTIFIER,前者是一个Transport对象的工厂方法,用于RPC Server和RPC Client间的通信;后者用于发送通知消息,可以通过以下方式将它与一个RPC的TRANSPORT进行关联:
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
    conf, allowed_remote_exmods=exmods)
LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
                                 serializer=serializer)
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
                              serializer=serializer, driver='noop')
     代码中指定了notifier的发送通道(transport)、notifier中的filed(serializer)及消息发送时需要用到的driver等。
2.定义Service Launcher
      通过launcher = service.process_launcher()创建一个ProcessLauncher的对象实例,代码位于/usr/lib/python2.7/site-packages/oslo_service/service.py,具体代码如下:
class ProcessLauncher(object):
    """Launch a service with a given number of workers."""

    def __init__(self, conf, wait_interval=0.01, restart_method='reload'):
        ......
        self.conf = conf
        conf.register_opts(_options.service_opts)
        self.children = {}
        self.sigcaught = None
        self.running = True
        self.wait_interval = wait_interval
        self.launcher = None
        rfd, self.writepipe = os.pipe()
        self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r')
        self.signal_handler = SignalHandler()
        self.handle_signal()
        self.restart_method = restart_method
        if restart_method not in _LAUNCHER_RESTART_METHODS:
            raise ValueError(_("Invalid restart_method: %s") % restart_method)
3.定义WSGIService实例
     这里只是定义一个WSGIService的实例,并没有启动它。从这个类中的成员变量可以看出,一个WSGIService实例中比较重要的几个属性有self.binary、self.topic、self.manager、self.app、self.workers和self.server。其中,self.binary就是nova-api,self.app实际上是一个WSGI Application,self.workers指定了需要启动的nova-api的数量,它的值是从nova.conf中获取的,如果用户没有指定workers的值,则默认取CPU的核数作为worker的值。self.server是一个WSGI的Server实例,所以在Nova中,只有nova-api才是WSGI的服务,其他服务都不是。
class WSGIService(service.Service):
    """Provides ability to launch API from a 'paste' configuration."""

    def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
        self.name = name
        # NOTE(danms): Name can be metadata, osapi_compute, per
        # nova.service's enabled_apis
        self.binary = 'nova-%s' % name

        LOG.warning('Running %s using eventlet is deprecated. Deploy with '
                    'a WSGI server such as uwsgi or mod_wsgi.', self.binary)

        self.topic = None
        self.manager = self._get_manager()
        self.loader = loader or api_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
        setup_profiler(name, self.host)
再具体看一下wsgi.Server(),它位于nova/nova/wsgi.py。Server类来管理WSGI Server,服务于WSGI Application,会通过self.pool_size指定Pool的大小,然后使用self.pool_size作为参数定义一个GreenPool()实例。在Python中,实际上是没有多线程这一概念的,如果用户需要实现类似功能,需要借助绿色线程来实现,这里Eventlet是对绿色线程的一个封装,主要功能是实现Python中的多线程,进而实现并发处理。在WSGIServer启动时,会对线程进行设置。
class Server(service.ServiceBase):
    """Server class to manage a WSGI server, serving a WSGI application."""

    default_pool_size = CONF.wsgi.default_pool_size

    def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
                       protocol=eventlet.wsgi.HttpProtocol, backlog=128,
                       use_ssl=False, max_url_len=None):
        # Allow operators to customize http requests max header line size.
        eventlet.wsgi.MAX_HEADER_LINE = CONF.wsgi.max_header_line
        self.name = name
        self.app = app
        self._server = None
        self._protocol = protocol
        self.pool_size = pool_size or self.default_pool_size
        self._pool = eventlet.GreenPool(self.pool_size)
        self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name)
        self._use_ssl = use_ssl
        self._max_url_len = max_url_len
        self.client_socket_timeout = CONF.wsgi.client_socket_timeout or None

        if backlog < 1:
            raise exception.InvalidInput(
                    reason=_('The backlog must be more than 0'))

        bind_addr = (host, port)
        try:
            info = socket.getaddrinfo(bind_addr[0],
                                      bind_addr[1],
                                      socket.AF_UNSPEC,
                                      socket.SOCK_STREAM)[0]
            family = info[0]
            bind_addr = info[-1]
        except Exception:
            family = socket.AF_INET

        try:
            self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
        except EnvironmentError:
            LOG.error(_LE("Could not bind to %(host)s:%(port)s"),
                      {'host': host, 'port': port})
            raise

        (self.host, self.port) = self._socket.getsockname()[0:2]
4.启动WSGI Server
       通过执行以下代码,将WSGIService实例启动起来:
launcher.launch_service(server, workers=server.workers or 1)
       在launch_service()中,根据传递的workers数量启动指定数量的子进程服务,。
def launch_service(self, service, workers=1):
    _check_service_base(service)
    wrap = ServiceWrapper(service, workers)
    if hasattr(gc, 'freeze'):
        gc.freeze()

    LOG.info('Starting %d workers', wrap.workers)
    while self.running and len(wrap.children) < wrap.workers:
        self._start_child(wrap)
5.等待WSGI Server启动
最后一步,就是等待nova-api 的WSGI Server启动:
launcher.wait()
循环等待子服务是否死亡并根据需要重新启动
def wait(self):
    """Loop waiting on children to die and respawning as necessary."""

    systemd.notify_once()
    if self.conf.log_options:
        LOG.debug('Full set of CONF:')
        self.conf.log_opt_values(LOG, logging.DEBUG)

    try:
        while True:
            self.handle_signal()
            self._respawn_children()
            # No signal means that stop was called.  Don't clean up here.
            if not self.sigcaught:
                return

            signame = self.signal_handler.signals_to_name[self.sigcaught]
            LOG.info('Caught %s, stopping children', signame)
            if not _is_sighup_and_daemon(self.sigcaught):
                break

            child_signal = signal.SIGTERM
            if self.restart_method == 'reload':
                self.conf.reload_config_files()
            elif self.restart_method == 'mutate':
                self.conf.mutate_config_files()
                child_signal = signal.SIGHUP
            for service in set(
                    [wrap.service for wrap in self.children.values()]):
                service.reset()

            for pid in self.children:
                os.kill(pid, child_signal)

            self.running = True
            self.sigcaught = None
    except eventlet.greenlet.GreenletExit:
        LOG.info("Wait called after thread killed. Cleaning up.")

    # if we are here it means that we are trying to do graceful shutdown.
    # add alarm watching that graceful_shutdown_timeout is not exceeded
    if (self.conf.graceful_shutdown_timeout and
            self.signal_handler.is_signal_supported('SIGALRM')):
        signal.alarm(self.conf.graceful_shutdown_timeout)

    self.stop()
       至此,Nova中的服务都是通过Systemd管理的,在Systemd中nova-api服务的名字为devstack@n-api.service,与之相应的脚本内容如下:
[Unit]
Description = Devstack devstack@n-api.service

[Service]
RestartForceExitStatus = 100
NotifyAccess = all
Restart = always
KillMode = process
Type = notify
ExecReload = /bin/kill -HUP $MAINPID
ExecStart = /usr/bin/uwsgi --procname-prefix nova-api --ini /etc/nova/nova-api-uwsgi.ini
User = stack
SyslogIdentifier = devstack@n-api.service

[Install]
WantedBy = multi-user.target
        在Systemd中通过systemctl status devstack@n-api.service可以查看nova-api服务的运行状态,下图显示nova-api的WSGI服务启动成功。
0条评论
0 / 1000
罗****兵
3文章数
1粉丝数
罗****兵
3 文章 | 1 粉丝
罗****兵
3文章数
1粉丝数
罗****兵
3 文章 | 1 粉丝
原创

nova源代码阅读-nova-api启动过程

2023-05-23 04:28:43
125
0
       在OpenStack中,RESTful API是组件内部与外部进行信息交互的主要途径,即外部请求在到达组件请求后,首先会到达组件的API服务,在Nova中,nova-api是访问并使用Nova所提供的各种服务的公共接口。作为客户端和Nova的中间层,nova-api扮演了一个桥梁,或者说中间人的角色。nova-api把客户端的请求传达给Nova,待Nova处理完请求后再将处理结果返回给客户端。本小节将会对nova-api的关键知识点进行详细解析。

Nova-API服务的作用

       nova-api服务的主要作用是对外提供REST API服务,接收外部组件的请求并负责转发,当接收到外部请求后,nova-api首先对此请求进行认证,只有通过认证,它才会处理此请求或将此请求发送到消息队列中,供Nova内部的其他服务消费。nova-api的代码位于nova/api/目录下,其目录结构如下:
.
|-- auth.py
|-- compute_req_id.py
|-- ec2
|   |-- cloud.py
|   `-- ec2utils.py
|-- manager.py
|-- metadata
|   |-- base.py
|   |-- handler.py
|   |-- password.py
|   |-- vendordata.py
|   |-- vendordata_dynamic.py
|   |-- vendordata_json.py
|   `-- wsgi.py
|-- openstack
|   |-- api_version_request.py
|   |-- auth.py
|   |-- common.py
|   |-- compute
|   |-- identity.py
|   |-- requestlog.py
|   |-- urlmap.py
|   |-- versioned_method.py
|   |-- wsgi.py
|   `-- wsgi_app.py
|-- validation
|   |-- parameter_types.py
|   `-- validators.py
`-- wsgi.py
        metadata目录下对应的是Metadata API,这是提供给所创建的虚拟机来获得一些配置信息的API。openstack目录下对应的是Nova v2.1 API。nova-api是基于WSGI实现的,nova/api/openstack/目录下包含着WSGI基础架构的代码,其中包含一些Nova WSGI stack中需要的Middleware,以及如何解析请求与分发请求的核心代码。在nova/api/openstack/compute中可以找到对应每个API的入口点。当前nova-api使用JSON-Schema来验证输入,这些JSON-Schema都位于nova/api/openstack/schemas/目录下,并使用与相应API所在文件相同的模块名称。JSON-Schema的验证实现则位于nova/api/validateion/目录下。

Nova-API服务的启动流程

       nova-api作为Nova组件的入口,它在启动过程中会进行许多初始化的操作,如加载API、创建WSGI Server、加载策略等。可以了解到nova-api启动的是WSGI服务,因此先简单介绍一下WSGI。

WSGI

       Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。
       WSGI区分为两个部份:一为“服务器”或“网关”,另一为“应用程序”或“应用框架”。在处理一个WSGI请求时,服务器会为应用程序提供环境资讯及一个回呼函数(Callback Function)。当应用程序完成处理请求后,透过前述的回呼函数,将结果回传给服务器。所谓的 WSGI 中间件同时实现了API的两方,因此可以在WSGI服务和WSGI应用之间起调解作用:从WSGI服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。“中间件”组件可以执行以下功能:
  • 重写环境变量,根据目标URL,将请求消息路由到不同的应用对象。
  • 允许在一个进程中同时运行多个应用程序或应用框架。
  • 负载均衡和远程处理,通过在网络上转发请求和响应消息。
  • 进行内容后处理,例如应用XSLT样式表。
      WSGI将 web 组件分为三类: web服务器,web中间件,web应用程序, wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application 。
       上图中最上面的三个彩色框表示角色,中间的白色框表示操作,操作的发生顺序按照1 ~ 5进行了排序,我们直接对着上图来说明middleware是如何工作的:
  1. Server收到客户端的HTTP请求后,生成了environ_s,并且已经定义了start_response_s。
  2. Server调用Middleware的application对象,传递的参数是environ_s和start_response_s。
  3. Middleware会根据environ执行业务逻辑,生成environ_m,并且已经定义了start_response_m。
  4. Middleware决定调用Application的application对象,传递参数是environ_m和start_response_m。Application的application对象处理完成后,会调用start_response_m并且返回结果给Middleware,存放在result_m中。
  5. Middleware处理result_m,然后生成result_s,接着调用start_response_s,并返回结果result_s给Server端。Server端获取到result_s后就可以发送结果给客户端了。

WSGI Server/gateway

      wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。文字很难解释清楚wsgi server到底是什么东西,以及做些什么事情,最直观的方式还是看wsgi server的实现代码。

WSGI Application

       wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。

WSGI Middleware

       有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。
       对于OpenStack的所有组件而言,每学习一个新的组件,都可以从setup.cfg文件开始,这里被称为OpenStack组件的结构地图,从这个文件中可以找到脚本的入口:
console_scripts =
    nova-api = nova.cmd.api:main
    nova-api-metadata = nova.cmd.api_metadata:main
    nova-api-os-compute = nova.cmd.api_os_compute:main
    ......
       从上述代码可以看出,nova-api对应的代码时nova.cmd.api文件中的main()函数,即当启动nova-api服务时,首先调用的就是nova.cmd.api中的main()方法。
def main():
    # 读取用户配置信息,所有配置信息都位于/etc/nova/nova.conf
    config.parse_args(sys.argv)
    logging.setup(CONF, "nova")
    objects.register_all()
    gmr_opts.set_defaults(CONF)
    if 'osapi_compute' in CONF.enabled_apis:
        # NOTE(mriedem): This is needed for caching the nova-compute service
        # version.
        objects.Service.enable_min_version_cache()
    log = logging.getLogger(__name__)

    gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)

    launcher = service.process_launcher()
    started = 0
    for api in CONF.enabled_apis:
        should_use_ssl = api in CONF.enabled_ssl_apis
        try:
            # 声明一个WSGIService的实例,以API作为参数传入,api的值是用户
            # 通过enabled_apis设置的
            server = service.WSGIService(api, use_ssl=should_use_ssl)
            # 通过launch_service()启动WSGI Server
            launcher.launch_service(server, workers=server.workers or 1)
            started += 1
        except exception.PasteAppNotFound as ex:
            log.warning("%s. ``enabled_apis`` includes bad values. "
                        "Fix to remove this warning.", ex)

    if started == 0:
        log.error('No APIs were started. '
                  'Check the enabled_apis config option.')
        sys.exit(1)

    launcher.wait()
     nova-api启动的简单时序图如下所示:
       以上就是服务启动的一个过程,解析来对方法中比较重要的代码进行分析。
1. 加载配置项
这一部分是通过config.parse_args(sys.argv)实现的,这个方法的具体实现代码位于nova/config.py中,如下所示:
def parse_args(argv, default_config_files=None, configure_db=True,
               init_rpc=True):
    log.register_options(CONF)
    # We use the oslo.log default log levels which includes suds=INFO
    # and add only the extra levels that Nova needs
    if CONF.glance.debug:
        extra_default_log_levels = ['glanceclient=DEBUG']
    else:
        extra_default_log_levels = ['glanceclient=WARN']

    # NOTE(danms): DEBUG logging in privsep will result in some large
    # and potentially sensitive things being logged.
    extra_default_log_levels.append('oslo.privsep.daemon=INFO')

    log.set_defaults(default_log_levels=log.get_default_log_levels() +
                     extra_default_log_levels)
    rpc.set_defaults(control_exchange='nova')
    if profiler:
        profiler.set_defaults(CONF)
    middleware.set_defaults()

    CONF(argv[1:],
         project='nova',
         version=version.version_string(),
         default_config_files=default_config_files)

    if init_rpc:
        rpc.init(CONF)

    if configure_db:
        sqlalchemy_api.configure(CONF)
       这里会加载用户配置的参数,然后设置RPC中的Exchange,这里启动的是Nova的服务,因此control_exchange的值为nova。然后在初始化一个RPC Server,创建TRANSPORT和NOTIFIER,前者是一个Transport对象的工厂方法,用于RPC Server和RPC Client间的通信;后者用于发送通知消息,可以通过以下方式将它与一个RPC的TRANSPORT进行关联:
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
    conf, allowed_remote_exmods=exmods)
LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
                                 serializer=serializer)
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
                              serializer=serializer, driver='noop')
     代码中指定了notifier的发送通道(transport)、notifier中的filed(serializer)及消息发送时需要用到的driver等。
2.定义Service Launcher
      通过launcher = service.process_launcher()创建一个ProcessLauncher的对象实例,代码位于/usr/lib/python2.7/site-packages/oslo_service/service.py,具体代码如下:
class ProcessLauncher(object):
    """Launch a service with a given number of workers."""

    def __init__(self, conf, wait_interval=0.01, restart_method='reload'):
        ......
        self.conf = conf
        conf.register_opts(_options.service_opts)
        self.children = {}
        self.sigcaught = None
        self.running = True
        self.wait_interval = wait_interval
        self.launcher = None
        rfd, self.writepipe = os.pipe()
        self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r')
        self.signal_handler = SignalHandler()
        self.handle_signal()
        self.restart_method = restart_method
        if restart_method not in _LAUNCHER_RESTART_METHODS:
            raise ValueError(_("Invalid restart_method: %s") % restart_method)
3.定义WSGIService实例
     这里只是定义一个WSGIService的实例,并没有启动它。从这个类中的成员变量可以看出,一个WSGIService实例中比较重要的几个属性有self.binary、self.topic、self.manager、self.app、self.workers和self.server。其中,self.binary就是nova-api,self.app实际上是一个WSGI Application,self.workers指定了需要启动的nova-api的数量,它的值是从nova.conf中获取的,如果用户没有指定workers的值,则默认取CPU的核数作为worker的值。self.server是一个WSGI的Server实例,所以在Nova中,只有nova-api才是WSGI的服务,其他服务都不是。
class WSGIService(service.Service):
    """Provides ability to launch API from a 'paste' configuration."""

    def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
        self.name = name
        # NOTE(danms): Name can be metadata, osapi_compute, per
        # nova.service's enabled_apis
        self.binary = 'nova-%s' % name

        LOG.warning('Running %s using eventlet is deprecated. Deploy with '
                    'a WSGI server such as uwsgi or mod_wsgi.', self.binary)

        self.topic = None
        self.manager = self._get_manager()
        self.loader = loader or api_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
        setup_profiler(name, self.host)
再具体看一下wsgi.Server(),它位于nova/nova/wsgi.py。Server类来管理WSGI Server,服务于WSGI Application,会通过self.pool_size指定Pool的大小,然后使用self.pool_size作为参数定义一个GreenPool()实例。在Python中,实际上是没有多线程这一概念的,如果用户需要实现类似功能,需要借助绿色线程来实现,这里Eventlet是对绿色线程的一个封装,主要功能是实现Python中的多线程,进而实现并发处理。在WSGIServer启动时,会对线程进行设置。
class Server(service.ServiceBase):
    """Server class to manage a WSGI server, serving a WSGI application."""

    default_pool_size = CONF.wsgi.default_pool_size

    def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
                       protocol=eventlet.wsgi.HttpProtocol, backlog=128,
                       use_ssl=False, max_url_len=None):
        # Allow operators to customize http requests max header line size.
        eventlet.wsgi.MAX_HEADER_LINE = CONF.wsgi.max_header_line
        self.name = name
        self.app = app
        self._server = None
        self._protocol = protocol
        self.pool_size = pool_size or self.default_pool_size
        self._pool = eventlet.GreenPool(self.pool_size)
        self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name)
        self._use_ssl = use_ssl
        self._max_url_len = max_url_len
        self.client_socket_timeout = CONF.wsgi.client_socket_timeout or None

        if backlog < 1:
            raise exception.InvalidInput(
                    reason=_('The backlog must be more than 0'))

        bind_addr = (host, port)
        try:
            info = socket.getaddrinfo(bind_addr[0],
                                      bind_addr[1],
                                      socket.AF_UNSPEC,
                                      socket.SOCK_STREAM)[0]
            family = info[0]
            bind_addr = info[-1]
        except Exception:
            family = socket.AF_INET

        try:
            self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
        except EnvironmentError:
            LOG.error(_LE("Could not bind to %(host)s:%(port)s"),
                      {'host': host, 'port': port})
            raise

        (self.host, self.port) = self._socket.getsockname()[0:2]
4.启动WSGI Server
       通过执行以下代码,将WSGIService实例启动起来:
launcher.launch_service(server, workers=server.workers or 1)
       在launch_service()中,根据传递的workers数量启动指定数量的子进程服务,。
def launch_service(self, service, workers=1):
    _check_service_base(service)
    wrap = ServiceWrapper(service, workers)
    if hasattr(gc, 'freeze'):
        gc.freeze()

    LOG.info('Starting %d workers', wrap.workers)
    while self.running and len(wrap.children) < wrap.workers:
        self._start_child(wrap)
5.等待WSGI Server启动
最后一步,就是等待nova-api 的WSGI Server启动:
launcher.wait()
循环等待子服务是否死亡并根据需要重新启动
def wait(self):
    """Loop waiting on children to die and respawning as necessary."""

    systemd.notify_once()
    if self.conf.log_options:
        LOG.debug('Full set of CONF:')
        self.conf.log_opt_values(LOG, logging.DEBUG)

    try:
        while True:
            self.handle_signal()
            self._respawn_children()
            # No signal means that stop was called.  Don't clean up here.
            if not self.sigcaught:
                return

            signame = self.signal_handler.signals_to_name[self.sigcaught]
            LOG.info('Caught %s, stopping children', signame)
            if not _is_sighup_and_daemon(self.sigcaught):
                break

            child_signal = signal.SIGTERM
            if self.restart_method == 'reload':
                self.conf.reload_config_files()
            elif self.restart_method == 'mutate':
                self.conf.mutate_config_files()
                child_signal = signal.SIGHUP
            for service in set(
                    [wrap.service for wrap in self.children.values()]):
                service.reset()

            for pid in self.children:
                os.kill(pid, child_signal)

            self.running = True
            self.sigcaught = None
    except eventlet.greenlet.GreenletExit:
        LOG.info("Wait called after thread killed. Cleaning up.")

    # if we are here it means that we are trying to do graceful shutdown.
    # add alarm watching that graceful_shutdown_timeout is not exceeded
    if (self.conf.graceful_shutdown_timeout and
            self.signal_handler.is_signal_supported('SIGALRM')):
        signal.alarm(self.conf.graceful_shutdown_timeout)

    self.stop()
       至此,Nova中的服务都是通过Systemd管理的,在Systemd中nova-api服务的名字为devstack@n-api.service,与之相应的脚本内容如下:
[Unit]
Description = Devstack devstack@n-api.service

[Service]
RestartForceExitStatus = 100
NotifyAccess = all
Restart = always
KillMode = process
Type = notify
ExecReload = /bin/kill -HUP $MAINPID
ExecStart = /usr/bin/uwsgi --procname-prefix nova-api --ini /etc/nova/nova-api-uwsgi.ini
User = stack
SyslogIdentifier = devstack@n-api.service

[Install]
WantedBy = multi-user.target
        在Systemd中通过systemctl status devstack@n-api.service可以查看nova-api服务的运行状态,下图显示nova-api的WSGI服务启动成功。
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0