nova api执行过程(以nova list为例)

来源:互联网 发布:软件开发测试基础知识 编辑:程序博客网 时间:2024/05/22 06:54

nova api执行过程(以nova list为例)

本文仅供学习参考:

  • 使用devstack部署的openstack,需了解如何在screen中重新启动一个服务
  • 请注意时间,因为openstack处在不断更新中,本文学习的是当前最新的openstack版本
  • 在screen下用ipdb进行调试
  • 对openstack整个架构有个大致了解
  • 没有写明根目录的默认根目录是/opt/stack/nova(即openstack源码目录下的nova目录)

REQ: curl -g -i -X GET http://10.238.158.189/identity -H “Accept: application/json” -H “User-Agent: nova keystoneauth1/2.12.1 python-requests/2.11.1 CPython/2.7.6”

REQ: curl -g -i -X GET http://10.238.158.189:8774/v2.1 -H “User-Agent: python-novaclient” -H “Accept: application/json” -H “X-Auth-Token: {SHA1}1ed8ae56fa11986c2d3aef62a58d3355d9178d2c”

REQ: curl -g -i -X GET http://10.238.158.189:8774/v2.1/servers/detail -H “OpenStack-API-Version: compute 2.37” -H “User-Agent: python-novaclient” -H “Accept: application/json” -H “X-OpenStack-Nova-API-Version: 2.37” -H “X-Auth-Token: {SHA1}1ed8ae56fa11986c2d3aef62a58d3355d9178d2c”

第一步:从Keystone拿到一个授权的token
第二步:把返回的token填入api请求中,该步为验证版本v2.1之类的(不确定)
第三步:同样需要将token填入api请求,这一个步骤是真正的主要的请求即获得所有当前活跃状态的虚拟机

nova list命令转化为HTTP请求

由novaclient实现,不关注…

HTTP请求到WSGI Application

这里需要了解下WSGI和paste配置文件,WSGI推荐 –> https://www.fullstackpython.com/wsgi-servers.html

Nova API服务nova-api(在screen中对应的编号为6的window)在启动 时(没有发出HTTP请求时),会根据Nova配置文件(/etc/nova/nova.conf)的enable_apis选项内容创建一个或多个WSGI Server,用devstack部署默认的配置如下

enabled_apis = osapi_compute,metadata

Paste Deploy会在各个WSGI Server创建使参与进来,基于Paste配置文件(/etc/nova/api-paste.ini)去加载WSGI Application,加载WSGI Application由nova/service.py实现,加载后,WSGI Application就处在等待和监听状态。

