设为首页 收藏本站
查看: 1381|回复: 0

[经验分享] openstack nova源码分析之api(二)

[复制链接]

尚未签到

发表于 2016-1-9 10:38:27 | 显示全部楼层 |阅读模式
三. nova api中的WSGI application
1. paste.deploy
官方文档:http://pythonpaste.org/deploy/
paste是python的一个WSGI工具包,在WSGI的基础上包装了几层,让应用管理和实现变得方便。
几个概念:
app:WSGI application,实现应用端,响应请求的方法,应当是一个callable object,在被call时传入参数为(env,start_response),app需要完成的任务是响应envrion中的请求,准备好响应头和消息体,然后交给start_response处理,并返回响应消息体。
filter:是一个callable object,其初始化时需要的唯一参数是(app),在被call时对env进行判断或者说过滤,当满足条件后call自身的app,否则直接链断,给出错误处理。
app_factory:app的工厂方法,参数(global_conf,**kwargs),其中global_conf对应配置文件中的[DEFAULT]section键值对,kwargs则对应自身setion键值对。该工程方法返回了app的一个实例。
filter_factory:filter的工厂方法,与app_factory比较需要注意的是,该方法返回的是filter类,实例化的过程在pipeline调用。
一个简单的例子:

#####################
##  api-paste.ini  ##
#####################
 
[DEFAULT]
#DEFAULT段,下面的键值对将作为global_conf
author = zhengtianbao
blog = www.zhengtianbao.com
 
[composite:web]
#web是一个符合wsgi的app,不过他是composite类型,下面的use方法将会调用
#Paste包中的urlmap对‘/home’,‘/blog’做初始化,同时在call的时候将会map
#到不同的app
use = egg:Paste#urlmap
/home: home
#/blog: blog
 
[pipeline:home]
#pipeline管道,是一个callable对象,具体call的过程为:
#faultwarp->logrequest->homeapp
#是不是很像linux中的grep命令
#顺便一提home作为一个app的初始化过程顺序为:
#faultwarp(logrequest(homeapp()))
pipeline = faultwarp logrequest homeapp
 
[filter:faultwarp]
#filter,直接调用factory方法,下面的key=value作为kwargs参数
#相当与:
#    import apipaste
#    apipaste.FaultWarp(app_from_paste, key=value)
paste.filter_factory = apipaste:FaultWarp.factory
key = value
 
[filter:logrequest]
paste.filter_factory = apipaste:RequestLogging.factory
 
[app:homeapp]
#app,这里是app最终的实现
#相当与:
#    import apipaste
#    apipaste.HomeApp(version='1.3')
#这里的键值对作为参数传入
paste.app_factory = apipaste:HomeApp.factory
version = 1.3



#!/usr/bin/env python
#coding=utf8
 
"""apipaste.py"""
 
import os
 
import eventlet
from eventlet import wsgi
from paste import deploy
 
class HomeApp(object):
    """home wsgi application"""
     
    def __init__(self):
        print 'In HomeApp init'
     
    def __call__(self, env, start_response):
        print 'app:HomeApp is called'
        start_response('200 OK', [('Content-Type', 'text/plain')])
        return ['welcome to home page, hello world!']
     
    @classmethod
    def factory(cls, global_conf, **kwargs):
        print 'In HomeApp.factory'
        #print 'global_conf: ', global_conf
        #print 'kwargs: ', kwargs
        return HomeApp()#返回的是HomeApp的一个实例化对象
     
class FaultWarp(object):
    """fault warpper class"""
     
    def __init__(self, app):#init时需要传入一个app对象
        print 'In FaultWarp init'
        self.app = app
     
    def __call__(self, env, start_response):
        #在这里可以对env进行一些判断过滤
        print 'filter:FaultWarp is called'
        return self.app(env, start_response)
     
    @classmethod
    def factory(cls, global_conf, **kwargs):
        print 'In FaultWarp factory'
        #print 'global_conf: ', global_conf
        #print 'kwargs: ', kwargs
        return FaultWarp#返回的是FaultWarp类
 
class RequestLogging(object):
    """request log class"""
     
    def __init__(self, app):
        print 'In RequestLogging init'
        self.app = app
         
    def __call__(self, env, start_response):
        #在这里可以对请求进行一些日志操作
        print 'filter:RequestLogging is called'
        return self.app(env, start_response)
     
    @classmethod
    def factory(cls, global_conf, **kwargs):
        print 'In RequestLogging factory'
        #print 'global_conf: ', global_conf
        #print 'kwargs: ', kwargs
        return RequestLogging
 
