|
本文从 xm network-attach命令着手,逐步分析xen 平台添加网卡的整体流程。需要说明的是,本文档只介绍针对在PV驱动环境下前后端网卡设备的添加流程。对于非PV驱动下的全虚拟化设备,不予关注。
1 流程分析
Xm network-attach是添加网卡设备的命令,该命令是用python语言编写,相关源文件位于主机/usr/lib64/python2.6/site-packages/xen/目录下。具体在分析流程时,也是逐步在源文件中添加打印信息,以达到一步步揭开该命令的执行流程。
1.1 问题描述
由于相关内容涉及面广、牵扯面多,本文可能无法面面俱到,所以先给出一个中心点,即当时遇到的故障,基于这个问题,在解决此问题的基础上,以求相对全面的分析xen添加网卡设备的流程。
【故障】该故障会发生在迁移、虚拟机内部重启,外部reboot中。假设当前虚拟机有五块网卡,ID分别是 0,1,2,3,4,如果在迁移前,手动将 ID为2 (或者0-3)的网卡删除,迁移后再添加网卡就会发生报告“Device xx is already connected”的错误。此后如果继续添加网卡,又会添加成功。
1.2 命令流程
1.2.1 总体入口
命令流程都是通过逐步在python脚本中添加打印得出的。首先看如下代码:
xend/server/XMLRPCServer.py 文件中 XMLRPCServer类中代码:
try:
while self.running:
self.server.handle_request()
finally:
self.shutdown()
所有 xm 命令的入口都是 while语句处,至于之前的一些初始化流程,那些过程属于xend的初始化流程,对于xend整体的消息流程框架,本文暂时不深入追究。针对xm network-attach命令而言,最终执行命令的入口处位于:
xend/XendDomainInfo.py文件XendDomainInfo类device_create函数中。
给出调用树关系如下:
XendDomainInfo.device_create (self, dev_config)
self._createDevice(dev_type, dev_config_dict)
self._waitForDevice(dev_type, devid)
下面分别介绍上面几个主要函数。
1.2.2 创建设备
创建设备调用的函数如下:
def _createDevice(self, deviceClass, devConfig):
return self.getDeviceController(deviceClass).createDevice(devConfig)
查看xend/XendDomainInfo.py文件,无法简单得出self.getDeviceController(deviceClass)的结构。通过添加打印得出,self.getDeviceController返回的类是xen.xend.server.netif.NetifController。在 tools/python/xen/xend/server/netif.py文件中,可以看到类class NetifController(DevController)的源代码。
显然,self.getDeviceController(deviceClass).createDevice(devConfig)即调用
NetifController. createDevice(devConfig)。由于NetifController类自己没有createDevice方法,根据面向对象的原则,其调用父类DevController的createDevice方法。
1.2.2.1 获取前后端信息
接着在DevController的createDevice方法中,看到如下代码:
def createDevice(self, config):
"""Trigger the creation of a device with the given configuration.
@return The ID for the newly created device.
"""
(devid, back, front) = self.getDeviceDetails(config)
self.getDeviceDetails获取前后端网卡的相关配置信息,这里需要暂时回到NetifController类中,因为DevController中的getDeviceDetails只是一个空接口,具体函数在子类NetifController中。
def getDeviceDetails(self, config):
"""@see DevController.getDeviceDetails"""
script = config.get('script', xoptions.get_vif_script())
……此处省略
if not mac:
raise VmError("MAC address not specified or generated.")
devid = self.allocateDeviceID() --- 从xenstore获取网卡ID
saveddevid = config.get('devid') – 从系统中获取网卡ID
if saveddevid is not None:
log.info("change devid %d -> int(%s)", devid, str(saveddevid))
devid=int(saveddevid)
……此处省略
return (devid, back, front)
对于前文所说的故障而言,这个地方是一个关键点。该函数主要在获取新增网卡设备的配置信息。红色部分的代码尤其关键,devid从 xenstore中获取一个网卡ID,saveddevid从系统配置中获取网卡ID,这个地方可能有点不太清楚,详细说明如下:
如果网卡是新增的,那么saveddevid是 None;但如果是执行虚拟机内部重启、迁移等过程,可想而知,重启迁移等动作之前的网卡,在重启迁移后,网卡的配置信息是不会丢失的,这说明系统中会保存网卡的配置信息,诸如mac、id等信息,这些信息在重启迁移等动作之后不会丢失。上述变量saveddevid就是从系统中读取这类网卡的配置信息。如果saveddevid非空,则不使用xenstore自动分配的id,而使用原有的配置ID;相反,如果saveddevid为空,则使用xenstore自动分配的id。到这里,逻辑上是没有任何问题的。
顺便说一下上述函数的返回值,根据打印信息得出:
devid 2 {'mac': '00:16:3e:6d:06:72', 'handle': '2', 'uuid': 'bec8d569-5594-7265-fcc8-9914283d95de', 'script': '/etc/xen/scripts/vif-bridge'} {'mac': '00:16:3e:6d:06:72', 'handle': '2'}分别对应(devid, back, front)。
下面详细分析下xenstore自动获取网卡配置ID的函数。
1.2.2.1.1 Xenstore自动分配网卡id的机制
主要的函数就是上述代码中的allocateDeviceID()。此时再回到在DevController的allocateDeviceID()方法,因为NetifController类没有该方法,其调用父类DevController中的方法。
def allocateDeviceID(self):
path = self.frontendMiscPath()
return complete(path, self._allocateDeviceID)
complete 来自xen.xend.xenstore.xstransact类,代码如下:
def complete(path, f):
while True:
t = xstransact(path)
try:
result = f(t)
if t.commit():
return result
except:
t.abort()
raise
def _allocateDeviceID(self, t):
result = t.read("nextDeviceID")
if result:
result = int(result)
else:
result = 0
t.write("nextDeviceID", str(result + 1))
return result
结合上面三段代码看,最后分配的函数实际是在 _allocateDeviceID函数中。该函数调用t.read从xenstore中读取 nextDeviceID的当前值,该值就是xenstore自动分配的网卡设备ID。同时,通过t.write将加一后的nextDeviceID值回写到xenstore中。到这里我们基本清楚xenstore网卡ID的分配机制,但对于xenstore的内部分配尚有不明之处,对此,我们继续深入,分析下xenstore里面的分配细节及相关通信机制。
1.2.2.1.2 Xenstore 与ID分配相关的内部通讯
我们从xen.xend.xenstore.xstransact类开始,关注下t.read与t.write的大致机制。由于xenstore本身的复杂性,本文不会对该内容面面俱到。详细请参考《xen虚拟化技术》一书中的第8.5节。
在xenstore.py 文件中查看 xstransact的调用处:
def complete(path, f):
while True:
t = xstransact(path) ---调用 transaction_start
try:
result = f(t) – 调用主体功能,此处是_allocateDeviceID
if t.commit():-- 调用 transaction_end
return result
except:
t.abort()
raise
在每次调用complete时,都会调用xstransact,该类的构造函数如下:
def __init__(self, path = ""):
self.in_transaction = False # Set this temporarily -- if this
# constructor fails, then we need to
# protect __del__.
assert path is not None
self.path = path.rstrip("/")
self.transaction = xshandle().transaction_start()
self.in_transaction = True
上述代码中的xshandle()来自xen.xend.xenstore.xsutil,xshandle()函数定义如下:
def xshandle():
global xs_handle, xs_lock
if not xs_handle:
xs_lock.acquire()
if not xs_handle:
xs_handle = xen.lowlevel.xs.xs()
xs_lock.release()
return xs_handle
根据xshandle的代码结构看出,其中的xs_handle是一个全局变量,非常类似一个“单例模式”,并不是每次都申请都初始化,相反,它初始化一次,却会一直存在。进一步到xen.lowlevel.xs.xs()代码中,此时已经到了python代码与C代码的结合之处,可以看出,这里已经是通过python来调用C的功能,完成xenstore的相关功能。
xenstore本质上是一个文件系统,所有对xenstore的操作与对linux下的文件操作类似,包括读、写、创建目录、设置文件权限等,对应的消息类型如下:(来自xen.lowlevel.xs.xs):
static PyMethodDef xshandle_methods[] = {
XSPY_METH(read, METH_VARARGS),
XSPY_METH(write, METH_VARARGS),
XSPY_METH(ls, METH_VARARGS),
XSPY_METH(mkdir, METH_VARARGS),
XSPY_METH(rm, METH_VARARGS),
XSPY_METH(get_permissions, METH_VARARGS),
XSPY_METH(set_permissions, METH_VARARGS),
XSPY_METH(watch, METH_VARARGS),
XSPY_METH(read_watch, METH_NOARGS),
XSPY_METH(unwatch, METH_VARARGS),
XSPY_METH(transaction_start, METH_NOARGS),
XSPY_METH(transaction_end, METH_VARARGS | METH_KEYWORDS),
XSPY_METH(introduce_domain, METH_VARARGS),
XSPY_METH(set_target, METH_VARARGS),
XSPY_METH(resume_domain, METH_VARARGS),
XSPY_METH(release_domain, METH_VARARGS),
XSPY_METH(close, METH_NOARGS),
XSPY_METH(get_domain_path, METH_VARARGS),
{ NULL /* Sentinel. */ },
};
xenstore的消息通讯机制依旧是基于环,详细原理见《xen虚拟化技术》。
对于 t.read,t.write,主要功能代码如下:
def _read(self, key):
path = self.prependPath(key)
return xshandle().read(self.transaction, path)
def _write(self, key, data):
path = self.prependPath(key)
xshandle().write(self.transaction, path, data)
通过xshandle()的read和write方法,最终调用的还是xen.lowlevel.xs.xs文件中对应的读写方法。通过添加打印,我们得出path的信息:
path /local/domain/55/device-misc/vif/nextDeviceID
这与前文所述,xenstore是一个文件系统吻合起来,path就是读写的路径,通过执行xshandle().read或者xshandle().write方法对文件中的路径进行读写,限于篇幅,本文不再继续罗列,读者可以继续往下深入分析,建议阅读《xen虚拟化技术》相关章节。
实际上到这里,还没有真正的创建设备,所以xenstore只是一个中间的交互及存储通信通道,它本身只起到沟通和管理的作用。
这里给出一个结论:此时通过向xenstore中获取nextDeviceID值,或者写入nextDeviceID的值,不会对实际设备造成干扰,因为现在还处于分配设备信息的前期,根本还不存在实际设备。nextDeviceID的值仅仅决定下次新增网卡的ID值,该值对于网卡设备而言,在同一台虚拟机上必须是唯一的。
1.2.2.2 与前后端交互
回到DevController类的createDevice函数中,接下来还是通过xenstore进行交互,向PV驱动的前后端发送消息。
首先获取前后端的消息路径:
(backpath, frontpath) = self.addStoreEntries(config, devid, back,
front)
通过打印得出:
backpath /local/domain/0/backend/vif/55/2
frontpath /local/domain/55/device/vif/2
在通过xenstore写入前端之前,这里先进行了一次检查:
t = xstransact()
try:
if devid in self.deviceIDs(t):
if 'dev' in back:
dev_str = '%s (%d, %s)' % (back['dev'], devid,
self.deviceClass)
else:
dev_str = '%s (%s)' % (devid, self.deviceClass)
raise VmError("Device %s is already connected." % dev_str)
此时就与故障中的信息对应起来,故障发生时打印出的错误,就是在这里抛出的。
在新添加网卡时,self.deviceIDs(t)从xenstore中获取了原有网卡的ID值,如果发现目前分配的网卡ID已经存在于xenstore中,则抛出错误。还是假设故障中的场景。综合上面的分析,给出故障的原因:
xenstore 里面有变量/local/domain/domid/device-misc/vif/nextDeviceID,如果网卡原来没有ID,该值决定下次分配网卡的ID。如果网卡原来已经有ID,则使用网卡原来的ID。当每增加一块网卡,nextDeviceID均会加1。迁移,内部重启等动作,虚拟机的网卡ID是不会丢失的,即都会沿用原来的网卡ID。
当前虚拟机有五块网卡,ID 分别是 0,1,2,3,4 此时nextDeviceID = 5,即下次如果新添加网卡,网卡的ID将是5,同时会将nextDeviceID加1。
当虚拟机发生迁移、重启等动作后,nextDeviceID会被置0,网卡设备会重新注册到虚拟机,但是会沿用网卡原来的ID。
如果在迁移前,手动将 ID 为2 (或者0-3)的网卡删除,迁移后再添加网卡就会发生故障。假设迁移前删除ID为2的网卡,此时剩余网卡ID是 0,1,3,4。迁移后,nextDeviceID=0,此时0,1,3,4的网卡重新注册到虚拟机,0,1,3,4网卡依次注册后,nextDeviceID的值依次是1,2,3,4,即nextDeviceID总是从0开始,每次加1的增长。这个过程不会有问题,问题会发生在下一次添加网卡的过程。这个过程需要明白,0,1,3,4网卡重新注册后,他们的ID是0,1,3,4,而不是0,1,2,3,因为它们原来是有ID的,所以不使用nextDeviceID的当前值。
当用户下次添加新网卡时,由于新网卡显然是不存在ID的,此时nextDeviceID决定网卡的ID,因为当前nextDeviceID=4,所以分配给新网卡的ID是4,此后更新nextDeviceID=5。此时问题来了,因为ID为4的网卡已经在系统中,故发生ID冲突,导致网卡添加失败。这就是迁移后第一次添加网卡失败的原因。
用户第二次添加网卡时,上次已经更新nextDeviceID=5,此时分配给新网卡的ID是5,因为当前nextDeviceID=5,此后更新nextDeviceID=6。由于系统中不存在ID为5的网卡,所以添加成功。这就是第二次添加网卡成功的原因。
对于上述结论,有几点需要说明:
第一:虚拟机内部关闭或者迁移等动作后,nextDeviceID的值会清0,这是通过实验和加打印得出的结论,具体代码点没有深入追究。
第二:虚拟机内部关闭或者迁移等动作后,网卡会依次重新向xenstore中注册,原有网卡的配置信息不会丢失。
第三: 一旦nextDeviceID被分配出去后,如果设备最终没有创建成功,代码中不会对nextDeviceID的值进行回滚处理,目前未发现该机制有问题。
xenstore得知前后端路径后,加上之前获取的网卡的配置信息,将相应的信息写入到前后端中。具体代码不再列出。
1.2.3 等待设备
可以推测,xenstore通过向前后端发送消息后,前端和后端会根据驱动的相关功能,完成具体设备的创建,这部分功能体现在PV驱动里面,xen中所做的就是发送消息给前后端,以及等待他们的是否成功的响应。有关PV驱动前后端实际虚拟设备的创建,将在单独的文档中进行说明。
此时回到xend/XendDomainInfo.py文件XendDomainInfo类device_create函数的_waitForDevice调用中。由于有了前面的介绍,我们不再走弯路,waitForDevice直接最终位于DevController类中。
waitForDevice调用waitForBackend函数,waitForBackend函数中执行回调函数hotplugStatusCallback,并设置超时时间,在超时规定的时间内,观察前后端设备是否创建并连接成功,并将最后结果返回,最后根据返回结果做出相应的处理。
【故障解决方案】:本文就不给出具体解决方案。理解该错误的原理后,解决的办法有许多。一个思路是:每次获取自动分别的ID后,可以与旧网卡的ID进行比较,如果前者要小,则继续获取,当然这样也会有其他的缺点,不再进一步描述。
后话:
通过写此文档,我发现对于添加网卡设备而言,xen中所做的事情其实不多,但是并不容易描述,因为这小块功能起点于一个比较庞大的框架之上,牵扯面广。后面真正需要花功夫啃的还是xenstore的整体通信机制,以及PV驱动前后端的虚拟设备创建过程。
版权声明:本文为博主原创文章,未经博主允许不得转载。 |
|