openstack API (一)- 独立项目对外接口 RESTFUL

openstack学习之各种API

  一句话: openstack各自独立的项目通过RESTFUL API对外提供服务,而项目内的 各个主机上的进程 通信主要通过RPC来通信的。



openstack  独立项目对外接口 ( RESTFUL API)

  总结:aste.deploy模块加载 api-paste.init定义的,用于服务的wsgi app; 用webob库对wsgi app进行封装,简化wsgi app的定义与编写;使用routes库来给服务内部定义url到具体函数的映射。deploy也有url到服务映射功能,但这个映射层次偏高一点,可以理解为二次精确映射。

      

个人觉得这个框架以后版本也会成为olso中的一个模块包。

网络上挺多相关文章不错,这里就转两章代表性,怕链接会找不到:

Python paste模块和routes模块 

      原作者自己写个简单-RESTFUL server的构架。不过要跑起来,需要自己完善下。作为实践,修改,理解openstack RESTFUL 框架不错。

Python paste模块和routes模块  【转】

在阅读openstack源码时,发现其各个组件基本都是使用paste模块和routes模块来构建server以及处理路由信息的,为了熟悉这些模块决定使用这些模块写一个简单的server,此server的构架和glance组件的设计保持一致。

  首先使用paste创建一个app,然后wsgiref.simple_server启动他,至于app的功能通过配置文件来决定

1 config = "python_paste.ini"
2 appname = "common"
3 wsgi_app = loadapp("config:%s" % os.path.abspath(config), appname)
4  server = make_server('localhost',80,wsgi_app)
5 server.serve_forever()

python_paste.ini

复制代码
[composite:common]
use = egg:Paste#urlmap
/:showversion
/log:showversion_log
/v1:apiv1app

[pipeline:showversion_log]
pipeline = filter_log showversion

[filter:filter_log ]
#filter2 deal with time,read args belowmanage
paste.filter_factory = manage:LogFilter.factory

[app:apiv1app]
paste.app_factory = v1.router:MyRouterApp.factory

[app:showversion]
version = 1.0.0
paste.app_factory = manage:ShowVersion.factory
复制代码
/:showversion:打印版本号
/log:showversion_log:打印版本号的同时做相应的日志记录
/v1:apiv1app:模仿glance中的多版本api,将所有v1的请求转发apiv1app处理。
复制代码
 1 class ShowVersion(object):
 2       '''
 3       app
 4       '''
 5       def __init__(self,version):
 6           self.version = version
 7       def __call__(self,environ,start_response):
 8           res = Response()
 9           res.status = '200 OK'
10           res.content_type = "text/plain"
11           content = []
12           content.append("%s\n" % self.version)
13           res.body = '\n'.join(content)
14           return res(environ,start_response)
15       @classmethod
16       def factory(cls,global_conf,**kwargs):
17           print 'factory'
18           print "kwargs:",kwargs
19           return ShowVersion(kwargs['version'])
复制代码
复制代码
 1 class LogFilter(object):
 2       '''
 3       Log
 4       '''
 5       def __init__(self,app):
 6           self.app = app
 7       def __call__(self,environ,start_response):
 8           print 'you can write log.‘
 9           return self.app(environ,start_response)
10       @classmethod
11       def factory(cls,global_conf,**kwargs):
12           return LogFilter
复制代码

  到此uri为/和/log时,均可得到服务器的正常响应。但是/v1还没有实现。不得不佩服python的强大,这么几行就实现了一个web服务器的基本功能,下面在介绍routes,配合routes就能够实现更多的更优雅的uri路由机制。

  注:此处对paste的配置文件介绍不多,大家去paste官网查看即可,写的很详细了,这个也不是很难,没必要去一一介绍。

   新建一个python模块v1,在v1里面新建两个文件router.py和wsgi.py,wsgi.py是一个通用文件,和业务无关,我们只需要关注router.py的实现即可,使用起来非常简单方便。

   router.py:

复制代码
 1 import wsgi
 2 
 3 class ControllerTest(object):
 4     def __init__(self):
 5         print "ControllerTest!!!!"
 6     def test(self,req):
 7           print "req",req
 8           return {
 9             'name': "test",
10             'properties': "test"
11         }
12 
13 class MyRouterApp(wsgi.Router):
14       '''
15       app
16       '''
17       def __init__(self,mapper):
18           controller = ControllerTest()
19           mapper.connect('/test',
20                        controller=wsgi.Resource(controller),
21                        action='test',
22                        conditions={'method': ['GET']})
23           super(MyRouterApp, self).__init__(mapper)
复制代码

