设为首页 收藏本站
查看: 1515|回复: 0

[经验分享] xen 添加网卡设备初步分析之 network-attach 流程分析

[复制链接]

尚未签到

发表于 2015-10-11 13:13:39 | 显示全部楼层 |阅读模式
  本文从 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,如果在迁移前,手动将 ID2 (或者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文件XendDomainInfodevice_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方法,根据面向对象的原则,其调用父类DevControllercreateDevice方法。
  
1.2.2.1         获取前后端信息
  接着在DevControllercreateDevice方法中,看到如下代码:
  
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中获取一个网卡IDsaveddevid从系统配置中获取网卡ID,这个地方可能有点不太清楚,详细说明如下:
  如果网卡是新增的,那么saveddevid None;但如果是执行虚拟机内部重启、迁移等过程,可想而知,重启迁移等动作之前的网卡,在重启迁移后,网卡的配置信息是不会丢失的,这说明系统中会保存网卡的配置信息,诸如macid等信息,这些信息在重启迁移等动作之后不会丢失。上述变量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()。此时再回到在DevControllerallocateDeviceID()方法,因为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.readxenstore中读取 nextDeviceID的当前值,该值就是xenstore自动分配的网卡设备ID。同时,通过t.write将加一后的nextDeviceID值回写到xenstore中。到这里我们基本清楚xenstore网卡ID的分配机制,但对于xenstore的内部分配尚有不明之处,对此,我们继续深入,分析下xenstore里面的分配细节及相关通信机制。
1.2.2.1.2       Xenstore ID分配相关的内部通讯
  我们从xen.xend.xenstore.xstransact类开始,关注下t.readt.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()readwrite方法,最终调用的还是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,同时会将nextDeviceID1
  
  当虚拟机发生迁移、重启等动作后,nextDeviceID会被置0,网卡设备会重新注册到虚拟机,但是会沿用网卡原来的ID
  
  如果在迁移前,手动将 ID 2 (或者0-3)的网卡删除,迁移后再添加网卡就会发生故障。假设迁移前删除ID2的网卡,此时剩余网卡ID 0,1,3,4。迁移后,nextDeviceID=0,此时0,1,3,4的网卡重新注册到虚拟机,01,3,4网卡依次注册后,nextDeviceID的值依次是1,2,3,4,即nextDeviceID总是从0开始,每次加1的增长。这个过程不会有问题,问题会发生在下一次添加网卡的过程。这个过程需要明白,0,1,3,4网卡重新注册后,他们的ID0,1,3,4,而不是0,1,2,3,因为它们原来是有ID的,所以不使用nextDeviceID的当前值。
  
  当用户下次添加新网卡时,由于新网卡显然是不存在ID的,此时nextDeviceID决定网卡的ID,因为当前nextDeviceID=4,所以分配给新网卡的ID4,此后更新nextDeviceID=5。此时问题来了,因为ID4的网卡已经在系统中,故发生ID冲突,导致网卡添加失败。这就是迁移后第一次添加网卡失败的原因。
  
  用户第二次添加网卡时,上次已经更新nextDeviceID=5,此时分配给新网卡的ID5,因为当前nextDeviceID=5,此后更新nextDeviceID=6。由于系统中不存在ID5的网卡,所以添加成功。这就是第二次添加网卡成功的原因。
  
  对于上述结论,有几点需要说明:
  第一:虚拟机内部关闭或者迁移等动作后,nextDeviceID的值会清0,这是通过实验和加打印得出的结论,具体代码点没有深入追究。
  第二:虚拟机内部关闭或者迁移等动作后,网卡会依次重新向xenstore中注册,原有网卡的配置信息不会丢失。
  第三: 一旦nextDeviceID被分配出去后,如果设备最终没有创建成功,代码中不会对nextDeviceID的值进行回滚处理,目前未发现该机制有问题。
  
  xenstore得知前后端路径后,加上之前获取的网卡的配置信息,将相应的信息写入到前后端中。具体代码不再列出。
1.2.3          等待设备
  可以推测,xenstore通过向前后端发送消息后,前端和后端会根据驱动的相关功能,完成具体设备的创建,这部分功能体现在PV驱动里面,xen中所做的就是发送消息给前后端,以及等待他们的是否成功的响应。有关PV驱动前后端实际虚拟设备的创建,将在单独的文档中进行说明。
  此时回到xend/XendDomainInfo.py文件XendDomainInfodevice_create函数的_waitForDevice调用中。由于有了前面的介绍,我们不再走弯路,waitForDevice直接最终位于DevController类中。
  waitForDevice调用waitForBackend函数,waitForBackend函数中执行回调函数hotplugStatusCallback,并设置超时时间,在超时规定的时间内,观察前后端设备是否创建并连接成功,并将最后结果返回,最后根据返回结果做出相应的处理。
  【故障解决方案】:本文就不给出具体解决方案。理解该错误的原理后,解决的办法有许多。一个思路是:每次获取自动分别的ID后,可以与旧网卡的ID进行比较,如果前者要小,则继续获取,当然这样也会有其他的缺点,不再进一步描述。
  后话:
  通过写此文档,我发现对于添加网卡设备而言,xen中所做的事情其实不多,但是并不容易描述,因为这小块功能起点于一个比较庞大的框架之上,牵扯面广。后面真正需要花功夫啃的还是xenstore的整体通信机制,以及PV驱动前后端的虚拟设备创建过程。
  
         版权声明:本文为博主原创文章,未经博主允许不得转载。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-125459-1-1.html 上篇帖子: Xen安装与使用 下篇帖子: Xen and the Art of Virtualizati-系统所翻译
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表