swift bug 调试记(wsgi.input)

第一次指定纠删码策略,修改了一部分swift代码后,执行PUT object,就被一个bug拦住。产生bug代码段如下:

try:
    with ChunkReadTimeout(self.client_timeout):
        mime_documents_iter = iter_mime_headers_and_bodies(
                request.environ['wsgi.input'],
                mime_boundary, self.network_chunk_size)
        _junk_hdrs, obj_input = next(mime_documents_iter)
except ChunkReadTimeout:
    return HTTPRequestTimeout(request=request)

在执行第三行iter_mime_headers_and_bodies()过程中出错,代码会最终定位至eventlet.wsgi的Input类中_chunked_read()函数,抛出一个ValueError异常。异常全文如下:

ValueError: invalid literal for int() with base 16: ''

而Input类中_chunked_read()函数相关代码如下:

def _chunked_read(self, rfile, length=None, use_readline=False):
        ......
            while self.chunk_length != 0:
                ......
                if maxreadlen > 0:
                    ......
                else:
                    self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
                    self.position = 0
                    if self.chunk_length == 0:
                        rfile.readline()
        except greenio.SSL.ZeroReturnError:
            pass
        return b''.join(response)

Google了一阵后,大致了解这个问题,一步步尝试解决。

eventlet版本原因导致?

首先利用apt-cache命令查看了本机上安装的eventlet版本信息。apt-cache用于查看相应软件包确切名称及详细信息。

apt-cache show python-eventlet

果然,显示Version: 0.9.7-0ubunutu1,而在swift项目中requirement.txt内容如下:

dnspython>=1.9.4
eventlet>=0.16.1,!=0.17.0
greenlet>=0.3.1
netifaces>=0.5,!=0.10.0,!=0.10.1
pastedeploy>=1.3.3
simplejson>=2.0.9
six>=1.9.0
xattr>=0.4
PyECLib==1.0.7    

明确规定eventlet版本为0.16.1以上。马上通过源码安装eventlet 0.16.1版本,重新运行swift服务。结果……依旧抛出异常!
人为比较了一下两个版本eventlet.wsgi,发现Input类具体实现确实有区别,但没能解决我的问题。我甚至尝试直接修改eventlet相应库文件,但依旧无效。两版本eventlet最大区别字符串前加b,具体解释可见字符串知识

配置错误?

这个猜想主要根据List Openstack上关于此错误的一些回答
然而当我尝试调整object-server.conf中间eventlet_debug后,异常依旧。

回归错误本身

多方尝试无果,重新回来思考这个错误。
其实错误行代码本身提示,便是在读取某个类似文件的对象一行后,尝试用“;”进行切分,取切分后生成列表的第一个元素,但问题是切分后列表的第一个元素为空,根本无法以16进制形式转换为整数。
这个readline()为空的问题究竟如何产生?难道是six模块版本太老?或是真的是配置错误?还是我删掉了一些必要的代码?抑或是我用dd命令生成的测试文件有问题?
多方尝试,耗费整整一天,依然无果。第二天升级编译python-six模块,并利用其它大文件做PUT object测试,问题依旧。在耗费两天毫无进展,自己都快发疯时,注意到了一个问题,那便是proxy server会返回状态码503然后输出如下信息:

Object PUT returning 503, 1/5 required connections

在源码中查询搜索了这条错误信息,发现只有在_check_min_conn()函数使用默认msg参数时,会输出这样的错误信息。而这样的调用仅在_check_failure_put_connections()函数的最末发生过一次!!!
到这里,总算发现解决问题的曙光。根本问题就出在纠删码策略PUT object过程中_store_object()调用_check_failure_put_connections()函数时(line. 2200)。我重读了这部分代码,发现_check_min_conn()函数中检查链接过程,实际上是将纠删码策略存取成功所需最新链接数min_conns和成功建立的链接conns数量相比较,后者要大于等于前者。即:
len(conns) >= min_conns
其中min_conns = policy.quroum_size,与选用的存储策略相关,quorum_size在相应的存储策略中均有定义(NRW策略)。比如我选用(4,2)RS码进行编码,即有:quorum_size = 4 + 2/2 = 5。而所有连接建立成功情况下,len(conns)值应为选定存放数据的结点数,应为6,其中nodes初始值由object_ring给出。但在我修改后的代码中,仅成功创建了一个链接,即len(conns) == 1

追根溯源

我终于明白是怎么回事。最初我看到了标示不同数据段的boundary,便理所当然认为proxy obj controller 和 object server之间仅会建立起一个链接,然后将所有的数据段传输上去,在我修改后的代码中,所有的数据放在一个http链接上进行传输,自始至终只成功建立了一个http链接。所有的一切,源于我对swift纠删码策略的一个错误认知:将boundary进行分区的在网络上进行传输的数据段(fragments)和要放置在一个具体结点上的相应数据块(由许多fragments组成)混淆了,boundary是用来区分不同网络数据段,并不是用来区分传输至不同object-server的多个数据块!!!
要保证纠删码策略正常,proxy和每个object-server间必须建立起相应的链接,而我只建立成功了一个链接,object-server端仍旧在等待真正的对象数据传输过去,而proxy obj controller这边确因为_check_min_conn()不合格而返回503,接下来的数据传输过程自然无从谈起,object-server苦等良久,却得不到需要的数据,这也就解释了为何对request.environ['wsgi.input']中数据处理到后来因为空而报错!也就是:

ValueError: invalid literal for int() with base 16: ''

后尝试注释掉obj controller中_check_min_conn(),代码果真一路向下正常运行,顿感心情舒畅。
关于最初在网络上搜索到的同类问题,我亦怀疑问题并不是出在接收数据方,而是数据发送方。没有具体研究过,仅写出我的经验,供参考。

参考:

[1]List Openstack上关于此问题的回答
[2]launchpad上关于此问题的研究
[3]nova中出现的类似错误
[4]ssync-reciver中类似问题

原文地址:https://www.cnblogs.com/qiyukun/p/4816676.html