【vdsm 源代码漫步】vdsm的存储(1)

  1. 存储管理是vdsm主要功能之一。代码在vdsm/storage下,主要功能是细化管理存储设备,为虚拟机提供相应的磁盘空间。“供给方”是块存储的lun,nfs文件存储提供的存储目录。块存储主要是IPSAN/FC; glusterfs, 本地存储都是文件存储。“需求方“主要是虚拟机的磁盘。当采用块存储时候,一个lun会被做成一个VG,虚拟机的磁盘文件会被做成LV。当采用文件存储时候,主要是生成qcow2模式或者raw模式的文件供虚拟机使用。当然在这个过程中也会生成一些管理类的文件,如master, inbox,xlease 等等。
  2. 除了虚拟机会对自己的磁盘有写入操作,vdsm对存储的管理也会有一些落盘操作,包括写入元数据,创建一些目录等。这些落盘操作是通过两种方式进行的。对于块文件是通过directio.py里面的函数处理的,主要是进行了字节对齐。对于文件操作相对比较复杂。它调用了2个rpm包(本文暂称呼为ioprocess)。这是因为nfs下,经常会出现一种 D 状态。为了处理这种情况,专门创建了ioprocess处理包(主要是通过管理子进程,来避免这种情况)。vdsm并没有直接使用ioprocess rpm处理包,而是通过storage/outOfProcess.py 进行wrapper后,使用的。

[root@gluster22 vdsm]# rpm -qa |grep ioprocess

python3-ioprocess-1.4.2-1.el8.x86_64

ioprocess-1.4.2-1.el8.x86_64

3. storage 进程管理相对比较复杂一些。但是基本原理很简单,就是创建一个线程池,池里面有若干线程,这些线程只做一件事情,就是等待队列里面出现任务数据,然后执行任务函数。

  • 场景: engine界面上可能会要求创建/删除一个快照。这个时候任务执行时间可能会很长,所以任务不是同步的,并且不止需要执行一个函数。此时,执行的任务还是一个(taskid唯一),但是任务被分成了若干job.每个job执行一个函数。同时,在操作过程中,如果重启vdsmd,任务不能被丢失。而且如果被操作对象被占用,需要等待。
  • 代码: storage/task.py storage/taskManager.py threadpool.py storage/dispatcher.py hsm.py vdsmd.py
  • 漫步: (1)我们进入hsm.py, 会找到好多类似 _spmSchedule, _hsmSchedule 的函数。这些函数都调用到了self.taskMng.scheduleJob 函数。我们进入taskManager.py, 会发现scheduleJob只是将job加入了task,至于什么时候得到执行,那是另一个故事了。(2)望文生义,taskManager就是管理task的类而已。先看__init__函数,最重要的一件事就是生成了一个threadpool,也就是生成了一个线程池。函数queue, queueRecovery 本质上都是在调用 self.tp.queueTask,即将task放入到threadpool里面的一个队列里。(3)我们先来看threadpool.py. 这个文件很简单,就是生成一堆线程,并将其启动

           newThread = WorkerThread(self, name)

           newThread.start()

而这些线程WorkerThread不停的做什么事情呢?

concurrent.thread(self.run, name=name) —— (包含在WorkerThread.__init__)

self._processNextTask()—————————–(包含在self.run)

self.__pool.getNextTask()—————————–包含在self._processNextTask

cmd(args)—————————–包含在self._processNextTask

显然,所有线程都在监控一个队列 self.__tasks = queue.Queue(maxTasks),一旦发现队列里面有task任务就会自动执行。换句话说将任务放到Queue后,只要线程空闲就会得到 执行。(4)那么task是什么时候放到queue中的呢。显然 调用taskManager中queue函数,就会将task放到queue中。再进一步,什么时候会调用queue函数。是在一个内部函数 def __state_queued(self, fromState)(task.py内部)中被调用。(5)我们进一步来查看task.py文件。文件中最重要的一个函数叫做_updateState,它会根据当前的task状态,自动进入下一个状态,关键代码:

fn = getattr(self, “_Task__state_%s” % state)

fn(fromState)

fn是所有 类似 __state_xxxxx的函数,其中xxxxx是状态。显然,task实现了一个自动状态机。简单而精巧。taskManager的queue函数,会将task和commit,放到threadpool的queue里面去执行,commit会自动调用状态机。 状态机的起始状态是init–>prepare–>……(见参考文献三)当task对象被创建时候自动就是init状态。所以整个状态机的启动有赖于外部调用prepare函数。从而使得任务得到开始执行。

(7)由以上分析,我们发现scheduleJob仅仅是简单的加入任务,并没有处理task状态机。事实上关键代码在storage/dispatcher.py中。

irs = Dispatcher(HSM()) —->vdsmd.py

__init__(self, obj):—–>dispatcher.py

self._exposeFunctions(obj)—–>dispatcher.py

setattr(self, funcName, self.protect(funcObj, funcName))—–>dispatcher.py

ctask = task.Task(id=None, name=name)—–>dispatcher.py

result = ctask.prepare(func, *args, **kwargs)—–>dispatcher.py

根据 以上代码关键流程,我们发现只要一个函数被_EXPORTED_ATTRIBUTE进行了装饰,那么一个任务将被创建,并且被修饰函数被推入函数的状态机执行。什么样子的函数会被_EXPORTED_ATTRIBUTE修饰呢。我们进入hsm.py文件,查看文件中的public函数,会发现public调用了exported函数,从而hsm.py中任何被public修饰的函数,最终会被_EXPORTED_ATTRIBUTE修饰,从而以任务的形式被执行。

(6) taskManager.py以及task.py里面有很大一部分涉及到recovery相关,主要是在重启vdsmd等操作时候,能够将task元数据保存,恢复之后,重新执行task。当然也包含一些撤销操作。

(7)一个task可能会包含很多Job。每一个Job才是一个基本的执行单元。而Task其实是一个管理单元。

4. 参考文献:

(1) https://www.cnovirt.com/archives/636

(2)https://www.ibm.com/developerworks/cn/cloud/library/1209_xiawc_ovirt/index.html

(3)https://www.ovirt.org/develop/developer-guide/vdsm/tasks.html

0 条回复 A 作者 M 管理员 E
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论