Cinder Volume 服务启动流程分析和周期性任务分析

Cinder Volume 服务启动流程分析和周期性任务分析


1. 服务启动流程


1) systemctl status openstack-cinder-volume        ---->       /usr/bin/cinder-volume
2) cat /usr/bin/cinder-volume                      ---->       from cinder.cmd.volume import main
3) 那么服务启动的起始位置就是 cinder.cmd.volume 的 main 函数
    a) 通过 _launch_services_posix 不难看出每个 backend 都是一个 launcher
       那么 launcher 又是什么?
        launcher = service.get_launcher()
        from cinder import service
        def get_launcher()                         ---->       def process_launcher():
            return service.ProcessLauncher(CONF, restart_method='mutate')

        from oslo_service import service
        launcher.launch_service(server)
            self._start_child(wrap)
            pid = os.fork()
        由此可以看出每个 backend 启动的 cinder-volume service 都是单独的子进程。
        即每个 launcher 都是一个子进程。

        def launch_service(self, service, workers=1):
        另一方面可以看出开启多少个子进程,可以通过 workers 参数来控制。

    b) _launch_service 是每个 backend 的启动主函数
        service 的 host 的命令:host = "%s@%s" % (backend_host or CONF.host, backend)
        server = service.Service.create
        from cinder import service

    c) def start(self)为服务启动入口,主要包含下面的内容(为啥是 start?待深入调查):
        1) self.manager.init_host,这边的详解看 2
            manager = CONF.get('%s_manager' % subtopic, None)
            volume_manager,default='cinder.volume.manager.VolumeManager'
        2) self.rpcserver.start()
        3) if self.report_interval: 默认值 10。这边详解看 3
            self.report_state
        4) if self.periodic_interval: 默认值 60。这边详解看 4
            self.periodic_tasks

2. init_host 过程分析

init_host 整个过程精简如下:
    try:
        driver.do_setup(ctxt)
        driver.check_for_setup_error()
    except Exception:
        LOG.exception()
        return
    driver.set_initialized()
    self.publish_service_capabilities(ctxt)

1) driver 的初始化在 VolumeManager 的 init 方法中。也就是说Cinder-volume 服务启动过程中,如果有任何一个卷驱动 init 失败,会导致服务启动失败(所有的卷驱动都启动失败)。
2) 把一些核心的和存储的初始化操作放到了 do_setup 中,当 do_setup 失败时,并不会导致服务启动失败,也不会影响 multi backend 的其他 backend。
3) 换一个角度就是说服务启动了,cinder service-list 显示正常了,并不表示这个 backend 服务可用,因为它的初始化未完成。那么这个时候就得看日志才能确定错误了。
4) set_initialized 将驱动的 initialized 属性设定为 true,标志驱动成功启动并初始化完成。
5) 这是一个完整的 cinder-volume 驱动初始化的过程,如果我们自己需要在代码中启动一个 volume driver,这就是一个很好的参考。K 版本的cinder-backup manager 就有类似的用法。
6) publish_service_capabilities,这边就是 cinder-volume 的周期性任务添加点。这边详解看 5

3. report_state

其实这个方法就是单纯的获取 service 列表,count +1 ,再更新到数据库。

分析代码找到之前遇到的一个 bug 的 fix:驱动初始化失败,但是 cinder service-list 却是 up。
https://bugs.launchpad.net/cinder/+bug/1446750
https://github.com/openstack/cinder/commit/2a84ae0fc015a43617521727ca31152066839d3c#diff-8f2683e56fe80449628d16fe5b4f9b37

这边补充下 Cinder-Scheduler 判断服务是 down 还是 up 过程如下:
    volume_services = db.service_get_all_by_topic(context,
                                                  topic,
                                                  disabled=False)
    def service_is_up(service):
        """Check whether a service is up based on last heartbeat."""
        last_heartbeat = service['updated_at'] or service['created_at']
        # Timestamps in DB are UTC.
        elapsed = (timeutils.utcnow() - last_heartbeat).total_seconds()
        return abs(elapsed) <= CONF.service_down_time
    就是对比数据库的 service 更新时间和现在的时间差,再跟配置文件可容忍的时间差进行比对。
    那么问题来了:service['updated_at'] 是什么时候怎么更新的呢?就是这儿做的更新。

4. periodic_tasks

self.manager.run_periodic_tasks
cinder.volume.manager.VolumeManager 没有实现该方法
父类manager.CleanableManager
    manager.SchedulerDependentManager
from cinder import manager
CleanableManager 无该方法,父类是 object
SchedulerDependentManager 无该方法,父类是 ThreadPoolManager -> 无该方法,父类是 Manager -> 无该方法,父类是 base.Base, PeriodicTasks
    from cinder.db import base base.Base 无该方法,父类是 object
    PeriodicTasks 无该方法,父类是 periodic_task.PeriodicTasks
        from oslo_service import periodic_task
        找到该方法的最终实现
        PeriodicTasks 是一个元类
self._periodic_tasks 一开始并没有任何值
所以这个函数实际上第一次并没有真正执行周期性任务
周期性任务的添加参 5

5. publish_service_capabilities

@periodic_task.periodic_task
def publish_service_capabilities(self, context):
    """Collect driver status and then publish."""
    self._report_driver_status(context)
    self._publish_service_capabilities(context)
通过这个装饰器只是将它加入周期性任务,周期性的周期性执行实际上是通过 1-3-c-4 来实现的
这边就涉及到一个元类的用法:
    1) 通过装饰器 @periodic_task.periodic_task 将需要周期性执行的任务再次包装为一个 periodic_task 对象 class-A。
    2) class-A 初始化的时候先找到了当前文件下的元类 class _PeriodicTasksMeta(type),通过它的 init 将 class-A 添加到周期性任务列表 cls._periodic_tasks.append((name, task))。
    3) 1-3-c-4 通过 loopingcall 初始化了周期性任务,并定时执行,那么这个时候 2 添加了新的任务,在下一次执行的时候就会开始执行周期性任务。

_report_driver_status
    volume_stats = self.driver.get_volume_stats(refresh=True)
    就是调用 volume driver 获取存储的容量情报
_publish_service_capabilities
    self.scheduler_rpcapi.update_service_capabilities
    使用 RPC 将容量情报推动到 cinder-scheduler
最新的代码已经将周期性任务的装饰器移动了位置,参:
https://review.openstack.org/#/c/550531/
    移动的原因是上面两个任务不同步,也就是 cinder-scheduler 获取都是上一次的容量情报,这边涉及到线程用法,详细看上述的代码说明
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值