本文为扶艾原创文章,版权所有,禁止转载!
请大家静下心来看源码分析的文章,看完后相信你会有收获的!
本文来自于微信公众号“扶艾”,欢迎大家关注获取更多精彩内容!
上篇内容主要分析了下创建虚拟机从点击按钮到发送http请求部分源码。这篇文章是本系列的第二篇文章,按照内容来说,这篇内容更多的在说nova api,和创建虚拟机的关系实在不大。不过这边文章能帮助大家更清晰的理解restful架构、wsgi以及openstack 如何构建api接口的。文中涉及东西很多,但都是必要得铺垫,有说得不对的地方,欢迎大家指出,欢迎大家拍砖!
nova api
这部分源码主要在两个地方:
- nova源码 /usr/lib/python2.7/site-packages/nova
- openstack服务管理源码 /usr/lib/python2.7/site-packages/oslo_service
首先,在开始阅读源码之前,需要先有几个铺垫。
rest
restful在openstack中大家应该是听得比较多了。rest是Roy Thomas Fielding在2000年的博士论文中提出的。如果大家想深入理解rest是什么,相比于网上去搜索答案,不如看下那篇博士论文。这里我们为大家准备了一份他的中文版的博士论文,大家扫描下方微信公众号二维码,回复fuai_rest即可获取。
现在网上一搜restful是什么?会出来很多的解释,很多术语都很专业,很多人都是看得云里雾里的。之前在网上看到一篇形象的解释,现在放在这大家理解理解:
没有用rest
POST /users/getuser 获取用户信息
POST /users/createuser 创建用户
POST /users/updateuser 更新用户信息
POST /users/deleteUser 删除用户
使用rest风格
GET /users/{user_id} 获取用户信息
POST /users 创建用户
PUT /users/{user_id} 更新用户信息
DELETE /users/{user_id} 删除用户信息
这里我也不过多解释rest了。openstack的api就是这种风格的api,所以叫它restful api。举个简单的例子:
POST v2/{project_id}/servers 创建虚拟机
GET v2/{project_id}/servers 获取所有虚拟机信息
routes
openstack的api是restful api,而restful架构的核心是资源以及对这些资源的操作。openstack定义了很多资源以及这些资源的操作函数。当openstack api接收到http请求时,怎么知道请求调用的是哪个资源的操作函数?这时候路由模块就登场了,它可以将请求url转换成相应的资源,并路由到对应的操作函数上。
Example:
#创建一个映射对象
mapper = routes.Mapper()
#实现一个资源类,openstack中有很多类似的Controller类,每一个Controller都对应了一个restful资源,该类下有对该资源的各种操作函数,比如index、show、create等
sc = ServerController()
#定义url到资源的映射
mapper.connect(None, '/svrlist', controller=sc, action='list')
mapper.resource('server', 'servers', controller=sc)
wsgi
wsgi是python中定义的web服务器和web应用程序之间的通用接口标准。它分成三个部分web服务器、web中间件、web应用程序。如果不理解这句话,那我再白话一点,它就像web服务器与你的应用程序之间管道一样。还不理解?那你知道nova api是需要实现那三个部分的就行了。
oslo.service
openstack的api接口既然采用的是wsgi标准,那它们是怎么实现这个标准的,翻了下前面几个版本的源码,kilo版本及之前实现wsgi相关的东西都是在nova这个源码目录下的,到了liberty版本之后,就正式将这部分源码从各个组件模块中提出了,就是现在的oslo.service。
oslo_service实现中,核心是oslo_serivce.service中的Service类和Launcher类。Service类描述一个服务,Launcher类用来启动服务。
paste.deploy
paste是openstack中用于实现wsgi标准的具体工具模块,它可以根据配置文件/etc/nova/api-paste.ini来加载wsgi app
api-paste.ini部分内容如下:
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
...
/v2.1: openstack_compute_api_v21
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
...
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21
...
[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
上面配置文件出现了下面几个部分
- composite, 这个部分是将url请求分发到对应的app,use表明的是分发的方式,这里是urlmap进行分发的
- filter,这个其实对应的是wsgi 的middleware。上面的cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext这些都是middleware,osapi_compute_app_v21才是app。这些middleware是可以自行增加或删除的
接收到请求后,首先会交给middleware依次处理,最后交给app。
- paste的deploy可根据传入的app名称来加载wsgi app:
deploy.loadapp("config:/etc/nova/api-paste.ini", name=name)
下面会具体用到paste,再细说。
nova api
现在进入主题。nova api是什么?nova api是nova提供各种服务的公共接口。nova api从客户端接收到请求之后,然后将请求给nova,nova处理完成请求之后将处理结果返回给nova客户端。那么nova api是如何接收请求的。前面已经简单说了下rest和wsgi,而nova api就是rest风格的api,它也是采用的wsgi标准,有web server、web middleware、web app。
如果要看nova api如何接收请求,我们先看它是如何启动起来的。看它的服务启动文件/usr/lib/systemd/system/openstack-nova-api.service,知道它启动脚本是/usr/bin/nova-api
...
[Service]
...
ExecStart=/usr/bin/nova-api
...
/usr/bin/nova-api文件内容如下
#!/usr/bin/python2
# PBR Generated from u'console_scripts'
import sys
from nova.cmd.api import main
if __name__ == "__main__":
sys.exit(main())
我们看下/usr/lib/python2.7/site-packages/nova/cmd/api.py文件,看看启动nova api要做哪些工作。下面路径1即为api.py的主要内容。首先获得了一个服务启动的对象launcher,它是 service.process_launcher()返回的。这里的service是导入路径2的service.py,在路径2中我们看到了process_launcher方法。它返回的是service.ProcessLauncher类,而路径2的service导入了的oslo_service的service.py(路径3)中的class ProcessLauncher。
继续看路径1,server = service.WSGIService(api, use_ssl=should_use_ssl)这里返回了一个wsgi的服务对象。用于描述一个wsgi服务。这里WSGIService也是在路径2中,它继承的是oslo_service的Service类(路径3),这就是我们文章开头说的oslo_service的两个类。
继续看路径1,launcher.launch_service启动了wsgi服务,最后launcher.wait让api服务处于等待外部请求状态。关于launch_service的信息请看路径3。
...
路径1. /usr/lib/python2.7/site-packages/nova/cmd/api.py
部分源码:
from nova import service
def main():
...
#创建一个ProcessLauncer对象,该对象可以启动一个或多个wsgi服务
launcher = service.process_launcher()
started = 0
for api in CONF.enabled_apis:
should_use_ssl = api in CONF.enabled_ssl_apis
try:
#创建一个wsgi服务对象,该对象具有wsgi服务的生命周期管理功能
server = service.WSGIService(api, use_ssl=should_use_ssl)
#启动wsgi server
launcher.launch_service(server, workers=server.workers or 1)
started += 1
except exception.PasteAppNotFound as ex:
log.warning("%s. ``enabled_apis`` includes bad values. "
"Fix to remove this warning.", ex)
if started == 0:
log.error('No APIs were started. '
'Check the enabled_apis config option.')
sys.exit(1)
launcher.wait()
路径2. /usr/lib/python2.7/site-packages/nova/service.py
部分源码:
from oslo_service import service
from nova import wsgi
def process_launcher():
return service.ProcessLauncher(CONF, restart_method='mutate')
#继承oslo_service中的service.Service
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
"""
#name是路径1中传递过来的api,这里是"osapi_compute"
self.name = name
...
self.loader = loader or wsgi.Loader()
#加载的是osapi_compute这个app
self.app = self.loader.load_app(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())
...
#初始化一个wsgi.Server来发布app服务
self.server = wsgi.Server()
...
路径3. /usr/lib/python2.7/site-packages/oslo_service/service.py
部分源码:
#服务封装类
class ServiceWrapper(object):
def __init__(self, service, workers):
self.service = service
self.workers = workers
self.children = set()
self.forktimes = []
class ProcessLauncher(object):
"""Launch a service with a given number of workers."""
def __init__(self, conf, wait_interval=0.01, restart_method='reload'):
...
#这里wrap是服务封装类ServiceWrapper的对象
def _start_child(self, wrap):
...
pid = os.fork() #开进程
if pid == 0:
self.launcher = self._child_process(wrap.service)
while True:
self._child_process_handle_signal()
status, signo = self._child_wait_for_exit_or_signal(
self.launcher)
if not _is_sighup_and_daemon(signo):
self.launcher.wait()
break
self.launcher.restart()
os._exit(status)
LOG.debug('Started child %d', pid)
wrap.children.add(pid)
self.children[pid] = wrap
return pid
def launch_service(self, service, workers=1):
"""Launch a service with a given number of workers.
...
#判断是否是ServerBase对象。WSGIService继承的Service,而Service继承的ServerBase
_check_service_base(service)
#服务封装类
wrap = ServiceWrapper(service, workers)
...
while self.running and len(wrap.children) < wrap.workers:
#启动服务
self._start_child(wrap)
class Service(ServiceBase):
def __init__(self, threads=1000):
self.tg = threadgroup.ThreadGroup(threads)
def reset(self):
...
def start(self):
...
def stop(self):
...
def wait(self):
self.tg.wait()
上面简单说了下启动nova api的过程,也就是启动wsgi服务的过程,创建launcher对象、创建wsgi服务对象、启动wsgi服务。那么我们具体看下是如何实现的。看上面路径2中的WSGIService(),它是描述wsgi服务的类。其中获取wsgi app通过self.loader.load_app来获取的,这里self.loader是等于wsgi.Loader(),wsgi是引入的路径4的这个wsgi.py。根据路径4中load_app可以发现它就是通过文章前面说的paste模块来加载的。继续看WSGIService类,获取主机ip及端口,然后wsgi.Server来发布app服务
路径4. /usr/lib/python2.7/site-packages/nova/wsgi.py
部分源码:
#用于从paste配置文件加载wsgi app的类
class Loader(object):
#初始化加载类,并检测配置文件是否存在
def __inti__(self, config_path=None):
self.config_path = None
#根据配置文件,可以发现CONF.wsgi.api_paste_config默认是"api-paste.ini"
config_path = config_path or CONF.wsgi.api_paste_config
if not os.path.isabs(config_path):
self.config_path = CONF.find_file(config_path)
elif os.path.exists(config_path):
self.config_path = config_path
if not self.config_path:
raise exception.ConfigNotFound(path=config_path)
#这里name是路径2的self.loader.load_app传过来的值,是"osapi_compute"
def load_app(self, name):
...
#这里加载的app是osapi_compute,配置文件路径是api-paste.ini
return deploy.loadapp("config:%s" % self.config_path, name=name)
从上面内容来看,我们已经知道WSGIService使用self.loader.load_app(name)来加载wsgi app,加载app完成之后,使用nova.wsgi.Server来发布服务。wsgi.Server会将监听到的http请求交给app处理。
上面加载了osapi_compute,wsgi server接收到的http请求首先会交给nova.api.openstack.urlmap:urlmap_factory的可调用对象,处理之后将请求分发给openstack_compute_api_v21,openstack_compute_api_v21使用nova.api.auth:pipeline_factory_v21来返回wsgi app,pipeline_factory_v21会依次加载cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext这些filter(这些filter就是wsgi middleware),这些filter处理完成之后才会将请求交给osapi_compute_app_v21(相当于wsgi app)。
api-paste.ini
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
...
/v2.1: openstack_compute_api_v21
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
...
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21
...
[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
这里的这些filter我们就不一一分析了,我们直接看osapi_compute_app_v21这边如何处理请求的。根据上面配置文件信息,我们知道调用了nova.api.openstack.compute.APIRouterV21.factory的可调用对象。这个factory实际就在下面路径6所示,它应该返回一个可调用对象。这里可以看到它返回的是类对象,而APIRouterV21继承的是base_wsgi.Router,也就是说请求是调用了路径7中的call方法。而call方法返回的是self._router,_router是routes.middleware.RoutesMiddleware的实例化对象,使用self._dispatch和self.map来初始化的。self.map是哪传进来的呢?注意看路径6中的APIRouterV21的init方法,其中传入了一个nova.api.openstack.ProjectMapper()对象,而这个ProjectMapper最终继承的是routes.Mapper,routes.Mapper在文章铺垫部分已经说明,它是一个路由模块,所以路径7中的self.map已经明了是一个routes.Mapper()对象,并且通过路径6中的APIRouterV21的init方法建立了映射关系,nova api在启动的时候已经初始化并建立url与资源的映射关系。继续看路径7中的dispatch方法,它通过前面设置environ[‘wsgiorg.routing_args’]来找到url对应的controller,找不到返回404。根据路径6的ROUTE_LIST信息,可以看到/servers对应的controller是nova.api.openstack.compute.servers中的ServersController
路径5. /usr/lib/python2.7/site-packages/nova/api/openstack/compute/__init__.py
部分源码:
from nova.api.openstack.compute.routes import APIRouterV21
路径6. /usr/lib/python2.7/site-packages/nova/api/openstack/compute/routes.py
部分源码:
from nova import wsgi as base_wsgi
from nova.api.openstack.compute import servers
...
server_controller = functools.partial(_create_controller,
servers.ServersController
...
ROUTE_LIST= (...
('/servers', {
'GET': [server_controller, 'index'],
'POST': [server_controller, 'create']
}),...)
class APIRouterV21(base_wsgi.Router):
...
def __init__(self, custom_routes=None):
super(APIRouterV21, self).__init__(nova.api.openstack.ProjectMapper())
...
for path, methods in ROUTE_LIST + custom_routes:
if isinstance(methods, str):
self.map.redirect(path, methods)
continue
for method, controller_info in methods.items():
# TODO(alex_xu): In the end, I want to create single controller
# instance instead of create controller instance for each
# route.
controller = controller_info[0]()
action = controller_info[1]
self.map.create_route(path, method, controller, action)
def factory(cls, global, **local_config):
return cls()
路径7. /usr/lib/python2.7/site-packages/nova/wsgi.py
部分源码:
import routes.middleware
class Router(object):
def __init__(self, mapper):
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
def __call__(self, req):
"""Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
return self._router
def _dispatch(req):
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
最后看看ServerController,找到了post对应的方法create。也就是说nova客户端发送的请求最后是调用到了操作函数create。
路径8. /usr/lib/python2.7/site-packages/nova/api/openstack/compute/servers.py
部分源码:
class ServersController(wsgi.Controller):
...
def create(self, req, body):
...
(instances, resv_id) = self.compute_api.create()
...
return wsgi.ResponseObject(...)
本篇文章涉及得东西很多,只是分析了下大体流程,很多地方展开说又是一篇文章。
至此,nova api已经接收到创建虚拟机的请,后面则是nova具体创建虚拟机的操作。我们将再《OpenStack源码阅读-创建虚拟机(三)》呈现给大家
更多精彩内容,OpenStack干货请扫描下方二维码,关注我们微信公众号“扶艾”!

1525



被折叠的 条评论
为什么被折叠?



