OpenStack cinder 源代码分析之cinder-api 服务启动

2.3 cinder-api 服务启动

2.3.1 WSGI Server

简单来说,python中的 WSGI Python 应用程序或框架与 Web 服务器之间的一种接口,它定义了一套接口来实现服务器与应用端的通信规范,它将 web 组件分为三类:

 

web 服务器(Service):接受客户端发来的 request,并返回 app 产生的 response 发回给客户端

web 应用程序(App: 每个 app 是一个 callable 对象。一个函数, 方法, , 或者实现了 __call__ 方法的实例对象都可以用来作为应用程序对象。服务器每次收到 http 客户端发来的请求都会调用相应的应用程序的 __call__ 方法去去处理。

web 中间件(Middleware):某些对象(app)可以在一些应用程序面前是服务器, 而从另一些服务器看来却是应用程序。可参考 http://k0s.org/mozilla/craft/middleware.html

 

1WSGI Server唯一的任务就是接收来自 client 的请求,然后将请求传给 application,最后将 application response 传递给 client

 

2)在使用 Middleware 的情况下,WSGI的处理模式为 WSGI Server -> (WSGI Middleware)* -> WSGI Application。其实 middleware 本身已是一个 app,只是你需要有一个 App 做为实现主要功能的 ApplicationMiddleware可以:

 

可以根据目的 URL 将一个请求分发 (routing) 给不同的应用程序对象, 并对 environ 做相应修改。这种 middleware 称为 router

允许多个应用程序或框架在同一个进程中一起运行。可以依次调用多个 middleware

通过分析 request,在网络上转发请求和响应, 进行负载均衡和远程处理.

response 内容进行后加工, 比如应用 XSL 样式.

3)在 Server Application 之间,可以使用若干个 Middleware 来处理 request response

 

 

 

具体过程描述如下:

 

A)服务器为 Applicaiton 实例化一个 WSGIService 实例,实例化过程中调用 Paste Deployment 来加载/注册各 middleware app

B)服务器启动 WSGIService 包括创建 socket,监听端口,然后等待客户端连接。

C)当有请求来时,服务器解析客户端信息放到环境变量 environ 中,并调用绑定的 handler 来处理请求。handler 解析这个 http 请求,将请求信息例如 methodpath 等放到 environ 中。wsgi handler还会将一些服务器端信息也放到 environ 中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ

wsgi handler 调用注册的各 app (包括 middleware来处理 request,比如 middleware 来进行数据检查、整理、用户验证等, wsgi app 生成 reponse header/status/body 回传给wsgi handler

D response 生成以后和回传之前,一些等待 response 被阻塞的 middleware 的方法可以对response 进行进一步的处理,比如添加新的数据等

最终 handler 通过socketresponse信息塞回给客户端

cinder-api 为例,其 WSGI 实例初始化和启动 service 的代码如下:

 

launcher = service.process_launcher()

server = service.WSGIService('osapi_volume') # 使用 Paste delopment 方式加载 osapi_volume 这个 application,这过程中包括加载各 middleware app

launcher.launch_service(server, workers=server.workers) launcher.wait() #真正启动service

2.3.2 Paste deploy

python中的WSGIWeb Server Gateway Interface)是Python应用程序或框架与Web服务器之间的一种接口,定义了一套借口来实现服务器与应用端的通信规范。按照一套规范,应用端想要通信,很简单,只需要实现一个接受两个参数的,含有__call__方法并返回一个可遍历的含有零个或者多个string结果的python对象。

服务端,对于每个http请求,调用一次应用端“注册”的那个协议规定应用必须实现的对象,然后返回相应的响应消息。 WSGI Server唯一的任务就是接收来自client的请求,然后将请求传给application,最后将applicationresponse传递给client。中间存在的一些东西,就需要中间件来处理。

Paste Deployment是用于发现和配置WSGI appliactionserver的系统。对于WSGI application,用户提供一个单独的函数(loadapp),用于从配置文件或者python egg中加载WSGI application。因为WSGI application提供了唯一的单独的简单的访问入口,所以application不需要暴露application的内部的实现细节。

Paste.deploy加载WSGI的应用十分简单,主要是使用paste.deploy.loadapp函数。

 

Paste deploy 是一种配置 WSGI Service app 的方法:

 

Spec文档: Paste Deployment http://pythonpaste.org/deploy/

Python paste deploy 的实现代码: https://bitbucket.org/ianb/pastedeploy

这篇文章也解释了 OpenStack 使用 Paste deployment 的方法。 http://www.choudan.net/2013/07/28/OpenStack-paste-deploy介绍.html

