cinder-backup启动过程跟踪

本文详细解析了OpenStack Cinder-Backup服务的启动过程,包括创建server、启动流程和服务的内部工作机制。从Service对象的创建到manager_class的实例化,再到RPC相关组件的初始化,逐一剖析了topic、exchange、queue和consumer的创建。通过分析代码,揭示了Cinder-Backup如何监听和处理消息。
摘要由CSDN通过智能技术生成

== 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,好好看看!):
enter image description here
从这个图中也可以看到,topic标识了message的整个通路。

To-do:代码层面,这个topic是怎么实现的?

  1. 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”
  2. “openstack”这个exchange是topic类型,他会分析routing_key。这里的routing_key没有通配符,那就完全匹配,匹配到叫做cinder-backup.maqi-kilo的queue上。
  3. “cinder-backup.maqi-kilo”这个queue的consumer也叫“cinder-backup.maqi-kilo”。这个consumer上暴露了多个方法(也就是endpoints),其中一个就是“create_backup”。
  4. 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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值