在前面的博文中,主要分析了Glance
及Nova
相关的代码,从这篇文章开始我将转到Cinder
的源码分析上来。Cinder
模块在Openstack
中为云主机提供块存储,主要包含:cinder-api
,cinder-scheduler
,cinder-volume
及cinder-backup
4个部分,后续将通过一系列文章逐个分析各个组件的源码。
今天先来看看cinder-api
启动过程的源码分析,预计将包括如下几个方面的内容:
- 请求路由映射(Python Routes)
- WSGI 应用发现(Python Paste Deployment)
- WSGI服务器
限于篇幅,可能将上述主题拆分到多篇博文,下面一起来看具体内容:
启动cinder-api
服务
当你通过cinder-api
命令(如:/usr/bin/cinder-api --config-file /etc/cinder/cinder.conf
)启动api
服务时,执行的实际上是
cinder/cmd/api.py/main()
函数, 如下:
#`cinder/cmd/api.py/main`
def main():
"""省略次要代码,完成代码请查看相关文件"""
#加载辅助对象,封装与数据库相关的操作
objects.register_all()
#加载配置并设置日志
CONF(sys.argv[1:], project='cinder',
version=version.version_string())
logging.setup(CONF, "cinder")
"""初始化rpc:
设置全局Transport和Notifier,Transport是
oslo_messaging/transport.py/Transport实例,我采用的是默认的
rpc_backend=rabbit,所以Transport采用的driver=oslo_messaging/
_drivers/impl_rabbit.py/RabbitDriver;Notifier是一个通知消息发
送器,它借助Transport将通知发送发送给ceilometer
"""
rpc.init(CONF)
#通过服务启动器启动WSGI服务(`osapi_volume`)并等待服务启动成功
#在初始化WSGI服务时,会设置路由映射以及加载WSGI应用程序
#在启动WSGI服务时,会启动http监听
#下文具体分析相关内容
launcher = service.process_launcher()
server = service.WSGIService('osapi_volume')
launcher.launch_service(server, workers=server.workers)
launcher.wait()
创建WSGIService
服务对象
def main():
......
#创建一个名为`osapi_volume`的`WSGIService`服务对象
server = service.WSGIService('osapi_volume')
......
#接上文,一起来看看`WSGIService`服务对象的初始化函数
#`cinder/service.py/WSGIService.__init__`
def __init__(self, name, loader=None):
"""Initialize, but do not start the WSGI server."""
#服务名`osapi_volume`
self.name = name
#加载名为(`osapi_volume_manager`)的管理器(None)
self.manager = self._get_manager()
"""创建WSGI应用加载器(`cinder/wsgi/common.py/Loader`)
并根据配置文件(`cinder.conf`)设置应用配置路径:
`config_path` = `/etc/cinder/paste-api.ini`
"""
self.loader = loader or wsgi_common.Loader()
"""加载WSGI应用并设置路由映射
return paste.urlmap.URLMap, 请看后文的具体分析
"""
self.app = self.loader.load_app(name)
"""根据配置文件(`cinder.conf`)设置监听地址及工作线程数
如果未指定监听ip及端口就分别设置为`0.0.0.0`及`0`
如果为指定工作线程数就设置为cpu个数
如果设置的工作线程数小于1,则抛异常
"""
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' % name, 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)d is"
"invalid, must be greater than 0.") %
{
'worker_name': worker_name,
'workers': self.workers})
raise exception.InvalidInput(msg)
"""如果CONF.profiler.profiler_enabled = True就开启性能分析
创建一个类型为`Messaging`的通知器(`_notifier`),将性能数据发送给
ceilometer
"""
setup_profiler(name, self.host)
#创建WSGI服务器对象(`cinder/wsgi/eventlet_server.py/Server`)
#下一篇博文再具体分析WSGI服务器的初始化及启动过程,敬请期待!!!
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port)
小结:在初始化WSGIService
服务对象过程中,主要完成了如下操作:
- 加载
WSGI Application
(Python Paste Deployment
) - 设置路由映射(
Python Routes
) - 创建WSGI服务器对象并完成初始化
先来看WSGI Application
的加载过程:
加载WSGI
应用
上文的self.loader.load_app(name)
,执行的是如下的调用:
#`cinder/wsgi/common.py/Loader.load_app`
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
`Python Paste`系统可以用来发现以及配置`WSGI`应用及服务, 包含如下三
种调用入口:
`loadapp` `loadfilter` `loadserver`
| | |
| |
|
V
`loadobj`
|
V
`loadcontext`
|
| |
| | |
V V V
_loadconfig _loadegg _loadfunc
分别用来配置`WSGI App`,`WSGI Filter`,`WSGI Server`;
`loadcontext`方法基于配置文件类型(`config`,`egg`,`call`),调用具
体的配置方法,在我们的示例中是:`loadapp` -> `loadobj` ->
`loadcontext` -> `_loadconfig`,下文依次分析:
"""
try:
#从`self.config_path`(`/etc/cinder/api-paste.ini`)指定的
#配置中加载名为`name`(`osapi_volume`)的应用
return deploy.loadapp("config:%s" % self.config_path,
name=name)
except LookupError:
LOG.exception(_LE("Error loading app %s"), name)
raise exception.PasteAppNotFound(name=name, path=self.config_path)
#接上文,直接通过`Python Paste`系统配置`WSGI`应用
#`../site-packages/paste/deploy/loadwsgi.py/loadapp`
def loadapp(uri, name=None, **kw):
"""输入参数如下:
uri: 'config:/etc/cinder/api-paste.ini'
name: 'osapi_volume'
**kw: None
"""
"""APP = _APP(),是一个_APP实例对象,定义应用所支持的协议及其前缀:
APP.name = 'application'
APP.egg_protocols = [['paste.app_factory'],
['paste.composite_factory'],
['paste.composit_factory']]
APP.config_prefixes = [['app', 'application'],
['composite', 'composit'],
['pipeline],
['filter-app']]
在后文的分析中会根据应用的协议来生成上下文(`context`)
"""
return loadobj(APP, uri, name=name, **kw)
#接上文`loadobj`
def loadobj(object_type, uri, name=None, relative_to=None,
global_conf=None):
"""根据应用的协议类型生成上下文并执行
object_type: _APP对象
uri: 'config:/etc/cinder/api-paste.ini'
name: 'osapi_volume'
"""
context = loadcontext(
object_type, uri, name=name, relative_to=relative_to,
global_conf=global_conf)
return context.create()
#接上文:这是一个工厂方法,它根据uri中的配置文件类型
#(`config`,`egg`,`call`)分别调用具体的配置方法
#(`_loadconfig`,`_loadegg`, `_loadfunc`)
def loadcontext(object_type, uri, name=None, relative_to=None,
global_conf=None):
"""创建应用上下文,结合输入参数,代码逻辑就很好理解了
object_type: _APP对象
uri: 'config:/etc/cinder/api-paste.ini'
name: 'osapi_volume'
relative_to: None
global_conf: None
"""