vdsm源码阅读
2024-02-26 15:13:18

简介

VDSM 是虚拟化管理器(如 oVirt 引擎或 Red Hat Enterprise Virtualization Manager)所需的守护进程,用于管理 Linux 主机及其 KVM 虚拟机客户机。VDSM 管理和监视主机的存储、内存和网络,以及虚拟机的创建、其他主机管理任务、统计数据收集和日志收集。 –来自 ovirt 官网

简单点说, VDSM 是一个运行在物理机上的守护进程,对外暴露了一系列接口,方便上层应用在物理机上创建虚拟机,修改物理机的网络配置,创建存储等。

背景

之前有段时间,公司里让加急学习 ovirt 相关技术,而我被分配去熟悉 VDSM 的项目,虽然时间时间已经过去了很久,想了一想,还是有必要做下记录,方便以后有需要时查看。因为只是粗略的介绍一下,所以直接跳过编译、单测以及各种代码规范等。

代码入口

**注意,本文代码基于 VDSM v4.4.100.2**,新版本或者旧版本中的实现可能不一致,注意甄别。

阅读 VDSM 源码注意,VDSM 中大量使用了 python 的魔术方法,动态的加载方法,很多时候你发现某个方法在 IDE 中找不到引用,那大概率就是通过魔术方法+字符串的方式来调用的,此时可以使用全局搜索,去全局搜索哪些地方有同名的方法

VDSM 的主要代码都在 /lib/vdsm 这个项目的相对路径下,其中包括了 api、common、hook、super-vdsm等等多个模块,而整个项目在安装完成以后,至少会有两个 service(由 systemd 管理,可用 systemctl 查看服务状态),第一个是 vdsmd,第二个是 super-vdsmd

这里对 super-vdsm 只简单介绍一下,vdsm 是由 vdsm 用户启动的守护进程,而 super-vdsm 是由 root 用户启动的守护进程,super-vdsm 主要用户执行一些需要 root 权限的操作,比如对物理机的网络进行配置,比如增加路由,创建虚拟网卡等。

vdsmd.py 是 VDSM 的入口,其中 main() 方法调用了 run() 启动了 vdsm 了,在 run 中,主要的逻辑在serve_clients(log) 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try:
logging.root.handlers.append(logging.StreamHandler())
log.handlers.append(logging.StreamHandler())

sysname, nodename, release, version, machine = os.uname()
log.info('(PID: %s) I am the actual vdsm %s %s (%s)',
os.getpid(), dsaversion.raw_version_revision, nodename,
release)

try:
__set_cpu_affinity()
except Exception:
log.exception('Failed to set affinity, running without')

serve_clients(log)
1
cif = clientIF.getInstance(irs, log, scheduler)

进入 serve_clients,有 libvirtconnection.start_event_loop() 用于监听 libvirt 事件,这里点击函数跟进去就可以看到。然后

1
2
3
scheduler = schedule.Scheduler(name="vdsm.Scheduler",
clock=time.monotonic_time)
scheduler.start()

这里创建了一个调度器,这个调度器是 VDSM 自己设计的,晦涩难懂,有功夫的可以自己好好看一看,我写不明白,简单理解就是类似 Go 的调度器,VDSM 把自己的任务提交以后,由它自己去执行,监控任务进度,以及返回任务状态。

1
2
3
4
5
6
7
8
from vdsm.clientIF import clientIF  # must import after config is read
cif = clientIF.getInstance(irs, log, scheduler)

jobs.start(scheduler, cif)

install_manhole({'irs': irs, 'cif': cif})

cif.start()

这里的 cif.start() 是重点,首先从 clientIF 获取了一个示例,然后执行了 start(),注意,这里将前面初始化的 scheduler 以参数的形式传递了进去。

让我们跳转到 clientIF.py 去看里面都做了什么。

clientIF.py 的 _init. 方法,初始化了 VDSM 的大部分内容,比如锁

1
2
3
4
self.vm_container_lock = threading.Lock()
self.vm_start_stop_lock = threading.Lock()
self._networkSemaphore = threading.Semaphore()
self._shutdownSemaphore = threading.Semaphore()

还启动了 RPC 以及 HTTP 服务器

1
2
self._prepareHttpServer()
self._prepareJSONRPCServer()

这里的 _prepareJSONRPCServer 进去,有一个 self._acceptor.add_detector()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def _prepareJSONRPCServer(self):
if config.getboolean('vars', 'jsonrpc_enable'):
try:
from vdsm.rpc import Bridge
from vdsm.rpc.bindingjsonrpc import BindingJsonRpc
from yajsonrpc.stompserver import StompDetector
except ImportError:
self.log.warn('Unable to load the json rpc server module. '
'Please make sure it is installed.')
else:
bridge = Bridge.DynamicBridge()
json_binding = BindingJsonRpc(
bridge, self._subscriptions,
config.getint('vars', 'connection_stats_timeout'),
self._scheduler, self)
self.servers['jsonrpc'] = json_binding
stomp_detector = StompDetector(json_binding)
self._acceptor.add_detector(stomp_detector)

在 VDSM,官方只有 http server 和 rpc server,但是实际上这里是预留出来了接口的,在 clientIF.py 的 _createAcceptor() 方法中,使用了一个 MultiProtocolAcceptor(),只要你实现了它的 def detect(self, data) 以及 def handle_socket(self, client_socket, socket_address),那么就可以实现一个自己的协议的 server。

各个模块

API

网络模块,对外提供调用的方法,基本都在 lib/vdsm/network/api.py 这个文件中,其中有一些方法通过 supervdsm_api 的方式暴露出去,供 ovirt-engine,或者其它的 client 去调用。在 api.py 中,又会通过调用 network 模块下的其它模块,去完成需要的操作。

Common

代码中的路径为 lib/vdsm/network/common,里面提供了一些通用的方法。

configurators

lib/vdsm/network/configurators,提供了一些使用 linux 中的 tc (一个命令行工具)去修改网络配置,以实现主机 qos 的方法。

Ip

lib/vdsm/network/ip,提供验证 nameserver,验证 ip 地址格式是否合法等的方法。

Lldp

获取所有设备的 lldp 信息,lldp 是一个链路层发现协议,用户发现交换机端口等,详细概念自己谷歌。

Lldpad

通过 python 的 cmd.exec_sync() 执行 shell 命令,包括 /usr/sbin/lldptool、/usr/bin/systemctl,去获取 lldpad.service 的状态信息,暴露出方法供前一个模块 lldp 调用。

Netinfo

提供跟网络信息有关的方法,包括获取主机的 ip 地址信息,获取主机 bond 信息 Linux 网卡 Bonding 配置 ,获取主机上的 bridges,添加网络设备的 qos 等等。

对 netlink 的封装。

Netswitch

对主机网络的操作,其中包括一个主要的用于配置网络的方法, lib/vdsm/network/netswitch/configurator.py 中的 setup()。

Nmstate

使用 nmstate 完成各种跟网络配置相关的操作,提供方法供其它模块调用

Tc

调用 linux 主机上的 tc 命令,完成网络配置,lib/vdsm/network/tc/_wrapper.py。

1
2
3
4
5
6
7
8
EXT_TC = '/sbin/tc'
_TC_ERR_PREFIX = 'RTNETLINK answers: '
_errno_trans = dict(((os.strerror(code), code) for code in errno.errorcode))

def process_request(command):
command.insert(0, EXT_TC)
retcode, out, err = cmd.exec_sync(command)
if retcode != 0:

参考

Linux 高级路由与流量控制手册