源码版本:H版
1、paste.deploy
参考文章:
http://pythonpaste.org/deploy/
http://blog.csdn.net/xiangmin2587/article/details/8203503
http://www.choudan.net/2013/07/28/OpenStack-paste-deploy%E4%BB%8B%E7%BB%8D.html
用来解析/etc/nova/api-paste.ini文件,加载用于服务的wsgi app。它的功能有:
1)api-paste.ini中配置多个wsgi app,deploy可根据传入的app name加载指定的wsgi app;
deploy.loadapp("config:/etc/nova/api-paste.ini", name="osapi-compute")
加载api-paste.ini中,名为osapi-compute的WSGI APP,并作为结果返回。
2)通过写入api-paste.ini的配置,可方便地实现特定字符开始的url到特定wsgi app的映射。如:
[composite:osapi_compute] use = call:nova.api.openstack.urlmap:urlmap_factory /: oscomputeversions /v2: openstack_compute_api_v2
通过该配置,以“/v2”开始的url将交给名为openstack_compute_api_v2的WSGI APP处理,其它以“/”开的url就交给oscomputerversions处理。其实这并非deploy的功能,而是上面配置的urlmap实现的。不过通过配置文件,再由deploy调用urlmap,使用就更简单了。
3)middle ware的简单加载和去除。
[composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
上面的faultwrap sizelimit authtoken keystonecontext ratelimit都为middle ware(在nova代码中称为MiddleWare,deploy中称为filter),osapi_compute_app_v2才是wsgi app。请求先交给middle ware处理,再由middle ware决定要不要或者怎么交给wsgi app处理。这些middle ware是可以添加和去除的,如果不想对osapi_compute_app_v2进行限速,那么去掉ratelimit就可以了。其实这是pipeline_factory实现的功能,不过通过deploy来配置加载更方便。
nova-api中提供了很多服务API:ec2(与EC2兼容的API),osapi_compute(openstack compute自己风格的API),osapi_volume(openstack volume服务API),metadata(metadata 服务API)等。通过配置文件api-paste.ini,可以方便管理这些API。
deploy提供了server、filter、app的概念,其中filter(即middle ware)、app在nova-api被重度使用,的确太好用了。发布每个API时,它们有时需要一些相同的功能,如:keystone验证、限速、错误处理等功能。nova将这些功能实现为middle ware,如果需要,通过api-paste.ini配置,给它加上就可以。比如,我需要记录每个访问nova-api的ip,及它们的访问次数和访问的时间。那么我实现一个middle ware--nova.api.middleware:MonitorMiddleware来记录这些数据。通过下面的api-paste.ini配置,就可对nova-api的访问进行记录了。
[composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory keystone = faultwrap sizelimit authtoken keystonecontext ratelimit <strong>monitor</strong> osapi_compute_app_v2 [filter:<strong>monitor</strong>] paste.filter_factory = nova.api.middleware:MonitorMiddleware.factory
2、webob
用于对wsgi app进行封装,简化wsgi app的定义与编写。webob主要提供三种功能。
1)Request。该类用于对传给wsgi app的env参数进行封装,简化对HTTP请求参数的访问和设置。这种简化体现在(假设用env对Request实例化了一个对象req):
1) 使用简洁明了的属性名对HTTP参数进行访问,req.method可获取HTTP请求方法(替代REQUEST_METHOD);req.scheme可获取HTTP请求协议http or https(替代wsgi.url_scheme);req.body获取请求body(替代wsgi.input)。
2)大量使用property,省去繁琐细节,简化HTTP参数的访问和设置。req.body直接访问HTTP请求的body,而不用考虑body的长度和字符编码;req.POST以字典形式返回POST请求的参数;req.GET以字典形式返回GET请求的参数。
nova.api.openstack.wsgi.Request继承该类,并加了一个缓存已访问db的记录的功能,和对content_type判断和检测的功能。
2)Response。该类用于对HTTP的返回值进行封装。与Request对象类似,同样使用了property简化对http参数的访问和设置。支持wsgi app一次返回status和body,这样更直观。其实Response实例本身也是一个wsgi app。
3)decorator--wsgify,装饰wsgi app,使其可以以如下方式定义:
@webob.dec.wsgify def wsgi_app(req): #do something with req return req.Response(...)
其中参数req是一个Request(默认)或其子类(通过wsgify(RequestClass=XXX)指定)的实例,是用env初始化的。req.Response默认为webob.Response。以该种方式定义的wsgi app,其结果可以以三种形式返回:
1)返回一个字符串。wsgify将其作为body,并加上一些默认的参数,如status=“200 OK", content_type, content_length等,构造成一个HTTP响应结果并返回;
2)返回一个Response实例,直接返回该resp代表的HTTP请求结果;
3)返回一个wsgi app,wsgify会继续调用该app,并返回app的响应结果。
nova.wsgi.Router就是用第三种返回形式,两次返回wsgi app,最终将HTTP请求根据url映射到对应的controller处理。
3、routes
参考文章:
http://routes.readthedocs.org/en/latest/
用来给服务内部定义url到具体函数的映射。
deploy也有url到服务映射功能,但这个映射层次偏高一点。根据上面配置,deploy将以“/v2”开始的url将交给名为openstack_compute_api_v2处理。但openstack_compute_api_v2怎么将/v2/project_id/servers/的GET请求交给nova.api.openstack.compute.servers.Controller.index()处理,并且将POST请求交给create()处理呢;怎么将/v2/project_id/servers/id的GET请求交给show()处理呢?这个就是routes.mappers所提供的功能,它根据path和请求方法,将请求映射到具体的函数上。如在nova中,添加/v2/project_id/servers/{list_vm_state, os_vmsum}两个GET请求来分别获取指定VM的状态和VM的总数。可在nova.api.openstack.compute.APIRouter中添加如下两行,将请求分别交给list_vm_state和os_vmsum两个函数处理并返回结果:
self.resources['servers'] = servers.create_resource(ext_mgr) mapper.resource("server", "servers", controller=self.resources['servers'], <strong>collection={'list_vm_state': 'GET', 'os_vmsum': 'GET'}</strong>)
这里利用了routes.mapper支持restful api的特性,仅用两条指令,就定义了十多个url到函数的映射。当然你可以如下方式添加接口,不过代码稍多,风格不那么统一:
mapper.connect("server", "/{project_id}/servers/list_vm_state", controller=self.resources['servers'], action='list_vm_state', conditions={'list_vm_state': 'GET'}) mapper.connect("server", "/{project_id}/servers/os_vmsum", controller=self.resources['servers'], action='os_vmsum', conditions={'os_vmsum': 'GET'})