wsgi.py:

复制代码
  1 import datetime
  2 import json
  3 import routes
  4 import routes.middleware
  5 import webob
  6 import webob.dec
  7 import webob.exc
  8 
  9 class APIMapper(routes.Mapper):
 10     """
 11     Handle route matching when url is '' because routes.Mapper returns
 12     an error in this case.
 13     """
 14 
 15     def routematch(self, url=None, environ=None):
 16         if url is "":
 17             result = self._match("", environ)
 18             return result[0], result[1]
 19         return routes.Mapper.routematch(self, url, environ)
 20 
 21 class Router(object):
 22     def __init__(self, mapper):
 23         mapper.redirect("", "/")
 24         self.map = mapper
 25         self._router = routes.middleware.RoutesMiddleware(self._dispatch,
 26                                                           self.map)
 27 
 28     @classmethod
 29     def factory(cls, global_conf, **local_conf):
 30         return cls(APIMapper())
 31 
 32     @webob.dec.wsgify
 33     def __call__(self, req):
 34         """
 35         Route the incoming request to a controller based on self.map.
 36         If no match, return a 404.
 37         """
 38         return self._router
 39 
 40     @staticmethod
 41     @webob.dec.wsgify
 42     def _dispatch(req):
 43         """
 44         Called by self._router after matching the incoming request to a route
 45         and putting the information into req.environ.  Either returns 404
 46         or the routed WSGI app's response.
 47         """
 48         match = req.environ['wsgiorg.routing_args'][1]
 49         if not match:
 50             return webob.exc.HTTPNotFound()
 51         app = match['controller']
 52         return app
 53 
 54 class Request(webob.Request):
 55     """Add some Openstack API-specific logic to the base webob.Request."""
 56 
 57     def best_match_content_type(self):
 58         """Determine the requested response content-type."""
 59         supported = ('application/json',)
 60         bm = self.accept.best_match(supported)
 61         return bm or 'application/json'
 62 
 63     def get_content_type(self, allowed_content_types):
 64         """Determine content type of the request body."""
 65         if "Content-Type" not in self.headers:
 66             return
 67 
 68         content_type = self.content_type
 69 
 70         if content_type not in allowed_content_types:
 71             return
 72         else:
 73             return content_type
 74 
 75 class JSONRequestDeserializer(object):
 76     def has_body(self, request):
 77         """
 78         Returns whether a Webob.Request object will possess an entity body.
 79 
 80         :param request:  Webob.Request object
 81         """
 82         if 'transfer-encoding' in request.headers:
 83             return True
 84         elif request.content_length > 0:
 85             return True
 86 
 87         return False
 88 
 89     def _sanitizer(self, obj):
 90         """Sanitizer method that will be passed to json.loads."""
 91         return obj
 92 
 93     def from_json(self, datastring):
 94         try:
 95             return json.loads(datastring, object_hook=self._sanitizer)
 96         except ValueError:
 97             msg = _('Malformed JSON in request body.')
 98             raise webob.exc.HTTPBadRequest(explanation=msg)
 99 
