oVirt (Open Virtual) 之 VDSM 学习笔记 vdsm/vdsm (一)

简介:

VDSM 是 oVirt 项目中的一个重要部分,oVirt 可以参见 oVirt 官网:http://www.ovirt.org/。

VDSM 可以通过 oVirt 官网找到,也可通过 GIT 仓库下载:git clone http://gerrit.ovirt.org/p/vdsm.git。

VDSM 模块开始运行的文件是: vdsm/vdsm。是 Python 语言编写的。

Let's go!

 1 loggerConfFile = constants.P_VDSM_CONF + 'logger.conf'
 2 
 3 if config.getboolean('devel', 'python_warnings_enable'):
 4     warnings.simplefilter("always")
 5     if hasattr(logging, 'captureWarnings'):
 6         # Python 2.6 does not have captureWarnings
 7         logging.captureWarnings(True)
 8 
 9 
10 if __name__ == '__main__':
11     try:
12         main()
13     except FatalError as e:
14         syslog.syslog("VDSM failed to start: %s" % e)
15         # Make it easy to debug via the shell
16         raise

这是一个通用的写法, 如果某个模块是被通过关键字 import 导入的,那么其 __name__ 属性将是模块名。

如果是以通过 Python 解释器直接执行,那么 __name__ 属性将为:'__main__'。对于此种写法,代码:

if __name__ == '__main__':

部分的代码一般用作测试此模块代码。“ vdsm/vdsm ” 将被 Python 直接解释执行,所以将执行下面的代码。

调用 main() 方法,main() 方法将是我们关注的对象。而 syslog.syslog 是调用了 syslog 模块的 syslog()

方法,记录日志。

小技巧:

查看 Python 代码时,对于这种导入库的使用,可以采用启动 Python 解释器后,通过 “ import <模块名> ”,

再使用“ help(<模块名>) ”的方式查看其帮助手册。

出现异常的时候,使用 raise 来讲程序挂起,便于调试(O__O 哥,从代码注释都能看出来的呢)。

Main 开始:

3个断言( assert )语句,用于保证程序运行的基本条件;

根据配置 [vars] 里的 'core_dump_enable' 值来设置程序跑飞(DUMP/CORE)时的事件记录资源设置;

设置组ID,保证运行,然后..., 跑。

 1 def main():
 2     __assertVdsmUser()
 3     __assertLogPermission()
 4     __assertSudoerPermissions()
 5 
 6     if not config.getboolean('vars', 'core_dump_enable'):
 7         resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
 8 
 9     if os.getsid(0) != os.getpid():
10         # Modern init systems such as systemd can provide us a clean daemon
11         # environment. So we setpgrp only when run by traditional init system
12         # that does not prepare the environment.
13         os.setpgrp()
14     run()

断言:

__assertVdsmUser(): 用于保证程序运行的用户和用户组为:vdsm/kvm。

1 def __assertVdsmUser():
2     username = getpass.getuser()
3     if username != constants.VDSM_USER:
4         raise FatalError("Not running as %r, trying to run as %r"
5                          % (constants.VDSM_USER, username))
6     group = grp.getgrnam(constants.VDSM_GROUP)
7     if (constants.VDSM_USER not in group.gr_mem) and 
8        (pwd.getpwnam(constants.VDSM_USER).pw_gid != group.gr_gid):
9         raise FatalError("Vdsm user is not in KVM group")

__assertLogPermission(): 用于保证程序具有对日志文件的写的权限。

 1 def __assertLogPermission():
 2     if not os.access(constants.P_VDSM_LOG, os.W_OK):
 3         raise FatalError("Cannot access vdsm log dirctory")
 4 
 5     logfile = constants.P_VDSM_LOG + "/vdsm.log"
 6     if not os.path.exists(logfile):
 7         # if file not exist, and vdsm has an access to log directory- continue
 8         return
 9 
10     if not os.access(logfile, os.W_OK):
11         raise FatalError("Cannot access vdsm log file")

__assertSudoerPermissions(): 用于保证程序具有调用某些特权命令(如:/sbin/multipath)的权限。

 1 def __assertSudoerPermissions():
 2     rc = 1
 3     with tempfile.NamedTemporaryFile() as dst:
 4         # This cmd choice is arbitrary to validate that sudoes.d/50_vdsm file
 5         # is read properly
 6         cmd = [constants.EXT_CHOWN, "%s:%s" %
 7                (constants.VDSM_USER, constants.QEMU_PROCESS_GROUP), dst.name]
 8         rc, _, stderr = utils.execCmd(cmd, sudo=True)
 9 
10     if rc != 0:
11         raise FatalError("Vdsm user could not manage to run sudo operation: "
12                          "(stderr: %s). Verify sudoer rules configuration" %
13                          (stderr))

从注释中可以注意到到 sudoes.d/50_vdsm 就是其特权命令的规则描述。

红帽( RedHat )系列Linux 为 /etc/sudoes.d/50_vdsm。

基本配置:

config.getboolean('type', 'des'): 将读取如下格式的配置文件,并返回“ = ”后面的值。