简单来说,它提供一个方法 loadapp,它可以用来从 config 配置文件或者 Python egg 文件加载 app(包括middleware/filterapp),它只要求 app 给它提供一个入口函数,该函数通过配置文件告诉Paste depoly loader。一个简单的调用 loadapp Python code 的例子:

 

from paste.deploy import loadapp

wsgi_app = loadapp('config:/path/to/config.ini')

/path/to/config.ini 文件:

[app:myapp]

paste.app_factory = myapp.modulename:factory

myapp.modulename:factory 的实现代码:

@classmethod

 def factory(cls, global_conf, **local_conf):

        """Factory method for paste.deploy."""

        return cls

OpenStack Paste deploy 使用的是各组件的 api-paste.ini 配置文件,比如 /etc/cinder/api-paste.ini

OpenStack WSGI app 是个实现了 __call__ 方法的类 class Router(object) 的一个实例对象。

OpenStack WSGI middleware 是个实现了 __call__ 方法的类 class Middleware(Application) 类的子类的实例。

Route 是用 Python 重新实现的 Rails routes system,它被用来做将 URL 映射为 App action,以及为 Appaction 产生 URL两个重要的方法:map.connect (定义映射规则) map.match (获取映射结果)。

2.3.3 cinder-api服务启动

Cinder cinder-api WSGIService 类型,其它服务是 Service 类型。这两个类的定义在文件 cinder/cinder/service.py 中。服务器启动 WSGIService 包括创建 socket,监听端口,然后等待客户端连接。

具体步骤如下:

1. 用户调用 cinder/bin/ 目录中的脚本来启动相关服务,比如 cinder-all 会启动cinder所有服务,cinder-api 会启动 cinder-api服务。以 cinder-api 为例,它会启动名为 osapi_volume WSGI Service

def main():

    objects.register_all()

    CONF(sys.argv[1:], project='cinder',

         version=version.version_string())

    logging.setup(CONF, "cinder")

    utils.monkey_patch()


    gmr.TextGuruMeditation.setup_autorun(version)


    rpc.init(CONF)

    launcher = service.process_launcher()

    server = service.WSGIService('osapi_volume') #初始化WSGIServiceload osapi_volume app

    launcher.launch_service(server, workers=server.workers)#启动该 WSGI service

    launcher.wait()

 

在执行 server = service.WSGIService('osapi_volume') 时,首先会 load app

class WSGIService(service.ServiceBase):

    """Provides ability to launch API from a 'paste' configuration."""


    def __init__(self, name, loader=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)

        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) or

                        processutils.get_worker_count())
 
#定义WSGI Server