100     def default(self, request):
101         if self.has_body(request):
102             return {'body': self.from_json(request.body)}
103         else:
104             return {}
105 
106 class JSONResponseSerializer(object):
107 
108     def _sanitizer(self, obj):
109         """Sanitizer method that will be passed to json.dumps."""
110         if isinstance(obj, datetime.datetime):
111             return obj.isoformat()
112         if hasattr(obj, "to_dict"):
113             return obj.to_dict()
114         return obj
115 
116     def to_json(self, data):
117         return json.dumps(data, default=self._sanitizer)
118 
119     def default(self, response, result):
120         response.content_type = 'application/json'
121         response.body = self.to_json(result)
122 
123 class Resource(object):
124     def __init__(self, controller, deserializer=None, serializer=None):
125         self.controller = controller
126         self.serializer = serializer or JSONResponseSerializer()
127         self.deserializer = deserializer or JSONRequestDeserializer()
128 
129     @webob.dec.wsgify(RequestClass=Request)
130     def __call__(self, request):
131         """WSGI method that controls (de)serialization and method dispatch."""
132         action_args = self.get_action_args(request.environ)
133         action = action_args.pop('action', None)
134 
135         deserialized_request = self.dispatch(self.deserializer,
136                                              action, request)
137         action_args.update(deserialized_request)
138 
139         action_result = self.dispatch(self.controller, action,
140                                       request, **action_args)
141         try:
142             response = webob.Response(request=request)
143             self.dispatch(self.serializer, action, response, action_result)
144             return response
145 
146         except webob.exc.HTTPException as e:
147             return e
148         # return unserializable result (typically a webob exc)
149         except Exception:
150             return action_result
151 
152     def dispatch(self, obj, action, *args, **kwargs):
153         """Find action-specific method on self and call it."""
154         try:
155             method = getattr(obj, action)
156         except AttributeError:
157             method = getattr(obj, 'default')
158 
159         return method(*args, **kwargs)
160 
161     def get_action_args(self, request_environment):
162         """Parse dictionary created by routes library."""
163         try:
164             args = request_environment['wsgiorg.routing_args'][1].copy()
165         except Exception:
166             return {}
167 
168         try:
169             del args['controller']
170         except KeyError:
171             pass
172 
173         try:
174             del args['format']
175         except KeyError:
176             pass
177 
178         return args
复制代码
我们使用 mapper.connect 接口创建路由信息:
/test :uri
controller:控制器对象
action:控制器对象中的方法,即在请求uri时最终执行的方法
contitions:请求类型
1           mapper.connect('/test',
2                        controller=wsgi.Resource(controller),
3                        action='test',
4                        conditions={'method': ['GET']})

在这个例子中,当我们执行/v1/test 即可得到如下回复:

{
'name': "test",
'properties': "test"
}

本文介绍的server框架和glance一模一样,和keystone也是大同小异。


 openstack之nova-api服务流程分析【转】

nova-api发布api服务没有用到一个些框架,基本都是从头写的。在不了解它时,以为它非常复杂,难以掌握。花了两三天的时间把它分析一遍后,发现它本身的结构比较简单,主要难点在于对它所使用的一些类库不了解,如paste.deploy/webob/routes。对于paste.deploy,结合它的官网文档把它的源码看了两遍。webob看的是源码。routes看的是文档。对于这些类库提供的函数,如果从文档中去理解他们想要做什么,真不是件容易的事。查看其实现源码,就明了了。不过在分析源码过程中,碰到每一个类库都去翻一遍它的源码,这也是非常累人的,后期甚至都不想再看下去了,因为脑子比较厌烦了。所以在学习routes时主要是看它的文档,基本理解了。

paste.deploy

用来解析/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

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处理。

routes

用来给服务内部定义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'})

主题--nova-api服务流程分析

上面介绍了nova-api发布所用到的一些lib库,有了上面的基础知识,再来分析nova-api的发布流量,就比较轻松了。

nova-api可以提供多种api服务:ec2, osapi_compute, osapi_volume, metadata。可以通过配置项enabled_apis来设置启动哪些服务,默认情况下,四种服务都是启动的。

从nova-api的可执行脚本中,可以看出每个nova-api服务都是通过nova.service.WSGIService来管理的:

