您的位置:首页 > 其它

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

2016-10-13 18:44 99 查看

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_manag
4000
er()
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_v21
keystone = 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)的wsg
aec9
i 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再尝试看代码,多设断点多调试,调试技巧很重要。继续努力!有小伙伴感兴趣的欢迎讨论。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: