提示:本文基于openstack liberty版本源码进行梳理
文章目录
前言
之前我们在源码准备工作中找到了程序入口。下面我们就四个模块的启动流程进行分析。启动流程大致相同。本文介绍cinder四个模块之中的三个:backup、scheduler、volume。这三个模块都是Service,cinder-api则是WSGIService,所以本文将这三个放在一起。
基本所有的openstack服务都依赖 evenlet 完成各种并发任务,它的进程可分为两类:
- WSGIService: 接收和处理 http 请求,依赖eventlet.wsgi的 wsgi server,处理 http 请求,比如cinder-api
- Service: 接收和处理 rpc 请求,如cinder-volume等
无论是 WSGIService还是 Service类型的进程,每当接收到一个请求(http 或 rpc),都会在线程池中分配一个协程处理该请求
一、cinder-backup&cinder-scheduler启动区别
没区别!!!
backup和scheduler的启动流程不能说是毫无相关,简直就是一模一样,上对比:
除了创建Service的binary参数不一样,其他不同就是改了个注释,换了个代码顺序。
惊不惊喜,意不意外😂
既然一样那本文以cinder-scheduler启动为例
二、cinder-scheduler启动流程
1.main方法
代码文件位置:cinder/cmd/scheduler.py
import eventlet
eventlet.monkey_patch()
import sys
from oslo_config import cfg
from oslo_log import log as logging
from oslo_reports import guru_meditation_report as gmr
from cinder import i18n
i18n.enable_lazy()
# Need to register global_opts
from cinder.common import config # noqa
from cinder import objects
from cinder import service
from cinder import utils
from cinder import version
CONF = cfg.CONF
def main():
objects.register_all()
CONF(sys.argv[1:], project='cinder',
version=version.version_string())
logging.setup(CONF, "cinder")
utils.monkey_patch()
gmr.TextGuruMeditation.setup_autorun(version)
server = service.Service.create(binary='cinder-scheduler')
service.serve(server)
service.wait()
这里主要关注的代码只有两行:
# 创建service对象
server = service.Service.create(binary='cinder-scheduler')
# 加载service
service.serve(server)
2.创建service对象
第一行创建了一个Service
对象,调用Service.create
方法去创建对象过程中主要做了以下几件事
- 根据传入的binary参数(这里是‘cinder-scheduler’),确定要加载的manager类名。类名为’cinder-'后面的内容,接上‘_manager’,如:‘scheduler_manager’
- 实例化
Service
对象 - 实例化过程中将manager对象导入(
scheduler.manager
)赋值给实例中的manager
以上代码可以在cinder.service.Service.create
以及cinder.service.Service.__init__
中看到:
# cinder.service.Service.create(binary='cinder-scheduler')
if not topic:
topic = binary # 此时topic也变成了cinder-scheduler
if not manager:
subtopic = topic.rpartition('cinder-')[2] # ('', 'cinder-', 'scheduler'),此时subtopic为'scheduler'
manager = CONF.get('%s_manager' % subtopic, None) # manager = 'scheduler_manager'
# ......省略无关代码
service_obj = cls(host, binary, topic, manager,
report_interval=report_interval,
periodic_interval=periodic_interval,
periodic_fuzzy_delay=periodic_fuzzy_delay,
service_name=service_name)
return service_obj
# cinder.service.Service.__init__
self.manager_class_name = manager
manager_class = importutils.import_class(self.manager_class_name)
manager_class = profiler.trace_cls("rpc")(manager_class)
self.manager = manager_class(host=self.host,
service_name=service_name,
*args, **kwargs)
manger的初始化代码不再贴出,主要就是加载了sechduler_driver
3.加载service
# 加载service
service.serve(server)
这行代码加载并启动刚刚创建的Service
对象,先来个总结:
- 使用
Service Launcher.launch_service
启动对象 - 实际是开一个线程执行
Service.start
方法
下面对主要代码进行跟踪:
# cinder.service.serve
_launcher = service.launch(CONF, server, workers=workers)
launch方法主要逻辑如下:
# ......省略无关代码
if workers is None or workers == 1:
# 由于workers传了None,走这里
launcher = ServiceLauncher(conf, restart_method=restart_method)
else:
launcher = ProcessLauncher(conf, restart_method=restart_method)
# 最终执行这个方法***
launcher.launch_service(service, workers=workers)
return launcher
launch_service逻辑:
def launch_service(self, service, workers=1):
# ......省略无关代码
self.services.add(service)
...
def add(self, service):
"""Add a service to a list and create a thread to run it.
:param service: service to run
"""
self.services.append(service)
# 这里开启一个线程执行self.run_service
self.tg.add_thread(self.run_service, service, self.done)
...
@staticmethod
def run_service(service, done):
"""Service start wrapper.
:param service: service to run
:param done: event to wait on until a shutdown is triggered
:returns: None
"""
try:
# 最终执行的是service的start方法
service.start()
except Exception:
LOG.exception('Error starting thread.')
raise SystemExit(1)
else:
done.wait()
根据代码跟踪知道,service的加载过程可以看作开启一个子线程执行service的start方法。后面看其他组件启动看到service.serve就可以直接去看service的start逻辑。
service的start方法:
def start(self):
...
# 调用manager的init_host,scheduler manager没有实现,空逻辑
self.manager.init_host()
...
self.rpcserver = rpc.get_server(target, endpoints, serializer)
self.rpcserver.start()
self.manager.init_host_with_rpc()
4. 流程图
三、cinder-volume启动流程
cinder-volume启动流程与volume-scheduler大差不离,主要区别在于:先判断有没有配置backend,如果配置了多个,对于每个backend,都启动一个独立的cinder-volume进程;在没有配置backend的情况下启动流程与scheduler无异。
if CONF.enabled_backends:
for backend in CONF.enabled_backends:
CONF.register_opt(host_opt, group=backend)
backend_host = getattr(CONF, backend).backend_host
host = "%s@%s" % (backend_host or CONF.host, backend)
try:
# 创建service
server = service.Service.create(host=host,
service_name=backend,
binary='cinder-volume')
except Exception:
msg = _('Volume service %s failed to start.') % host
LOG.exception(msg)
else:
session.dispose_engine()
# 加载并启动service
launcher.launch_service(server)
service_started = True
else:
# 没有配置backend,直接创建
server = service.Service.create(binary='cinder-volume')
launcher.launch_service(server)
service_started = True
总结
以上就是cinde-scheduler的启动流程,放上主要代码及注释,有时候真的不知道怎么将这些代码思路组织成语言😭,憋字憋的难受,如有错误欢迎指教😘
后面有更好的想法再补充