class WSGIService(object):
    def __init__(self, name, loader=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(FLAGS, '%s_listen' % name, "0.0.0.0")
        self.port = getattr(FLAGS, '%s_listen_port' % name, 0)
        self.workers = getattr(FLAGS, '%s_workers' % name, None)
        self.server = wsgi.Server(name,                               #这里通过eventlet来启动服务
                                  self.app,
                                  host=self.host,
                                  port=self.port)
&nbsp; &nbsp; def start(self):
&nbsp; &nbsp; &nbsp; &nbsp; if self.manager:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.manager.init_host()
&nbsp; &nbsp; &nbsp; &nbsp; self.server.start()
    ......

从上可知,WSGIService使用self.app = self.loader.load_app(name)来加载wsgi app,app加载完成后,使用nova.wsgi.Server来发布服务。Server首先用指定ip和port实例化一个监听socket,并使用wsgi.server以协程的方式来发布socket,并将监听到的http请求交给app处理。对于Server的启动过程,代码上理解还是比较简单的,没多少分析的。下面我们主要来分析处理HTTP请求的wsgi app是如何构建的,对于每一个请求,它是如何根据url和请求方法将请求分发到具体的具体函数处理的。

上个语句self.loader.load_app(name)中的loader是nova.wsgi.Loader的实例。Loader.load_app(name)执行下面指令,使用deploy来加载wsgi app:

deploy.loadapp("config:%s" % self.config_path, name=name)  

self.config_path为api-paste.ini文件路径,一般为/etc/nova/api-paste.ini。name为ec2, osapi_compute, osapi_volume, metadata之一,根据指定的name不同来加载不同的wsgi app。下面以name=“osapi_compute”时,加载提供openstack compute API服务的wsgi app作为具体分析。osapi_compute的配置如下

[composite:osapi_compute]                                                       
use = call:nova.api.openstack.urlmap:urlmap_factory                             
/: oscomputeversions                                                
/v2: openstack_compute_api_v2

osapi_compute是调用urlmap_factory函数返回的一个nova.api.openstack.urlmap.URLMap实例,nova.api.openstack.urlmap.URLMap继承paste.urlmap.URLMap,它提供了wsgi调用接口,所以该实例为wsgi app。但是函数nova.api.openstack.urlmap:urlmap_factory与paste.urlmap.urlmap_factory定义完全一样,不过由于它们所在的module不同,使得它们所用的URLMap分别为与它处于同一module的URLMap。paste.urlmap.urlmap_factory咋不支持一个传参,来指定URLMap呢?这样nova就不用重写一样的urlmap_factory了。paste.urlmap.URLMap实现的功能很简单:根据配置将url映射到特定wsgi app,并根据url的长短作一个优先级排序,url较长的将优先进行匹配。所以/v2将先于/进行匹配。URLMap在调用下层的wsgi app前,会更新SCRIPT_NAME和PATH_INFO

nova.api.openstack.urlmap.URLMap继承了paste.urlmap.URLMap,并写了一堆代码,其实只是为了实现对请求类型的判断,并设置environ['nova.best_content_type']:如果url的后缀名为json(如/xxxx.json),那么environ['nova.best_content_type']=“application/json”。如果url没有后缀名,那么将通过HTTP headers的content_type字段中mimetype判断。否则默认environ['nova.best_content_type']=“application/json”。

经上面配置加载的osapi_compute为一个URLMap实例,wsgi server的接受的HTTP请求将直接交给该实例处理。它将url为'/v2/.*'的请求将交给openstack_compute_api_v2,url为'/'的请求交给oscomputerversions处理(它直接返回系统版本号)。其它的url请求,则返回NotFound。下面继续分析openstack_compute_api_v2,其配置如下:

[composite:openstack_compute_api_v2]                                            
use = call:nova.api.auth:pipeline_factory                                       
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2              
keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2

openstack_compute_api_v2是调用nova.api.auth.pipeline_factory()返回的wsgi app。pipeline_factory()根据配置项auth_strategy来加载不同的filter和最终的osapi_compute_app_v2。filter的大概配置如下:

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

filter在nova中对应的是nova.wsgi.Middleware,它的定义如下:

class Middleware(Application):                                                                       
                                                                                
    @classmethod                                                                
    def factory(cls, global_config, **local_config):                                                                   
        def _factory(app):                                                      
            return cls(app, **local_config)                                     
        return _factory                                                         
                                                                                
    def __init__(self, application):                                            
        self.application = application                                          
                                                                                
    def process_request(self, req):                                             
        return None                                                             
                                                                                
    def process_response(self, response):                           
        return response                                                         
                                                                                
    @webob.dec.wsgify(RequestClass=Request)                                     
    def __call__(self, req):                                                    
        response = self.process_request(req)                                    
        if response:                                                            
            return response                                                     
        response = req.get_response(self.application)                           
        return self.process_response(response)

Middleware初始化接收一个wsgi app,在调用wsgi app之前,执行process_request()对请求进行预处理,判断请求是否交给传入的wsgi app,还是直接返回,或者修改些req再给传入的wsgi app处理。wsgi app返回的response再交给process_response()处理。例如,对于进行验证的逻辑,可以放在process_request中,如果验证通过则继续交给app处理,否则返回“Authentication required”。不过查看nova所有Mddlerware的编写,似乎都不用这种定义好的结构,而是把处理逻辑都放到__call__中,这样导致__call__变得复杂,代码不够整洁。对于FaultWrapper尚可理解,毕竟需要捕获wsgi app处理异常嘛,但其它的Middleware就不应该了。这可能是不同程序员写,规范就忽略了。

当auth_strategy=“keystone”时,openstack_compute_api_v2=FaultWrapper(RequestBodySizeLimiter(auth_token(NovaKeystoneContext(RateLimitingMiddleware(osapi_compute_app_v2)))))。所以HTTP请求需要经过五个Middleware的处理,才能到达osapi_compute_app_v2。这五个Middleware分别完成:

1)异常捕获,防止服务内部处理异常导致wsgi server挂掉;

