2015年7月1日, 15年的一半过去了, 我也来发发上半年总结和下半年计划, 上半年新房下来了,到现在还没开始装修,我也是够不靠谱的,.一不留神还有了孩子, 这个比较神奇. 下半年要好好把房子收拾收拾, 建立一个家不容易啊. 未来的孩子, 你觉得咱家买个什么样的游戏机好呢? 下半年要学的东西好多, 优先级大概是这样的: openstack, linux, 算法, 网络, python, 对对对,还有英语. 另外要坚持锻炼, 我伟大的六块腹肌的梦, 要加油.
废话不说了, 切入正题.
之前用devstack安装neutron一直有这种那种问题, 所以也就没深究. 这次因为想学学新的openstack docker组件所以对原有的openstack进行了升级, 结果不出意外的, 环境又不好用了. 所以开始了漫漫debug之路. 说句题外话, 之前听到很多童鞋都不太喜欢devstack, 其中很多还都是大牛. 确实devstack这种东东对于大牛来说确实意义不大, 但是像我这样的小菜, 一套可以快速部署, 容易上手的openstack环境还是很有意义的, 而且自己的环境想咋搞咋搞, 还是不错的. 最重要的是, 所有的openstack组件都支持devstack, 如果你想尝试尝试什么新东西, 不得不说没有什么比devstack更方便的.
对localrc进行几轮修改后, 终于成功的安装了openstack, 网络部分的功能还没测试, 不过至少组件都安装成功了. 嘿嘿, 好景不长,页面创建实例报错, 页面显示类似数据错误啥啥的. 第一反应f**k, 好吧, 重装, 重装, 试了几次始终不行,好吧,我承认我很笨. 于是开始查看日志
首先, 我从horizion的日志入手, 仔细查看, 发现是cinderclient报的错误,日志大致如下:
2015-07-01 09:02:57.007233 File "/usr/lib/python2.7/dist-packages/cinderclient/client.py", line 302, in get
2015-07-01 09:02:57.007236 return self._cs_request(url, 'GET', **kwargs)
2015-07-01 09:02:57.007238 File "/usr/lib/python2.7/dist-packages/cinderclient/client.py", line 294, in
_cs_request
2015-07-01 09:02:57.007241 raise exceptions.ConnectionError(msg)
2015-07-01 09:02:57.007243 ConnectionError: Unable to establish connection: ('Connection aborted.',
BadStatusLine("''",))
检查cinderclient之后发现, 这部分代码请求了cinder-api(似乎是废话), 然后转到cinder-api的日志, 根据最后一条请求可以看出之前的函数调用了cinder/api/v2/snapshots.py
detail()函数, 这个时候我又茫然了, 代码是最新的, 没有做过什么修改, 所以考虑会不会是cinderclient的版本和新下cinder版本不对应. 所以我又很二的把cinderclient给删了, 重新安装了一次, 上帝保佑这么做不会对以后有什么影响. 结果不出意外的继续不好使, 这个时候我基本确定系统可能出了什么问题, 于是我转到社区, 用我蹩脚的英语发了第一个问题帖子, 并开始了焦急的等待. 大概是时差的问题把, 很久没有人回复, 倒是有几个viewer, 我想大概是我的描述还不够准确, 所以我试着开始debug代码, 看看能不能有更多的提示.
python的debug在我之前的帖子里有介绍过, 首先我在detail中加入如下代码, 启动pycharm的远程调试功能并重启c-api
def detail(self, req):
"""Returns a detailed list of snapshots."""
import pydevd
pydevd.settrace("127.0.0.1", port=5678)
return self._items(req, entity_maker=_translate_snapshot_detail_view)
pycharm很顺利的进入断点, 但是奇怪的是断点位置不对, 并且无法debug detail函数. 根据之前网上查到的资料,这很有可能是因为openstack使用的线程机制造成的(这部分后面也会做总结), 于是在cinder/cmd/api.py中顺利的找到了那只猴子, 并对猴子做了小小的修改, 注释掉的是原来的猴子, 长的是debug用的新猴子, 做过如下修改后,重启c-api,这次一切正常了,终于开始debug了.
#eventlet.monkey_patch()
eventlet.monkey_patch(all=False, socket=True, time=True, thread=False)
跟踪detail函数, detail调用_item(), 在item()调用如下方法时发生异常,这里需要注意的是python发生异常时不像在java中会直接输出到控制台,如果发现代码执行流跳转异常那很有可能就是发生异常了,通常发生异常时,函数会直接返回到它的调用者.
snapshots = self.volume_api.get_all_snapshots(context,search_opts=search_opts) #1
在继续跟踪过程中就凸显了debug的优势,一方面python的函数可能有多个实现,且可以随意指定参数,所以对于代码不是很熟悉的话和可能找不到到底调用的是哪个函数, 另一方面debug过程中我们可以看到所有变量的变化, 对于理解程序的执行非常有好处.以上函数#1实际调用如下
#cinder/volume/api.py #1
get_all_snapshots(self, context, search_opts=None):
check_policy(context, 'get_all_snapshots')
search_opts = search_opts or {}
if (context.is_admin and 'all_tenants' in search_opts): #2
# Need to remove all_tenants to pass the filtering below.
del search_opts['all_tenants']
snapshots = objects.SnapshotList.get_all(context,
search_opts)
else:
snapshots = objects.SnapshotList.get_all_by_project(
context, context.project_id, search_opts) #3
return snapshots
这里根据前台传来的参数, 代码应该进入if条件, 实际情况却走了else, 这里我还有意识到有问题, 直到后面继续执行我才发现了问题. 这里我们继续跟踪代码
#cinder/objects/snapshot.py #3
get_all_by_project(cls, context, project_id, search_opts):
snapshots = db.snapshot_get_all_by_project(context, project_id,
search_opts) #4
#cinder/db/api.py #4
snapshot_get_all_by_project(context, project_id, filters=None):
return IMPL.snapshot_get_all_by_project(context, project_id, filters) #5
#cinder/db/sqlalchemy/api.py #5
snapshot_get_all_by_project(context, project_id, filters=None):
authorize_project_context(context, project_id)
query = model_query(context, models.Snapshot)
if filters:
query = query.filter_by(**filters)
return query.filter_by(project_id=project_id).ptions(joinedload('snapshot_met adata')).all()
最终我们发现, 上面的query.fiter_by发生了异常. 而这个filter是根据**filters里面的key-value对查询结果进行过滤,观察变量发现filters的值为 MultiDict([(u'alltenants', u'1'), (u'tenant_id', u'6889d9c31549458b83a4316e97181e71')]), 而对象snapshot中根本没有这两个字段, 所以造成了错误. 于是开始返回去找这个filters从哪里传过来的,一直跟踪到#2的条件判断, 这时我们发现代码执行错误的原因是判断条件中使用的字段为all_tenants而dict中却保存的为alltenants造成代码执行路径有问题,导致后台错误.于是继续跟踪search_opts到底从哪里来的,很容易找到
_item():
search_opts = req.GET.copy()
找了这么久终于找到罪魁祸首了,这个search_opts是从url中获取的, 而这个url肯定是horizion生成的. 所以继续查找horzion的代码, 全文检索'alltenants', 终于我们在下面的代码中找到了问题
#horizon/openstack_dashboard/usage/quotas.py
def _get_tenant_volume_usages(request, usages, disabled_quotas, tenant_id):
if 'volumes' not in disabled_quotas:
if tenant_id:
opts = {'alltenants': 1, 'tenant_id': tenant_id}
把上面的alltenants修改为all_tenants后,重启horizion, 终于世界恢复了它应有的样子.
之后发现社区发的帖子也被回复了,问题和我找的一致 , 基本可以确定是个小bug, 能找到这个bug还是很兴奋的,本来想提到社区上, 后来觉得有点小题大做了, 就没发,其实主要是不知道怎么提.不过没关系,以后还有的是机会.
以上就是整个debug的过程, 还是挺有意思的, 总结几点, 遇到问题不能急, 多看看日志, 对代码的整体理解有助于解决问题, 最后平时还是要多多看看源码.