self.server = wsgi.Server(name,

                          self.app,

                          host=self.host,

                          port=self.port)
 
 
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: `cinder.exception.PasteAppNotFound`


    """

    try:

        return deploy.loadapp("config:%s" % self.config_path, name=name) #从配置文件 paste-api.ini 中加载WSGI application

    except LookupError:

        LOG.exception(_LE("Error loading app %s"), name)

        raise exception.PasteAppNotFound(name=name, path=self.config_path)
 
 
执行 launcher.launch_service(server, workers=server.workers) 会启动该 service。它会绑定IP osapi_volume_listen = 0.0.0.0并在 osapi_volume_listen_port端口监听,并且可以启动 osapi_volume_workers=<None> worker线程。
 
 

2.3.4使用 Paste deploy 加载 osapi_volume 的过程

1 osapi-volume 服务启动的过程中,调用 deploy.loadapp 使用 config 方式从 paste-api.conf 文件来 load 名为osapi_volume 的应用,其入口在文件的 [composite:osapi_volume]部分:osapi_volume deploy.loadapp方法中传入的name



 

 

调用 cinder/api/__init__.py   root_app_factory 方法:local_conf 包括 /,/v1,/v2 三个pair

 

def root_app_factory(loader, global_conf, **local_conf):

    if CONF.enable_v1_api:

        LOG.warning(_LW('The v1 api is deprecated and will be removed in the '

                        'Liberty release. You should set enable_v1_api=false '

                        'and enable_v2_api=true in your cinder.conf file.'))

    else:

        del local_conf['/v1']

    if not CONF.enable_v2_api:

        del local_conf['/v2']

    return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)

 

def urlmap_factory(loader, global_conf, **local_conf):

    if 'not_found_app' in local_conf:

        not_found_app = local_conf.pop('not_found_app')

    else:

        not_found_app = global_conf.get('not_found_app')

    if not_found_app:

        not_found_app = loader.get_app(not_found_app, global_conf=global_conf)

    urlmap = URLMap(not_found_app=not_found_app)

    for path, app_name in local_conf.items():

        path = parse_path_expression(path)

        app = loader.get_app(app_name, global_conf=global_conf)

        urlmap[path] = app

    return urlmap

 

在该方法中,如果 cinder.conf enable_v1_api = False 则不加载 V1对应的app;如果 enable_v2_api = False 则不加载V2对应的app。否则三个都会被加载,所以在不需要使用 Cinder V1 API 的情况下,建议 disable V1 来节省 cinder-api 的启动时间和资源消耗。

加载 openstack_volume_api_v2,它对应的composite 是:

 

 

 

cinder/api/middleware/auth.py 中的 pipeline_factory 方法如下:

def pipeline_factory(loader, global_conf, **local_conf):

    """A paste pipeline replica that keys off of auth_strategy."""

    pipeline = local_conf[CONF.auth_strategy] #读取CONF.auth_strategy,其值默认为 token,再获取 'token' 对应的 pipeline

    pipeline = pipeline.split() # ['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext', 'apiv2']

    filters = [loader.get_filter(n) for n in pipeline[:-1]] #依次使用 deploy loader load 各个 filter

    app = loader.get_app(pipeline[-1]) #使用 deploy loader load app apiv2

    filters.reverse() #[<function _factory at 0x7fe44485ccf8>, <function auth_filter at 0x7fe44485cc80>, <function filter_ at 0x7fe43fe5f398>, <function _factory at 0x7fe43fe59ed8>, <function _factory at 0x7fe43fe595f0>, <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>]

    for filter in filters:

        app = filter(app) #然后依次调用每个 filter wrapper app

 return app

它首先会读取 cinder.conf auth_strategy 的值。我们一般会使用 token,所以它会从 local_conf 中读取keystone pipeline

 

 

 

 

A),loader.get_filterfilter)来使用 deploy loader load 每一个 filter,它会调用每个 filter 对应的入口 factory 函数,该函数在不同的 MiddleWare 基类中定义,都是返回函数 _factory 的指针(keystonemiddleware 函数auth_filter的指针)。比如:

 

Enter Middleware.factory with cls: <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>

Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.fault.FaultWrapper'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385

Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385

Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.auth.CinderKeystoneContext'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385

B),app = loader.get_app(pipeline[-1]):使用 deploy loader load app apiv2,调用入口函数 cinder.api.v2.router:APIRouter.factoryfactory 函数会调用其__init__方法,这方法里面会setup routessetup ext_routes setup extensions

 

 

C),app = filter(app):然后调用每个 filter wrapper APIRouter

 

依次调用 filter 类的 factory 方法或者 keystonemiddleware audit_filter方法,传入 app 做为参数。

keystonemiddleware为例,在 audit_filter 方法中,首先会初始化个 AuthProtocol 的实例,再调用其__init__ 函数,会设置 auth token 需要的一些环境,比如_signing_dirname_signing_cert_file_name 等。

 

 

到这里,osapi_volume loading 就结束了。在此过程中,各个 filter middleware 以及 WSGI Application 都被加载/注册,并被初始化。WSGI Server 在被启动后,开始等待 HTTP request,然后进入HTTP request 处理过程。

 

2.3.5 Cinder 中的资源、Controller 操作及管理

在进入具体的 load apiv2 application 过程之前,我们先来了解了解下 Cinder 里面的资源管理。

1 Cinder 的资源(Resource)类型

OpenStack 定义了两种类型的资源:

  • Core resource Resource: 核心资源。核心资源的定义文件在 /cinder/api/v2/ 目录下面。Cinder  的核心资源包括:volumestypessnapshotslimits等。
  • Extension resource: 扩展资源也是资源,使用同核心资源,只是它们的地位稍低。在 ./cinder/api/contrib/ 目录下面有很多文件,这些文件定义的都是扩展资源,例如 quotas.py 等。扩展资源又分为两种情况:
    • 一种扩展资源本身也是一种资源,只是没那么核心,比如 os-quota-sets。对扩展资源的访问方法同核心资源,比如 PUT /v2/2f07ad0f1beb4b629e42e1113196c04b/os-quota-sets/2f07ad0f1beb4b629e42e1113196c04b
    • 另一种扩展资源是对核心资源的扩展(Resource extension),包括对 action 的扩展和基本操作的扩展,现在的 Cinder 中只有对 Resource 基本操作的扩展,例如 SchedulerHints 是对 volumes 提供了扩展方法。一些扩展资源同时具备这两种功能。

Extension resource 的加载见下面。

Cinder 中的Extension resource 包括 extensionsos-hostsos-quota-setsencryptionbackupscgsnapshotsconsistencygroupsencryptionos-availability-zoneextra_specsos-volume-manageqos-specsos-quota-class-setsos-volume-transferos-servicesscheduler-stats

 

 

猜你喜欢

转载自chenyingkof.iteye.com/blog/2242645