Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15188
  • 博文数量: 4
  • 博客积分: 20
  • 博客等级: 民兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-12 21:55
文章分类

全部博文(4)

文章存档

2015年(4)

我的朋友

分类: 云计算

2015-11-11 21:09:21

先来说问题场景:
由于我们需要对虚拟机进行限速,但又不是纯粹的限速,而是低速率的报文走高优先级队列,高速率的报文走低优先级队列
同时,qos规则不能设置在虚拟机内的网卡上,只能设置在对应的tap网卡上,这就导致我们只能用ingress qos来限制虚拟机出方向报文的速率
这就导致了我们没法直接使用openvswitch已有的qos功能来进行设置,而只能使用linux tc自己的功能

问题发生时的现象:(基本为以下3类)
1. 虚拟机创建的时候,偶然发生qos规则丢失的情况
2. 虚拟机重启的时候,以很高的概率出现qos规则丢失的情况
3. 虚拟机创建之后,隔了一段时间,qos规则莫名丢失

去尝试复现的时候发现如下规律:
单台虚拟机基本上无论如何重启,规则都不会消失;
两台以上的虚拟机,如果重启其中一台,可能会导致另外一台虚拟机的qos规则消失;

在做了无数次尝试后,终于排除了我们自己写的qos代码问题,转而怀疑是不是libvirt和nova本身有问题
使用命令sudo virsh destroy ; sudo virsh start 对虚拟机进行重启操作, 发现能复现
 更进一步,发现在使用命令sudo virsh destroy ; 的时候虚拟机B的qos规则就已经丢失

查看了libvirt的代码,发现在重启的时候,会调用ovs的命令行ovs-vsctl del-port tapxxxx
只要改命令行一调用, 就会出现qos规则被莫名删除的现象。 后来干脆不用nova reboot命令,而是直接使用
ovs-vsctl del-port命令,发现删除其他人网卡的时候,另外的虚拟机qos规则被干掉了

问题说完了,基本就可以确定是ovs qos和linux tc不兼容,但是为什么会这样呢?
官网下载最新的2.3.2版本代码(当时我调试的时候是该版本最新),重新编译后,开始单步调试
   主要涉及到以下几个接口:
   iface_configure_qos:每次端口配置发生变化后,都会调用这个函数
   netdev_linux_set_policing: qos ingress队列加删的接口
   netdev_linux_update:while循环调用,在add port到ovs bridge的时候,接收RTM_NEWLINK后会设置端口标志
  一个重要的flag: VALID_POLICING