2)限制HTTP请求body大小,对于太大的body,将直接返回BadRequest;

3)对请求keystone对header中token id进行验证;

4)利用headers初始化一个nova.context.RequestContext实例,并赋给req.environ['nova.context'];

5)限制用户的访问速度。

当HTTP请经过上面五个Middlerware处理后,最终交给osapi_compute_app_v2,它是怎么继续处理呢?它的配置如下:

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

osapi_compute_app_v2是调用nova.api.openstack.compute.APIRouter.factory()返回的一个APIRouter实例。nova.api.openstack.compute.APIRouter继承nova.api.openstack.APIRouter,nova.api.openstack.APIRouter又继承nova.wsgi.APIRouter。APIRouter通过A它的成员变量mapper来建立和维护url与controller之间的映射,该mapper是nova.api.openstack.ProjectMapper的实例,它继承nova.api.openstack.APIMapper(routes.Mapper)。APIMapper将每个url的format限制为json或xml,对于其它扩展名的url将返回NotFound。ProjectMapper在每个请求url前面加上一个project_id,这样每个请求的url都需要带上用户所属的project id,所以一般请求的url为/v2/project_id/resources。nova.api.openstack.compute.APIRouter.setup_routes代码如下:

class APIRouter(nova.api.openstack.APIRouter):                                                                    
    ExtensionManager = extensions.ExtensionManager                              
                                                                                
    def _setup_routes(self, mapper, ext_mgr):
        self.resources['servers'] = servers.create_resource(ext_mgr)            
        mapper.resource("server", "servers",                                    
                        controller=self.resources['servers'])                   
                                                                                
        self.resources['ips'] = ips.create_resource()                           
        mapper.resource("ip", "ips", controller=self.resources['ips'],          
                        parent_resource=dict(member_name='server',              
                                             collection_name='servers'))
        ......
                         

APIRouter通过调用routes.Mapper.resource()函数建立RESTFUL API,也可以通过routes.Mapper.connect()来建立url与controller的映射。如上所示,servers相关请求的controller设为servers.create_resource(ext_mgr),该函数返回的是一个用nova.api.openstack.compute.servers.Controller()作为初始化参数的nova.api.openstack.wsgi.Resource实例,ips相关请求的controller设为由nova.api.openstack.ips.Controller()初始化的nova.api.openstack.wsgi.Resource实例。因为调用mapper.resource建立ips的url映射时,添加了一个parent_resource参数,使得请求ips相关api的url形式为/v2/project_id/servers/server_id/ips。对于limits、flavors、metadata等请求情况类似。当osapi_compute_app_v2接收到HTTP请求时,将调用nova.wsgi.Router.__call__,它的定义如下:

class Router(object):             
                                                                                
    def __init__(self, mapper):
        self.map = mapper                                                       
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,       
                                                          self.map)             
                                                                                
    @webob.dec.wsgify(RequestClass=Request)                                     
    def __call__(self, req):
        return self._router                                                     
    @staticmethod                                                               
    @webob.dec.wsgify(RequestClass=Request)                                     
    def _dispatch(req):
        match = req.environ['wsgiorg.routing_args'][1]                          
        if not match:                                                           
            return webob.exc.HTTPNotFound()                                     
        app = match['controller']                                               
        return app   

这里开始让我迷惑了一下,__call__()怎么能返回一个wsgi app呢,直接返回wsgi app那它又怎么被调用呢?查看一下wsgify源码,发现如果函数返回的是wsgi app时,它还会被继续调用,并返回它的处理结果。所以它会继续调用self._router,_router是routes.middleware.RoutesMiddleware的实例,使用self._dispatch和self.map来初始化的,self.map是在Router的子类nova.api.openstack.APIMapper.__init__中,被初始化为ProjectMapper实例,并调用_setup_routes建立好url与cotroller之间的映射。routes.middleware.RoutesMiddleware.__call__调用mapper.routematch来获取该url映射的controller等参数,以{"controller":Resource(Controller()), "action": funcname, "project_id": uuid, ...}的格式放在match中。并设置如下的environ变量,方便后面调用的self._dispatch访问。最后调用self._dispatch。



          



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值