== Based on Kilo ==
大致看了启动过程,很多细节还不清楚。又贴了很多代码,仅作为一个记录。
启动命令
devstack下的启动命令:
/usr/local/bin/cinder-backup --config-file /etc/cinder/cinder.conf
内容为:
#!/usr/bin/python
# PBR Generated from u'console_scripts'
import sys
from cinder.cmd.backup import main
if __name__ == "__main__":
sys.exit(main())
就是执行cinder/cmd/backup.py中的main方法。
也很短:
"""Starter script for Cinder Volume Backup."""
import sys
import warnings
warnings.simplefilter('once', DeprecationWarning)
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
eventlet.monkey_patch()
from cinder import i18n
i18n.enable_lazy()
# Need to register global_opts
from cinder.common import config # noqa
from cinder import service
from cinder import utils
from cinder import version
CONF = cfg.CONF
def main():
CONF(sys.argv[1:], project='cinder',
version=version.version_string())
logging.setup(CONF, "cinder")
utils.monkey_patch()
server = service.Service.create(binary='cinder-backup')
service.serve(server)
service.wait()
主要就是这三行:
1. server = service.Service.create(binary='cinder-backup')
2. service.serve(server)
3. service.wait()
这里就是所谓的Service-Manager框架,有三篇很好的博客做了分析:hackerain的博客、bingotree的博客、sammyliu的博客。
下面我准备参考网上的资料自己走一遍这个流程。
- 第1行调用Service的create方法创建了一个server。参数只指定了
binary
,事实上不止这么多,很多都是用CONF
这个模块读取配置文件或者使用默认参数。这一块回头再看(关于host
取值就在这里)[To-Do] - 第2行调用service的serve方法,来serve创建的server。
作用是初始化rpc相关的信息,并放入eventlet协程中。到这一步为止,cinder-backup相关的exchange、queue、consumer都创建出来了,用rabbitmqctl命令可以看到。 - 最终启动的service都是eventlet中的协程,第3行启动service(也就是rabbitmq的consumer)监听消息。
一个一个来看code。
启动流程
1. 创建server
from cinder import service
server = service.Service.create(binary='cinder-backup')
所谓的Service就是rabbitmq consumer,看其说明据说是listening to queues based on topic。
从Service对象开始研究,它定义了一些方法,如:start, create, kill, stop, wait, periodic_tasks, report_state, basic_config_check。
其中,create方法用@classmethod
修饰,所以不用实例化就可以直接调用(上面就是这样用的),在create方法内部再实例化Service对象。
来看看这个create方法:
# cinder/service.py
@classmethod
def create(cls, host=None, binary=None, topic=None, manager=None,
report_interval=None, periodic_interval=None,
periodic_fuzzy_delay=None, service_name=None):
"""Instantiates class and passes back application object.
:param host: defaults to CONF.host
:param binary: defaults to basename of executable
:param topic: defaults to bin_name - 'cinder-' part
:param manager: defaults to CONF.<topic>_manager
:param report_interval: defaults to CONF.report_interval
:param periodic_interval: defaults to CONF.periodic_interval
:param periodic_fuzzy_delay: defaults to CONF.periodic_fuzzy_delay
"""
if not host:
host = CONF.host # 如果不指定,CONF.host会取主机hostname
if not binary:
binary = os.path.basename(inspect.stack()[-1][1])
if not topic:
topic = binary # topic怎么理解?类似rabbitmq里面的exchange topic?思考:service本质上是consumer,最多会创建queue并指定exchange。Kombu中需要consumer也创建exchange,否则无法指定。
if not manager:
subtopic = topic.rpartition('cinder-')[2] # 这里是'backup'
manager = CONF.get('%s_manager' % subtopic, None) # 这里是'cinder.backup.manager.BackupManager'
# 以下3个参数不知道干嘛的,以后再研究
if report_interval is None:
report_interval = CONF.report_interval
if periodic_interval is None:
periodic_interval = CONF.periodic_interval
if periodic_fuzzy_delay is None:
periodic_fuzzy_delay = CONF.periodic_fuzzy_delay
# 调用Service的__init__创建Service对象
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
create方法的参数会指定在哪个host上启动处理哪个topic的service,启动之后真正干活(处理消息)的是那个manager(python类)。还有一些periodic task不是很清楚。
其中:
- host - 可以在cinder.conf中指定;如果不指定,则取主机名。
该host就是cinder service-list中看到的“Host”。后续rpcapi.py中的self.client.prepare中指定的host就是这个参数。 - binary - 传入时已经指定,如cinder-backup, cinder-volume, cinder-scheduler
- topic - 可以在cinder.conf中指定;如果不指定,则和binary同名
manager
真正干活的类。这里的manager类是(cinder.conf未指定):ipdb> manager 'cinder.backup.manager.BackupManager' ipdb> CONF.get("volume_manager") 'cinder.volume.manager.VolumeManager' ipdb> CONF.get("back_manager") *** NoSuchOptError: no such option: back_manager ipdb> CONF.get("backup_manager") 'cinder.backup.manager.BackupManager' ipdb> CONF.get("api_manager") *** NoSuchOptError: no such option: api_manager ipdb> CONF.get("scheduler_manager") 'cinder.scheduler.manager.SchedulerManager'
最不好理解的是topic:
topic是oslo_messaging里面的topic,不是amqp里面的exchange类型。
oslo_messaging wiki:
a topic is a identifier for an RPC interface; servers listen for method invocations on a topic; clients invoke methods on a topic
从这个描述来看,topic就是一个标识(identifier)。在server端,topic标识queue <–> consumer的关系;在client端,topic标识publisher <–> exchange的关系。
Nova RPC文档中有个经典的图(里面还有几个UserCase,好好看看!):
从这个图中也可以看到,topic标识了message的整个通路。
To-do:代码层面,这个topic是怎么实现的?
- publisher,比如cinder-api,调用cinder/backup/rpcapi.py中的方法,构造msg。这个msg会指定发送到“openstack”这个exchange上(这一步不清楚,也有可能发送的default exchange上。最好能打印msg),其routing_key=cinder-backup.maqi-kilo(因为prepare方法指定了server=host)。调用的方法名称为“create_backup”。这一步中,“topic”就是“cinder-backup”
- “openstack”这个exchange是topic类型,他会分析routing_key。这里的routing_key没有通配符,那就完全匹配,匹配到叫做cinder-backup.maqi-kilo的queue上。
- “cinder-backup.maqi-kilo”这个queue的consumer也叫“cinder-backup.maqi-kilo”。这个consumer上暴露了多个方法(也就是endpoints),其中一个就是“create_backup”。
- consumer “cinder-backup.maqi-kilo”接收消息并处理。
create方法中最终调用cls(…)来实例化并返回Service对象,其初始化方法如下:
# cinder/service.py
from cinder.objects import base as objects_base
from cinder.openstack.common import loopingcall
from cinder.openstack.common import service
from cinder import rpc
from cinder import version
class Service(service.Service):
"""Service object for binaries running on hosts.
A service takes a manager and enables rpc by listening to queues based
on topic. It also periodically runs tasks on the manager and reports
it state to the database services table.
"""
def __init__(self, host, binary, topic, manager, report_interval=None,
periodic_interval=None, periodic_fuzzy_delay=None,
service_name=None, *args, **kwargs):
super(Service, self).__init__()
# 初始化rpc
# 主要根据配置得到TRANSPORT、serializer、NOTIFIER
if not rpc.initialized():
rpc.init(CONF)
self.host = host # 默认为主机名
self.binary = binary # cinder-backup
self.topic = topic # 默认等于binary,为cinder-backup
self.manager_class_name = manager
manager_class = importutils.import_class(self.manager_class_name) # 动态地import manager类
manager_class = profiler.trace_cls("rpc")(manager_class) # osprofile相关
self.manager = manager_class(host=self.host,
service_name=service_name,
*args, **kwargs)
self.report_interval = report_interval
self.periodic_interval = periodic_interval
self.periodic_fuzzy_delay = periodic_fuzzy_delay
self.basic_config_check() # Perform basic config checks before starting service
self.saved_args, self.saved_kwargs = args, kwargs
self.timers = []
setup_profiler(binary, host)
所做的主要工作是:
初始化rpc:
根据配置得到TRANSPORT(’rabbit’, ‘qpid’, ‘zmq’)、serializer、NOTIFIER。这些都是oslo_messaging里面的概念。transport可以理解为用哪种mq。实例化manager类
主要就是这几行:
self.manager_class_name = manager # 'cinder.backup.manager.BackupManager'
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)
# ipdb> manager_class
# <class 'cinder.backup.manager.BackupManager'>
# ipdb> self.host
# 'maqi-kilo'
# ipdb> service_name
# ipdb> args