if __name__ == '__main__':
    config_file = 'api-paste.ini'
    app = 'web'
    #app = 'homeapp'
    wsgi_app = deploy.loadapp("config:%s" % os.path.abspath(config_file), app)
    wsgi.server(eventlet.listen(('', 8090)), wsgi_app)



我们通过浏览器分别访问
http://localhost:8090/
http://localhost:8090/home/
http://localhost:8090/home/page/id/1
输出结果如下:
[iyunv@localhost 20130801]# python apipaste.py
In HomeApp.factory
In HomeApp init
In FaultWarp factory
In RequestLogging factory
In RequestLogging init
In FaultWarp init
(25629) wsgi starting up on http://0.0.0.0:8090/
 
(25629) accepted ('127.0.0.1', 59121)
127.0.0.1 - - [01/Aug/2013 22:15:48] "GET / HTTP/1.1" 404 448 0.011558
filter:FaultWarp is called
filter:RequestLogging is called
app:HomeApp is called
127.0.0.1 - - [01/Aug/2013 22:15:53] "GET /home/ HTTP/1.1" 200 160 0.000222
filter:FaultWarp is called
filter:RequestLogging is called
app:HomeApp is called
127.0.0.1 - - [01/Aug/2013 22:16:01] "GET /home/page/id/1 HTTP/1.1" 200 160 0.000364



发现URL中以/home/开头的地址都分发到了HomeApp,观察打印信息可以跟踪它调用pipeline的过程。
当然,在openstack中的app可不是这么简单的,下面就接着说说webob这个python库。

2. webob
官方文档:http://docs.webob.org/en/latest/
webob是一个Python库,主要是用在WSGI中对请求环境变量request environment(也就是WSGI应用中的参数environ)进行包装(提供wrapper),并提供了一个对象来方便的处理返回response消息。
nova中主要用到了webob中的装饰器,对HTTP请求信息req做了包装判断,对response的处理也做了包装。
基本用法:

#!/usr/bin/env python
#coding=utf8
 
import eventlet
from eventlet import wsgi
import webob
import webob.dec
import webob.exc
  