class WSGIService(service.Service):    """Provides ability to launch API from a 'paste' configuration."""    def __init__(self, name, loader=None, use_ssl=False, max_url_len=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        # 这里的name就是WSGI server name,比如osapi_compute或者metadata        # nova.service's enabled_apis        self.binary = 'nova-%s' % name        self.topic = None        self.manager = self._get_manager()        self.loader = loader or wsgi.Loader()        # 从paste配置文件加载 Nova API 对应的 WSGI Application        self.app = self.loader.load_app(name)        # inherit all compute_api worker counts from osapi_compute        if name.startswith('openstack_compute_api'):            wname = 'osapi_compute'        else:            wname = 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' % wname, None) or                        processutils.get_worker_count())        if self.workers and self.workers < 1:            worker_name = '%s_workers' % name            msg = (_("%(worker_name)s value of %(workers)s is invalid, "                     "must be greater than 0") %                   {'worker_name': worker_name,                    'workers': str(self.workers)})            raise exception.InvalidInput(msg)        self.use_ssl = use_ssl        self.server = wsgi.Server(name,                                  self.app,                                  host=self.host,                                  port=self.port,                                  use_ssl=self.use_ssl,                                  max_url_len=max_url_len)        # Pull back actual port used        self.port = self.server.port        self.backdoor_port = None

在nova-api运行过程中(发出了HTTP请求),Paste Deploy会将WSGI Server上监听到的HTTP请求根据Paste配置文件准确地路由到特定的WSGI Application,这其中经过了
nova.api.openstack.urlmap的urlmap_factory
nova.api.auth的pipeline_factory_v21
nova.api.openstack.compute的APIRouterV21.factory(路由到指定的app)

下面根据nova –debug list输出的请求信息,一一介绍
GET http://10.238.158.189:8774/v2.1/servers/detail
1.首先我们通过这个请求知道使用了v2.1的API,再根据paste配置文件中

[composite:osapi_compute]   <--use = call:nova.api.openstack.urlmap:urlmap_factory     <--/: oscomputeversions/v2: openstack_compute_api_v21_legacy_v2_compatible/v2.1: openstack_compute_api_v21    <--

则WSGI服务器osapi_compute将监听到这个请求,根据第一行use的内容我们知道是由nova.api.openstack.urlmap模块的urlmap_factory进行分发,即对应了openstack_compute_api_v21

2.在paste配置文件中,又有

[composite:openstack_compute_api_v21]   <--use = call:nova.api.auth:pipeline_factory_v21   <--noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit noauth2 osapi_compute_app_v21keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21   <--

可知又使用了nova.api.auth模块的pipeline_factory_v21进一步分发,并根据/etc/nova/nova.conf中认证策略“auth_strategy”选项使用参数keystone,在paste文件中,我们看到noauth2和keystone后面跟着一大串,不用过于深究,这是一个pipeline,最后一个是真正的app即osapi_compute_app_v21,从代码中分析可知根据这个pipeline,从后往前为这个app穿上一件件“外衣”,每一次封装都是封装成一个WSGI Application。
代码实现见nova/api/auth.py

def _load_pipeline(loader, pipeline):    filters = [loader.get_filter(n) for n in pipeline[:-1]]    app = loader.get_app(pipeline[-1])    filters.reverse()    for filter in filters:        app = filter(app)    return app

3.再由paste配置文件中

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

需要调用nova.api.openstack.compute的APIRouterV21.factory

WSGI Application到具体的执行函数

这个部分是最复杂的部分,函数的调用和封装很复杂,有时候看到最深层次也看不大懂了,如果有愿意分享的小伙伴欢迎多多交流

APIRouterV21类的定义在nova/api/openstack/__init__.py中,主要完成对所有资源的加载和路由规则的创建(得到一个mapper包含所有资源的路由信息),APIRouterV21初始化的最后一步是将这个mapper交给它的父类nova.wsgi.Router,完成mapper和_dismatch()的关联,在openstack中,每个资源对应一个Controller,也对应一个WSGI Application,比如请求
GET http://10.238.158.189:8774/v2.1/servers/detail
对应的Contrller就是资源servers的Controller,具体位置是nova/api/openstack/compute/servers.py中的ServersController,这个Controller中定义了各种action,其中有一个函数是detail(),最终会调用这个函数得到想要的结果
下面详细介绍其过程

1.所有资源的加载以及路由规则的创建

class APIRouterV21(base_wsgi.Router):       def __init__(self, init_only=None):        def _check_load_extension(ext):            return self._register_extension(ext)        # 使用stevedore的EnabledExtensionManager类载入位于setup.cfg        # 的nova.api.v21.extensions的所有资源        self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(            namespace=self.api_extension_namespace(),            check_func=_check_load_extension,            invoke_on_load=True,            invoke_kwds={"extension_info": self.loaded_extension_info})        mapper = ProjectMapper()        self.resources = {}        # NOTE(cyeoh) Core API support is rewritten as extensions        # but conceptually still have core        if list(self.api_extension_manager):            # NOTE(cyeoh): Stevedore raises an exception if there are            # no plugins detected. I wonder if this is a bug.            # 所有资源进行注册,同时使用mapper建立路由规则            self._register_resources_check_inherits(mapper)            # 扩展现有资源及其操作            self.api_extension_manager.map(self._register_controllers)        LOG.info(_LI("Loaded extensions: %s"),                 sorted(self.loaded_extension_info.get_extensions().keys()))        super(APIRouterV21, self).__init__(mapper)

2.父类Router将mapper和dispatch()关联起来

class Router(object):    """WSGI middleware that maps incoming requests to WSGI apps."""    def __init__(self, mapper):        """Create a router for the given routes.Mapper.        Each route in `mapper` must specify a 'controller', which is a        WSGI app to call.  You'll probably want to specify an 'action' as        well and have your controller be an object that can route        the request to the action-specific method.        Examples:          mapper = routes.Mapper()          sc = ServerController()          # Explicit mapping of one route to a controller+action          mapper.connect(None, '/svrlist', controller=sc, action='list')          # Actions are all implicitly defined          mapper.resource('server', 'servers', controller=sc)          # Pointing to an arbitrary WSGI app.  You can specify the          # {path_info:.*} parameter so the target app can be handed just that          # section of the URL.          mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())        """        self.map = mapper        self._router = routes.middleware.RoutesMiddleware(self._dispatch,                                                          self.map)       # <--    @webob.dec.wsgify(RequestClass=Request)    def __call__(self, req):        """Route the incoming request to a controller based on self.map.        If no match, return a 404.        """        return self._router    @staticmethod    @webob.dec.wsgify(RequestClass=Request)    def _dispatch(req):        """Dispatch the request to the appropriate controller.        Called by self._router after matching the incoming request to a route        and putting the information into req.environ.  Either returns 404        or the routed WSGI app's response.        """        match = req.environ['wsgiorg.routing_args'][1]        if not match:            return webob.exc.HTTPNotFound()        app = match['controller']        return app

这里的__call__函数进行了wsgify的封装,在这个Router被call的时候,或者是它的子类APIRouterV21在被call的时候,需要看webob.dec.wsgify里面是怎么进行封装的才能知道具体的执行过程。我们已经了解到,在从HTTP请求路由到特定的WSGI Apllication的时候,最终路由到的app是经过封装的,即经过了pipeline列出的一系列filter,所以这个时候我们要真正地调用时,需要一层层把外面的filter去掉,这个在/usr/local/lib/python2.7/dist-packages/webob/dec.py中的wsgify类中有一个__call__的定义

def __call__(self, req, *args, **kw):        """Call this as a WSGI application or with a request"""        func = self.func        if func is None:            if args or kw:                raise TypeError(                    "Unbound %s can only be called with the function it "                    "will wrap" % self.__class__.__name__)            func = req            return self.clone(func)        if isinstance(req, dict):            if len(args) != 1 or kw:                raise TypeError(                    "Calling %r as a WSGI app with the wrong signature")            environ = req            start_response = args[0]            req = self.RequestClass(environ)            req.response = req.ResponseClass()            try:                args = self.args                if self.middleware_wraps:                    args = (self.middleware_wraps,) + args                resp = self.call_func(req, *args, **self.kwargs)            except HTTPException as exc:                resp = exc            if resp is None:                ## FIXME: I'm not sure what this should be?                resp = req.response            if isinstance(resp, text_type):                resp = bytes_(resp, req.charset)            if isinstance(resp, bytes):                body = resp                resp = req.response                resp.write(body)            if resp is not req.response:                resp = req.response.merge_cookies(resp)            return resp(environ, start_response)        else:            if self.middleware_wraps:                args = (self.middleware_wraps,) + args            return self.func(req, *args, **kw)

这个函数定义了,被dec.wsgify封装后的函数在被调用的时候,会不断地把外面的“外衣”脱掉,一直到最核心的app,这个app是一个直接接收参数(environ, start_response)的wsgi app,我们看到在Router类的__init__操作中,初始化了一个self.router = routes.middleware.RoutesMiddleware(self._dispatch,self.map),而routes.middleware.RoutesMiddleware的__call_函数是一个标准的WSGI Application,它接收参数(environ, start_response),如下,

    def __call__(self, environ, start_response):        """Resolves the URL in PATH_INFO, and uses wsgi.routing_args        to pass on URL resolver results."""        old_method = None        if self.use_method_override:            req = None        #(以下省略...)

所以在APIRouter被call的整个过程中,我们可以通过在dec.wsgify的__call__函数中加打印信息查看整个流程,打印每次被call时的func信息,结果如下(总共有三次类似的输出,因为有三次请求)

********************************************************************************<bound method CORS.__call__ of <oslo_middleware.cors.CORS object at 0x7f15db89ef50>>********************************************************************************<bound method HTTPProxyToWSGI.__call__ of <oslo_middleware.http_proxy_to_wsgi.HTTPProxyToWSGI object at 0x7f15db89eed0>>********************************************************************************<bound method ComputeReqIdMiddleware.__call__ of <nova.api.compute_req_id.ComputeReqIdMiddleware object at 0x7f15db89ee50>>********************************************************************************<bound method FaultWrapper.__call__ of <nova.api.openstack.FaultWrapper object at 0x7f15db89ec50>>********************************************************************************<bound method RequestBodySizeLimiter.__call__ of <oslo_middleware.sizelimit.RequestBodySizeLimiter object at 0x7f15db89edd0>>********************************************************************************<bound method AuthProtocol.__call__ of <keystonemiddleware.auth_token.AuthProtocol object at 0x7f15db89e690>>********************************************************************************<bound method NovaKeystoneContext.__call__ of <nova.api.auth.NovaKeystoneContext object at 0x7f15db89e6d0>>********************************************************************************<bound method APIRouterV21.__call__ of <nova.api.openstack.compute.APIRouterV21 object at 0x7f15dbf45990>>

和paste配置文件中的pipeline的顺序是一样的,即

keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21

小结

看了几天的代码,很多还是一头雾水,python调用的库很多,openstack里又经常进行封装操作,有时候一个调用栈非常深,很容易混乱,这里涉及到的比如mapper是怎么建立路由的,等等,都没有在此深究,这种先看官网的exsample再尝试看代码,多设断点多调试,调试技巧很重要。继续努力!有小伙伴感兴趣的欢迎讨论。

0 0
原创粉丝点击