深入理解nova api服务

一、wsgi简介

在构建 Web 应用时,通常会有 Web Server 和 Application Server 两种角色。其中 Web Server 主要负责接受来自用户的请求,解析 HTTP 协议,并将请求转发给 Application Server,Application Server 主要负责处理用户的请求,并将处理的结果返回给 Web Server,最终 Web Server 将结果返回给用户。

由于有很多动态语言和很多种 Web Server,他们彼此之间互不兼容,给程序员造成了很大的麻烦。

WSGI是一种 web server or gateway 和 python web application or framework 之间简单通用的接口,符合这种接口的 application 可运行在所有符合该接口的 server 上。通俗的讲,WSGI 规范了一种简单的接口,解耦了 server 和 application,使得双边的开发者更加专注自身特性的开发。

WSGI 标准中主要定义了两种角色:

  • “server” 或 “gateway” 端
  • “application” 或 “framework” 端

“application” 或 “framework” 端

Application/framework 端必须定义一个 callable object,callable object 可以是以下三者之一:

  • function, method
  • class
  • instance with a call method

Callable object 必须满足以下两个条件:

  • 接受两个参数:字典(environ),回调函数(start_response,返回 HTTP status,headers 给 web server)
  • 返回一个可迭代的值

一个简单的callable object 如下所示:

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello World']

“server” 或 “gateway” 端

Server/gateway 端主要专注 HTTP 层面的业务,重点是接收 HTTP 请求和提供并发。每当收到 HTTP 请求,server/gateway 必须调用 callable object:

  • 接收 HTTP 请求,但是不关心 HTTP url, HTTP method 等
  • 为 environ 提供必要的参数,实现一个回调函数
  • start_response,并传给 callable object
    调用 callable object

一个简单的例子如下所示:

# server/gateway side
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 8080, application)
    server.serve_forever()

二、Paste Deployment简介

paste deployment是一个配置WSGI APP和发现服务的一个系统,对于WSGI APP的开发者,它提供了一个简单的函数(loadapp),以便从配置文件和python egg中加载一个wsgi app,对于提供的WSGI APP,它只请求一个简单的入口,因此你不需要提供你的app详情给它。

通俗来讲:可以将它理解成是一种机制或者流程模式,在一个Server中,可以通过他将server中的app通过它进行连接组合。其中的连接方式他通过一个简单的接口提供给用户,用户只需要配置好paste deployment即可完成app的连接组合,这对用户完全是透明的。

与paste deployment的主要交互方式是配置文件,让整体server中app的流程处理按照配置文件中的流向去运行。

配置文件中由多个sections(段)组成,每个sections是由[type:name]这样的格式组成,如果不是这样的格式,将会被忽略

2.1 解读nova中api-paste.ini

/etc/nova/api-paste.ini的部分内容如下:


#############
# OpenStack #
#############

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v2.1: openstack_compute_api_v21
/v3: openstack_compute_api_v3
这里利用composite将xxx/xxx形式的请求交给oscomputeversions,形似xxxx/v1.1/xxxxx请求交给openstack_compute_api_v2,来实现API版本控制