def hello_world(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello, World!\r\n']
 
@webob.dec.wsgify
def app(req):
    """
    webobo.dec.wsgify可以将函数修饰成wsgi app
    webob.exc可以生成HTTP的一些错误响应,如404,500等
    经过修饰后的WSGI application可以返回以下几种response
        #1: 字符串
        res = 'hello world!\r\n'
        #2: webob错误页信息
        res = webob.exc.HTTPForbidden(detail='Error page')
        #3: 另外一个wsgi app
        res = hello_world
    """
    #res = 'message\n'
    #res = webob.exc.HTTPForbidden(detail='Error page')
    #res = hello_world
    #在这里可以对req进行操作
    req.environ['msg'] = 'welcome to my blog:)'
    #这样传递到下一个app的时候就能够做一些判断处理
    res = app2
    #可以打印查看req的信息
    #print dir(req)
    #print req
    #print req.environ
    return res
 
@webob.dec.wsgify
def app2(req):
    if req.environ['msg']:
        print req.environ['msg']
    res = hello_world
    return res
  
#wsgi.server(eventlet.listen(('', 8090)), hello_world)
wsgi.server(eventlet.listen(('', 8090)), app)



回过头来,看nova/wsgi.py文件,该文件是nova中wsgi相关的基类,其中定义了下面几个类:
Server –> WSGI server,wsgi api服务基类
Request –> app request, HTTP请求类,继承自webob.Request
Application –> WSGI application基类
Middleware –> WSGI app filter基类,继承自Application
Debug –> debug,继承自Middleware
Router –> WSGI 路由,用到了routes包
Loader –> paste.deploy加载接口类
貌似就剩下Router这个类了,下面就接着讲nova中所用到的routes。

3. routes
官方文档:http://routes.readthedocs.org/en/latest/index.html
Routes 可以方便的定义符合RESTful 标准的 web服务.map.resource可以快速创建出 增/改/删 路由接口.
这里只说明nova中所用到的:

import routes
import routes.middleware
import webob.dec
import webob.exc
 
mapper = routes.Mapper()
#需要对mapper指定controller,这个controller相当于是一个wsgi app,用来做URL路由call的app
#1.connect方式创建一条route
#mapper.connect(None, '/svrlist', controller=sc, action='list')
#2.隐式的创建多条routes
#mapper.resource('server', 'servers', controller=sc)
 
@webob.dec.wsgify
def dispatch(req):
    match = req.environ['wsgiorg.routing_args'][1]
    if not match:
        return webob.exc.HTTPNotFound()
    app = match['controller']
    return app
 
router = routes.middleware.RoutesMiddleware(dispatch, mapper)
#router做为一个wsgi app被call的时候将会对请求URL先在mapper中匹配前面注册的URL route
#将匹配到的信息(上面connect或者resource创建的routes)添加到req.environ中
#然后调用dispatch函数,对req.environ['wsgiorg.routing_args']进行判断,
#例如id,controller等,然后分发到相应的wsgi app中



下面看从nova中的简化出来的例子:
import eventlet
from eventlet import wsgi
import routes
import routes.middleware
import webob
import webob.dec
import webob.exc
  
class Router(object):
    """wsgi router"""
     
    def __init__(self, mapper):
        self.map = mapper
        self._router = routes.middleware.RoutesMiddleware(
                    self._dispatch,
                    self.map)
     
    @webob.dec.wsgify
    def __call__(self, req):
        return self._router
 
    @staticmethod
    @webob.dec.wsgify
    def _dispatch(req):
        match = req.environ['wsgiorg.routing_args'][1]
        print 'match: ', match
        #print 'body: ', req.body
        if not match:
            return webob.exc.HTTPNotFound()
        app = match['controller']
        #简单的判断方法,nova中是放到了Resource类的call方法中进行判断的
        if hasattr(app, match['action']):
            fun = getattr(app, match['action'])
            return fun(req)
        else:
            return webob.exc.HTTPNotFound()
 
 
class APIRouter(Router):
    """"""
     
    def __init__(self):
        mapper = routes.Mapper()
        self.resources = {}
        self._setup_routes(mapper)
        super(APIRouter, self).__init__(mapper)
 
    def _setup_routes(self, mapper):
        #注册routes,关于下面两个用法请看官方文档说明
        self.resources['versions'] = VersionsApp()
        self.resources['servers'] = ServersApp()
 
        mapper.connect("version", "/version/",
                    controller=self.resources['versions'],
                    action='show')
        mapper.resource("server", "servers",
                    controller=self.resources['servers'],
                    collection={'detail': 'GET'},
                    member={'action': 'POST'})
 
 
class VersionsApp(object):
    """wsgi application"""
     
    def __init__(self):
        pass
     
    @webob.dec.wsgify
    def __call__(self, req):
        return 'version called'
 
    @webob.dec.wsgify
    def show(self, req):
        #对应GET /versions/
        return 'in version show fun'
         
 
class ServersApp(object):
    """wsgi application"""
     
    def __init__(self):
        pass
 
    def __call__(self, req):
        return 'servers called'
 
    @webob.dec.wsgify  
    def index(self, req):
        #对应GET /servers
        return 'welcome to servers app'
     
    @webob.dec.wsgify
    def action(self, req):
        #对应POST /servers/{id}/action
        #这里没有定义id的接口,nova中是在上层route的时候判断定义了接口才能传入id
        #例如这个接口定义def action(self, req, id, body)
        return 'server action function'
 
wsgi.server(eventlet.listen(('', 8090)), APIRouter())



请试着发送以下RESTful请求:
GET http://localhost:8090/version/
GET http://localhost:8090/servers
POST http://localhost:8090/servers/2/action Body={‘msg’:’hello world’}
观察返回结果是不是预料的那样.

关于mapper.resource方法的一点说明:
例如创建下面的resource:
map.resource(“message”, “messages”)
则对应为:
GET /messages => messages.index() => url(“messages”)
POST /messages => messages.create() => url(“messages”)
GET /messages/new => messages.new() => url(“new_message”)
PUT /messages/1 => messages.update(id) => url(“message”, id=1)
DELETE /messages/1 => messages.delete(id) => url(“message”, id=1)
GET /messages/1 => messages.show(id) => url(“message”, id=1)
GET /messages/1/edit => messages.edit(id) => url(“edit_message”, id=1)

map.resource(“message”, “messages”, collection={“rss”: “GET”})
GET /messages/rss => messages.rss()

map.resource(“message”, “messages”, member={“mark”:”POST”})
POST /messages/1/mark” => Messages.mark(1)

总结下访问顺序:RESTful请求 -> RoutesMiddleware进行mapper -> wsgi application
大体上nova也是这个流程,不过nova中对Mapper的connect以及resource进行了重写,在router时加了更多的判断,而且最终的wsgi app是包装成Resource(Controller())这种形式的.

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-162108-1-1.html 上篇帖子: OpenStack网络知识片断(持续更新) 下篇帖子: OpenStack块存储nova-volume工作机制和相关问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表