main() 函数里是设置程序DUMP(如 C 语言工程师讨厌的:段错误)的时候,内核将对其问题

追踪产生一些资源(文件),对程序的资源进行限制。

# 模板
# [type]
# des=xxx

[vars] core_dump_enable=True
# Comments [string] name=YBHello addr=ChengDu

  

跑:

检查会话( Session ) ID 是否和进程 ID 相等:

如果相等,等价于此程序是一后台程序运行,使用 init 机制可以保证(注释里都这样说的);

如果不等,将进程自己的 ID ( PID )设置为组 ID。

【使用旧版本的 service vdsmd restart 的方式重启,程序部分日志将写到终端】

因为 VDSM 的需要这样的设置来保证环境(注释翻译)。

1     if os.getsid(0) != os.getpid():
2         # Modern init systems such as systemd can provide us a clean daemon
3         # environment. So we setpgrp only when run by traditional init system
4         # that does not prepare the environment.
5         os.setpgrp()
6     run()

run() 起来:

输入:pidfile 将被写入 vdsm 程序的 PID 的值。

日志配置文件的处理(日志配置文件为: etc/vdsm/logger.conf)

添加一个 TRACE 日志级别(作者说:这很粗鲁但很有用 O__O "…)

导入 VDSM 调试插件

导入 覆盖率测试 模块(参见代码覆盖率测试)

将进程的 PID 写入 pidfile 中(pidfile 作为 run() 函数的参数传入),并记录日志

调用  serve_clients(log) ,正常运行下,serve_clients 不会退出(将继续追踪下去)

如果 serve_clients(log) 返回了,将所有的线程关闭

 1 def run():
 2     try:
 3         lconfig.fileConfig(loggerConfFile, disable_existing_loggers=False)
 4     except RuntimeError as e:
 5         raise FatalError("Cannot configure logging: %s" % e)
 6 
 7     logging.addLevelName(5, 'TRACE')
 8     logging.TRACE = 5  # impolite but helpful
 9 
10     # Used to enable code coverage. On production machines
11     # "coverage" should not exists and COVERAGE_PROCESS_START should not be
12     # set.
13     try:
14         import coverage
15         coverage.process_startup()
16     except ImportError:
17         pass
18 
19     log = logging.getLogger('vds')
20     try:
21         logging.root.handlers.append(logging.StreamHandler())
22         log.handlers.append(logging.StreamHandler())
23 
24         sysname, nodename, release, version, machine = os.uname()
25         log.info('(PID: %s) I am the actual vdsm %s %s (%s)',
26                  os.getpid(), dsaversion.raw_version_revision, nodename,
27                  release)
28 
29         __set_cpu_affinity()
30 
31         serve_clients(log)
32     except:
33         log.error("Exception raised", exc_info=True)
34 
35     log.info("VDSM main thread ended. Waiting for %d other threads..." %
36              (threading.activeCount() - 1))
37     for t in threading.enumerate():
38         if hasattr(t, 'stop'):
39             t.stop()
40         log.info(str(t))

serve_clients():

添加 SIGTERM、SIGUSR1 的信号处理函数

概要信息统计线程开始运行(包括:CPU 和内存两部分)

开启与 libvirt 的事件循环处理(线程)

根据配置文件的 [irs] 的 irs_enable 的值设置环境

scheduler(调度)实例线程

cif(Client InterFace )实例线程

开启周期执行线程

等待信号到来,如果信号到来,停止各个线程,函数放回,程序将结束

 1 def serve_clients(log):
 2     cif = None
 3     irs = None
 4     scheduler = None
 5     running = [True]
 6 
 7     def sigtermHandler(signum, frame):
 8         log.debug("Received signal %s" % signum)
 9         running[0] = False
10 
11     def sigusr1Handler(signum, frame):
12         if irs:
13             log.debug("Received signal %s" % signum)
14             irs.spmStop(
15                 irs.getConnectedStoragePoolsList()['poollist'][0])
16 
17     sigutils.register()
18     signal.signal(signal.SIGTERM, sigtermHandler)
19     signal.signal(signal.SIGUSR1, sigusr1Handler)
20     zombiereaper.registerSignalHandler()
21 
22     profile.start()
23 
24     libvirtconnection.start_event_loop()
25 
26     try:
27         if config.getboolean('irs', 'irs_enable'):
28             try:
29                 irs = Dispatcher(HSM())
30             except:
31                 utils.panic("Error initializing IRS")
32 
33         from clientIF import clientIF  # must import after config is read
34         cif = clientIF.getInstance(irs, log)
35 
36         install_manhole({'irs': irs, 'cif': cif})
37 
38         scheduler = schedule.Scheduler(name="vdsm.Scheduler",
39                                        clock=utils.monotonic_time)
40         scheduler.start()
41         cif.start()
42         periodic.start(cif, scheduler)
43         try:
44             while running[0]:
45                 sigutils.wait_for_signal()
46 
47             profile.stop()
48         finally:
49             periodic.stop()
50             cif.prepareForShutdown()
51             scheduler.stop()
52     finally:
53         libvirtconnection.stop_event_loop(wait=False)