[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
noauth2 = compute_req_id faultwrap sizelimit noauth2 ratelimit osapi_compute_app_v2
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
针对openstack_compute_api_v2的实现来看,首先调用了nova.api.auth中pipeline_factory方法,从源代码可以看出,实际上_load_pipeline调用了keystone,
keystone属于是一个filter的集合,请求会依次通过前面的这些filter,最后到达osapi_compute_app_v2这个app

[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth = compute_req_id faultwrap sizelimit noauth osapi_compute_app_v21
noauth2 = compute_req_id faultwrap sizelimit noauth2 osapi_compute_app_v21
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21

[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v21
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
noauth2 = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3

[filter:request_id]
paste.filter_factory = oslo.middleware:RequestId.factory

[filter:compute_req_id]
paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory

[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory

[filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareOld.factory

[filter:noauth2]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory

[filter:noauth_v3]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory

[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory

[filter:sizelimit]
paste.filter_factory = oslo.middleware:RequestBodySizeLimiter.factory

[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
该app直接调用了nova.api.openstack.compute中的APIRouter类中的factory函数。

[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory

[pipeline:oscomputeversions]
pipeline = faultwrap oscomputeversionapp

[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory

##########
# Shared #
##########

[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

从上面来看在OpenStack中nova-api的请求流程:依次经过filter[compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit],最后到达osapi_compute_app_v2这个app。

三、Nova API服务的启动

3.1 WSGI Server

Nova-api(nova/cmd/api.py) 服务启动时,初始化 nova/wsgi.py 中的类 Server,建立了 socket 监听 IP 和端口,再由 eventlet.spawn 和 eventlet.wsgi.server 创建 WSGI server:

class Server(object):
    def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
        ...
        eventlet.wsgi.MAX_HEADER_LINE = CONF.max_header_line
        self.name = name
        self.app = app
        self._server = None
        bind_addr = (host, port)
        ...
        # 建立 socket,监听 IP 和端口
        self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
    def start(self):
        ...
        # 构建所需参数
        wsgi_kwargs = {
            'func': eventlet.wsgi.server,
            'sock': dup_socket,
            'site': self.app,
            'protocol': self._protocol,
            'custom_pool': self._pool,
            'log': self._wsgi_logger,
            'log_format': CONF.wsgi_log_format,
            'debug': False,
            'keepalive': CONF.wsgi_keep_alive,
            'socket_timeout': self.client_socket_timeout
            }

        if self._max_url_len:
            wsgi_kwargs['url_length_limit'] = self._max_url_len
        # 由 eventlet.sawn 启动 server
        self._server = eventlet.spawn(**wsgi_kwargs)
        

3.2 Application Side & Middlewar

nova/wsgi.py的init方法中加载了Loader类,Application 的加载就是由它完成的,Loader 的 load_app 方法调用了 paste.deploy.loadapp 加载了 WSGI 的配置文件 /etc/nova/api-paste.ini:

class Loader(object):
    """Used to load WSGI applications from paste configurations."""

    def __init__(self, config_path=None):

        # 获取 WSGI 配置文件的路径
        self.config_path = config_path or CONF.api_paste_config

    def load_app(self, name):

        # paste.deploy 读取配置文件并加载该配置
        return paste.deploy.loadapp("config:%s" % self.config_path, name=name)

前面的api-paste.ini提到过,nova-api请求会到osapi_compute_app_v2这个app,根据api-paste.ini里面的定义:

[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory

nova/api/openstack/compute/__init__.py下APIRouter类中的factory方法继承自nova.api.openstack.APIRouter,factory方法创建了一个APIRouter对象,在APIRouter的__init__方法中调用了_setup_routes方法。其方法定义如下:

#nova/api/openstack/compute/__init__.py
class APIRouter(nova.api.openstack.APIRouter):
    def _setup_routes(self, mapper, ext_mgr, init_only):
        ...
        if init_only is None or 'consoles' in init_only:
            self.resources['consoles'] = consoles.create_resource()
            mapper.resource("console", "consoles",
                        controller=self.resources['consoles'],
                        parent_resource=dict(member_name='server',
                        collection_name='servers'))

        if init_only is None or 'consoles' in init_only or \
                'servers' in init_only or 'ips' in init_only:
            #ext_mgr是一个ExtensionManager对象,定义在nova/api/openstack/compute/extensions.py
            self.resources['servers'] = servers.create_resource(ext_mgr)
            mapper.resource("server", "servers",
                            controller=self.resources['servers'],
                            collection={'detail': 'GET'},
                            member={'action': 'POST'})
        ...
        
#nova/api/openstack/compute/consoles.py
def create_resource():
    #console资源的controller对象是一个wsgi.Resource对象
    return wsgi.Resource(Controller())
    

在_setup_routes方法中定义了许多nova资源的url映射,以上代码中给出了console,server资源的url映射。可以看到,对于每种资源,都是先调用资源的create_resource方法。返回一个resource对象。然后再调用mapper.resource添加资源的url.

以server资源为例:

mapper.resource("server", "servers",
                            controller=self.resources['servers'],
                            collection={'detail': 'GET'},
                            member={'action': 'POST'})

为了让代码更加简洁,mapper.resource将一些最基本的url映射封装了起来,即它已经封装了一些PUT,GET,DELETE之类的常见方法映射。
mapper.resource的参数含义为:server是成员名,servers是集合名,collection参数指定额外的集合操作,member指定额外的成员操作。
上面的代码的含义是,在最基本的url映射方法的基础上,额外添加了如下url映射:

map.connect("servers","/servers/detail",controller=self.resources['servers'],action="detail",conditions=dict(method=["GET"]))
map.connect("server","/server/{id}/action",controller=self.resources['servers'],action="action",conditions=dict(method=["POST"]))

总结

概括一下nova api的大致启动流程:

1.执行nova/cmd/api.py中main函数,解析参数,设置日志,启动WSGIService
2.WSGI的服务启动过程中,主要涉及WSGIService的初始化(init)和启动(start)方法
3.init方法会根据/etc/nova/api-paste.ini中的Paste规则,通过wsgiLoader来load_app
4.start方法通过 eventlet.spawn 和 eventlet.wsgi.server 创建 WSGI server

wsgi的application端和server端的实现:

server端通过Eventlet来实现。通过Paste deployment 配置来发现和配置 WSGI server 和 application 。

application端需要实现实现一个factory的类方法,收到HTTP请求时,会调用call方法,返回response.

APIRouter根据 http url把请求映射到具体的方法.

参考

Paste Deployment简介及nova-api-paste.ini解析
JiYou

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 以下是本人通过阅读Grizzly版OpenStack源码,整理的简要的Nova模块源码结构,希望和大家相互交流。 ...
    Chenzongshu阅读 2,594评论 0 50
  • Openstack项目的REST接口普遍使用wsgi app,作者在这里通过对senlin项目接口的解读,来帮助大...
    EldonZhao阅读 601评论 0 0
  • 在 从零开始搭建论坛(一):Web服务器与Web框架 中我们弄清楚了Web 服务器、Web 应用程序、Web框架的...
    selfboot阅读 2,257评论 0 8
  • 人生在世要的评价不是现在,也不是以前,要的是百年以后。你做过了什么,好的坏的公道自在人心。金钱财富名誉地位,孰轻孰...
    翱翔交易阅读 111评论 0 1