来回答几个问题:
1.为什么ovs只影响ingress队列,而不影响egress队列?
static void
iface_configure_qos(struct iface *iface, const struct ovsrec_qos *qos)
{
    struct ofpbuf queues_buf;


    ofpbuf_init(&queues_buf, 0);


    if (!qos || qos->type[0] == '\0') {
        netdev_set_qos(iface->netdev, NULL, NULL);
    } else {
...
}
因为我们没有用ovs 配置qos,所以此处qos为null
   而后在函数netdev_set_qos->netdev_linux_set_qos中,发现type为null,
   因此new_ops = tc_lookup_ovs_name(type), 此处的new_ops为tc_ops_default,
   该变量的set_queue的callback函数为null,因此qos 规则不会被执行。
   (主要是ovs的egress队列支持htb和hfsc两种,因此默认不配的话会被认为是什么都不用执行)

2.
为什么将tapA从ovs bridge里删除,会导致tapB的ingress规则丢失?
   为什么一定要两个虚拟机重启后再去重启其中一台才能复现?
   为什么一定要这个顺序才能复现?(add-port tapA; add-port tapB; set ingress qdisc to tapA&tapB; del-port tapA; tapB ingress qdisc disappear)
   这3个问题其实是同一个问题,奥秘都在函数netdev_linux_set_policing和netdev_linux_update。
   netdev_linux_set_policing()
  {
    if (netdev->cache_valid & VALID_POLICING) {
        error = netdev->netdev_policing_error;
        if (error || (netdev->kbits_rate == kbits_rate &&
                      netdev->kbits_burst == kbits_burst)) {
            /* Assume that settings haven't changed since we last set them. */
            goto out;
        }
        netdev->cache_valid &= ~VALID_POLICING;
    }
    /* Remove any existing ingress qdisc. */
    error = tc_add_del_ingress_qdisc(netdev_, false);
   if (kbits_rate) {
        error = tc_add_del_ingress_qdisc(netdev_, true);
        ...
    }
   ...
   out:
    if (!error || error == ENODEV) {
        netdev->netdev_policing_error = error;
        netdev->cache_valid |= VALID_POLICING;
    }
  }
 
   netdev_linux_update()
{
  if (change->nlmsg_type == RTM_NEWLINK) {
    netdev->cache_valid &= VALID_DRVINFO
  }
  ...
}

第一步加tapA的流程:
   iface_configure_qos
     ->netdev_linux_set_policing(tapA, rate=0, burst=0)
       由于此时netdev->cache_valid=0,
       因此会走一遍tc_add_del_ingress_qdisc(netdev_, false);这就是add-port时会将端口的tc规则删除的原因
       全部走完后,netdev->cache_valid |= VALID_POLICING
   由于该端口属于system口,因此加完后,内核会发一个RTM_NEWLINK的消息过来
   netdev_linux_update
     -->netdev_linux_changed: netdev->cache_valid = VALID_DRVINFO
 
  第二步加tapB的流程:
  同样的最后tapB网卡: netdev->cache_valid = VALID_DRVINFO
  但是tapB网卡增加的时候,会继续在函数iface_configure_qos对tapA网卡进行操作
  此时的tapA流程:
  iface_configure_qos
    ->netdev_linux_set_policing(tapA, rate=0, burst=0)
    由于netdev->cache_valid 并没有置位VALID_POLICING, 因此会直接走函数tc_add_del_ingress_qdisc(netdev_, false);
    然后在最后将netdev->cache_valid |= VALID_POLICING
 
  第三步删tapA网卡:
  同样的删除tapA后,会继续在函数iface_configure_qos对tapB网卡进行操作
  此时的tapB由于netdev->cache_valid 并没有置位VALID_POLICING, 因此会直接走函数tc_add_del_ingress_qdisc(netdev_, false);
  这就是tapB网卡的ingress规则被删除的原因
 
  如果反过来删tapB网卡,受影响的tapA网卡由于标志位VALID_POLICING被置上,因此判断当前的rate和burst和存储的一样,直接go out
  跳过了del ingress qdisc的接口。
  其他的更多网卡情况可以照此分析。

至此,问题的原因已经找到了,但是怎么解决?
其实到目前为止,openvswitch社区一直没有一个很明确的方法来解决这个问题,只是在讨论当中。
此处,笔者给出自己的一个方案,算抛装引玉吧
@@ -689,6 +689,8 @@ netdev_linux_update(struct netdev_linux *dev,
 {
     if (rtnetlink_type_is_rtnlgrp_link(change->nlmsg_type)){
         if (change->nlmsg_type == RTM_NEWLINK) {
+            int old_flag = dev->cache_valid;
+
             /* Keep drv-info, in4, in6. */
             netdev_linux_changed(dev, change->ifi_flags,
                                  VALID_DRVINFO | VALID_IN4 | VALID_IN6);
@@ -706,6 +708,10 @@ netdev_linux_update(struct netdev_linux *dev,
                 dev->ether_addr_error = 0;
             }


+            if (old_flag & VALID_POLICING){
+                dev->cache_valid |= VALID_POLICING;
+            }
+
             dev->ifindex = change->if_index;
             dev->cache_valid |= VALID_IFINDEX;
             dev->get_ifindex_error = 0;
@@ -1991,6 +1997,10 @@ netdev_linux_set_policing(struct netdev *netdev_,
                    : !kbits_burst ? 1000 /* Default to 1000 kbits if 0. */
                    : kbits_burst);       /* Stick with user-specified value. */


+    if (!kbits_rate && !(netdev->cache_valid & VALID_POLICING)){
+        return 0;
+    }
+
     ovs_mutex_lock(&netdev->mutex);
     if (netdev->cache_valid & VALID_POLICING) {
         error = netdev->netdev_policing_error;
@@ -2025,6 +2035,8 @@ netdev_linux_set_policing(struct netdev *netdev_,
                     netdev_name, ovs_strerror(error));
             goto out;
         }
+
+        netdev->cache_valid |= VALID_POLICING;
     }


     netdev->kbits_rate = kbits_rate;
@@ -2033,7 +2045,6 @@ netdev_linux_set_policing(struct netdev *netdev_,
 out:
     if (!error || error == ENODEV) {
         netdev->netdev_policing_error = error;
-        netdev->cache_valid |= VALID_POLICING;
     }
     ovs_mutex_unlock(&netdev->mutex);
     return error;

简单点来说,就是把flag VALID_POLICING作为ingress policing是否使能的标志
阅读(1754) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~