分享

如何阅读openstack源代码,源代码阅读指导

pig2 发表于 2013-11-10 18:42:18 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 1 18465
本帖最后由 pig2 于 2014-1-25 01:49 编辑

OpenStack 本身用 python 语言编写,虽然我一直觉得自己的 python 功底已经不错了,但在看源码的过程中,还总是觉得自己掌握的东西太少了,所以,首要的一点,如果你在看 OpenStack 源码,请一定要打牢你的 python 基础,不然有些技巧性的代码可能让你停滞不前。

    看源码,如果能一气呵成最好。什么叫一气呵成呢?我先讲个每个人生活中都可能遇到的一些情况:你在做 A 事,但是突然 B 打电话,让你帮着解决 C 事,然后你就去做 C 事了,等你做完 C 事,发现家里有 D 事必须要做,然后你又去做 D 事……这样的结果就是,你把 A 事给遗忘了,即便你空闲时候想起来了,但是再去做的时候,发现没有第一次那样熟悉 A 事了,你需要重新花费一些时间来熟悉它。试想,如果一开始你就把 A 事做到底,会怎样?

    看源码其实是一个很漫长的过程,特别对一个大型项目而言,如果你要看完它的源码,过程是很曲折的,这里的看完不仅仅是过目了一遍,脑子里还要能把逻辑关系理顺。你可能有疑问了,要看源码,一天两天解决不了,但是又要保证一气呵成,这根本就是无稽之谈嘛!事情也的确是这样,鱼与熊掌不可兼得!这里就有一个技巧的问题了,你不妨想象,这么大一个项目,它是怎么开发出来的?难道一开始,项目就已经策划好了?需要多少个源文件,每个文件里面的源码是什么也都做好了?有点经验的程序员都知道,这是不可能的。项目的开发是慢慢细化的,一开始只是核心,然后是骨架,然后有血肉,然后有做 A 事的工具……到这里,或许你知道我要说什么了,源码怎么一步步写出来的,我们就怎么一步步的去看它。先研究核心,再研究骨架,然后血肉,其它工具……。还有一个问题,就算我知道怎么看这些源码,我怎么去一气呵成?这就好比你要完成一件大事情,但是你发现给自己定这么宏大的目标对自己来说比登天还难,所以你就想到用小目标来不断激励自己,最终不断接近大目标。这里的一气呵成既然不能一气把所有源码呵成,那就分段吧!不要心急,不要总想着还有很多源码都还没看,保持淡定!

    其实,看源码都是一样的,从架构处着手,然后慢慢扩展到细枝末叶。这里,说一些 utilities 。看源码是很枯燥的,一点都不形象不说,还要让脑子一直保持着源码中的很多东西,如果你想偷懒,如果你想让生活更简单,那就用图形吧,图形加速了整个 IT 的发展,它的强大与便利有目共睹。源码中的各个模块,类怎么耦合的,用了什么设计模式,拿张纸,画几笔,就显而易见了,当然,做个 PPT 更好。源码之间的互相交错是最让人头疼的,很多人一开始看源码,就从这个源文件的某个函数跳转到另一个源文件的某个函数,我想问一下,你以为你的大脑是电脑吗?你的大脑也可以像电脑那样按着调用顺序依次调用各个函数??如果你在看一个源文件,OK,先把这个源文件一气呵成再说,不要跳转到其它源文件,如果引用的其它源文件中的函数你不知道是干嘛的,先 pass ,以后再说,只要你知道调用它的函数是干嘛的就行,等你以后研究到另外一个源文件的时候,这个关系就很明确了。还有一个现象,很多人一接触一个项目的源码,看见那么多源文件,一下子就懵了,不知道如何下手,别人说,从 main 开始看,于是,他就从 main 开始看了,其实这个无所谓,还是那句话,不要以为你的大脑是电脑,做一些人脑力所能及的事,随便找个源文件,然后用心去看它,不要觉得这里的随便就是随便,虽然它的确是随便,但是如果你不知道我说的随便是哪个随便,那就只有随便你了。不管哪个项目,源码包中大致结构一看,基本上就知道各个东西大致是干嘛的,开发这些东西的也是人脑,不是电脑,为了方便理解,基本上文件取名都还是见名知意的。看源码是一件很有挑战的事情,对源码而言,记住,你永远都要站在它的对面,而不是将自己深埋进源码中,一旦你钻进去了,你就已经迷失了自己。

    上面说了那么多,都没有谈到 OpenStack ,其实这个是相辅相成的,上面的你知道了,看 OpenStack 你也应该没有问题了,OpenStack 的核心项目是 nova, glance, swift ,最核心的就是 nova 了,所以,从 nova 开始看吧。nova 源码包中有很多子包,源文件。除了版权版本以及和其它组件交互的东西,随便找一个开始看吧。切记,在开始看之前,最好能把你知道的 nova 架构图烂熟于心。 这个很重要,因为你之后随时有可能沉迷进源码大军中。

     貌似没有给冲着 OpenStack 源码来的读者一个很好的建议,其实,任何事都没有一蹴而就的方法,想做成它,最好的方法就是,保持淡定的心态,一步步,走下去!作为过来人,还是给个建议,从 虚拟化开始看,因为这个里面用到了适配器设计模式,你稍微看一点就知道了这个包是干嘛的了,而且,可以提升你继续看源码的信心。
    下面进一步给大家研究一下nova模块中的建立实例的实现过程的源码。肯定会有错误和不严谨的地方,需要大家给予指正。
  代码中是本人之前看源码时候写的注释,可能不是太规范,但是贴上来希望能帮助大家更好的理解源码;

    之前的命令行和配置文件解析这里暂时不详解。因为OpenStack的服务能兼容亚马逊的EC2/S3 API,所以可以应用EC2 API来建立一个新的实例,将会调用/nova/api/ec2/cloud.py中的run_instances方法:
  1. def run_instances(self, context, **kwargs):
  2. """
  3. 准备实例,并且发送实例的信息和要运行实例的请求消息到远程调度器scheduler;
  4. 实现实例的简历和运行,由调度器完成;
  5. """
  6. # 设置最小建立实例的数目;
  7. min_count = int(kwargs.get('min_count', 1))
  8. # 获取kwargs['kernel_id']指定的镜像image数据返回给kernel;
  9. # 获取更新的kwargs['kernel_id'];
  10. # 注:kernel_id为虚拟机内核ID值;
  11. if kwargs.get('kernel_id'):
  12. # _get_image:
  13. # context:上下文信息;
  14. # kwargs['kernel_id']:从参数信息中获取'kernel_id'值;
  15. kernel = self._get_image(context, kwargs['kernel_id'])
  16. # 根据kernel['id']查询数据库中匹配的S3镜像数据,获取它的uuid属性,并返回;
  17. # 返回匹配的db.models.S3Image.uuid给kwargs['kernel_id'];
  18. kwargs['kernel_id'] = ec2utils.id_to_glance_id(context, kernel['id'])
  19. # 获取kwargs['ramdisk_id']指定的镜像image数据返回给ramdisk;
  20. # 获取更新的kwargs['ramdisk_id'];
  21. if kwargs.get('ramdisk_id'):
  22. ramdisk = self._get_image(context, kwargs['ramdisk_id'])
  23. # 根据ramdisk['id']查询数据库中匹配的S3镜像数据,获取它的uuid属性,并返回;
  24. # 返回匹配的db.models.S3Image.uuid给kwargs['ramdisk_id'];
  25. kwargs['ramdisk_id'] = ec2utils.id_to_glance_id(context, ramdisk['id'])
  26. # 循环获取每一个块设备映射;
  27. # 解析块设备映射bdm;
  28. for bdm in kwargs.get('block_device_mapping', []):
  29. _parse_block_device_mapping(bdm)
  30. # 获取kwargs['image_id']指定的镜像image数据;
  31. image = self._get_image(context, kwargs['image_id'])
  32. # 根据image['id']查询数据库中匹配的S3镜像数据,获取它的uuid属性,并返回;
  33. # 返回匹配的db.models.S3Image.uuid给image_uuid;
  34. image_uuid = ec2utils.id_to_glance_id(context, image['id'])
  35. # 获取镜像image的状态;
  36. if image:
  37. image_state = self._get_image_state(image)
  38. else:
  39. raise exception.ImageNotFoundEC2(image_id=kwargs['image_id'])
  40. if image_state != 'available':
  41. raise exception.EC2APIError(_('Image must be available'))
  42. # create:准备实例,并且发送实例的信息和要运行实例的请求消息到远程调度器scheduler;
  43. # 实现实例的简历和运行,由调度器完成,这部分代码实际上只是实现请求消息的发送;
  44. (instances, resv_id) = self.compute_api.create(context,
  45. # get_instance_type_by_name:通过给定的name检索单个实例类型信息;
  46. # 以字典的形式返回查询结果;
  47. instance_type=instance_types.get_instance_type_by_name(kwargs.get('instance_type', None)),
  48. image_href=image_uuid,
  49. max_count=int(kwargs.get('max_count', min_count)),
  50. min_count=min_count,
  51. kernel_id=kwargs.get('kernel_id'),
  52. ramdisk_id=kwargs.get('ramdisk_id'),
  53. key_name=kwargs.get('key_name'),
  54. user_data=kwargs.get('user_data'),
  55. security_group=kwargs.get('security_group'),
  56. availability_zone=kwargs.get('placement', {}).get('availability_zone'),
  57. block_device_mapping=kwargs.get('block_device_mapping', {}))
  58. return self._format_run_instances(context, resv_id)</FONT>
复制代码
1.方法_get_image的源码分析:

可以看到程序中三次调用_get_image方法,来获取相应的镜像元数据,首先来分析这个方法:
  1. def _get_image(self, context, ec2_id):
  2. """
  3. 获取ec2_id指定的镜像image元数据;
  4. # 调用之一传进来的参数:
  5. # context:上下文信息;
  6. # ec2_id=kwargs['kernel_id']:从参数信息中获取'kernel_id'值,kernel_id为虚拟机内核ID值 ;
  7. """
  8. try:
  9. # ec2_id_to_id:转换一个EC2的ID为一个实例(镜像)的ID(INT格式);(主要是格式变换的问题)
  10. # 转换之后赋值给internal_id,也就是镜像image的内置ID;
  11. internal_id = ec2utils.ec2_id_to_id(ec2_id)
  12. # show:这个方法完成了以下的工作:
  13. # 转换镜像image的ID值internal_id到image_uuid;
  14. # 根据给定的image_uuid从glance下载image元数据,并且转换为字典格式;
  15. # 转换镜像image中的image_uuid到新的image_id;
  16. # 更新image当中的相关属性,返回更新后的image数据;
  17. # context:上下文信息;
  18. # internal_id:实例镜像的ID值(从EC2 ID值变换了格式以后得到的);
  19. # 注:S3是EC2的存储平台,所以这里调用/nova/image/s3.py中的show方法;
  20. image = self.image_service.show(context, internal_id)
  21. except (exception.InvalidEc2Id, exception.ImageNotFound):
  22. filters = {'name': ec2_id}
  23. images = self.image_service.detail(context, filters=filters)
  24. try:
  25. return images[0]
  26. except IndexError:
  27. raise exception.ImageNotFound(image_id=ec2_id)
  28. # 通过ec2_id获取image_type;
  29. image_type = ec2_id.split('-')[0]
  30. # 通过image_type验证找到的镜像image是否是所要求找的镜像;
  31. # 如果通过ec2_id获取的image_type和通过获取的镜像image得到的image_type不一致;
  32. # 则引发异常,提示所要求找的镜像找不到;
  33. if ec2utils.image_type(image.get('container_format')) != image_type:
  34. raise exception.ImageNotFound(image_id=ec2_id)
  35. # 返回获取的镜像数据;
  36. return image</FONT>
复制代码
这个方法主要实现的是根据参数ec2_id获取指定的实例镜像元数据;
1.1 internal_id = ec2utils.ec2_id_to_id(ec2_id)
转换一个EC2的ID为一个实例(镜像)的ID(INT格式)(主要是格式变换的问题);
1.2 image = self.image_service.show(context, internal_id)
这是比较重要的方法,完成了获取镜像元数据的任务,这个方法主要完成了以下工作流程:
internal_id分别针对'kernel_id'、'ramdisk_id'和'image_id':

1)转换镜像ID值internal_id到image_uuid;
2)根据给定的image_uuid从glance下载image元数据,并且转换为字典格式;
3)根据元数据中的id值查找到匹配的S3格式镜像数据信息,进一步获取镜像的数据库内部的id值,更新image元数据信息,并返回;
4)更新image当中的相关属性,返回更新后的image数据;
后面对这个方法进行详细的跟踪分析;
1.3 image_type = ec2_id.split('-')[0]
通过ec2_id获取image_type;
通过image_type验证找到的镜像image是否是所要求找的镜像;
1.4 return image
返回获取的镜像元数据;

1.2 image = self.image_service.show(context, internal_id)跟踪分析:
因为S3是EC2的存储平台,所以这里调用/nova/image/s3.py中----show方法
1.2.1 image_uuid = ec2utils.id_to_glance_id(context, image_id)

进入方法id_to_glance_id可见:
  1. def id_to_glance_id(context, image_id):
  2. return db.s3_image_get(context, image_id)['uuid']
复制代码
  1. def s3_image_get(context, image_id):
  2. """
  3. 通过给定的image_id查找数据库中的S3格式的镜像;
  4. """
  5. result = model_query(context, models.S3Image, read_deleted="yes").\
  6. filter_by(id=image_id).\
  7. first()
  8. if not result:
  9. raise exception.ImageNotFound(image_id=image_id)
  10. return result
复制代码
model_query是SQLAlchemy的一个方法;SQLAlchemy是一个Python的SQL工具包以及数据库对象映射框架,SQLAlchemy 的一个目标是提供能兼容众多数据库(如 SQLite、MySQL、Postgres、Oracle、MS-SQL、SQLServer 和 Firebird)的企业级持久性模型;后续我会对这个框架进行一个总结;(见后续链接)

model_query方法实现的是对数据库按照一定的规则的查询操作并返回结果;
我们看到,在调用model_query方法的时候传入了一个models.S3Image参数,原来S3Image是个类:
  1. class S3Image(BASE, NovaBase):
  2. __tablename__ = 's3_images'
  3. id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
  4. uuid = Column(String(36), nullable=False)</FONT>
复制代码
这个类从NovaBase类继承,这也是model_query方法所要求的。看看这个类,我们可以理解为,它创建了一个数据库中的表s3_images,并且定义数据表的结构,有两个属性id和uuid;

所以我们就能够知道id_to_glance_id(context, image_id)实现的是根据给定的image_id值,查询数据库,找到匹配的表信息,获取它的S3Image.uuid并返回,赋值给:
image_uuid = S3Image.uuid;
出自about云总结,暂时到此,谢谢大家!











已有(1)人评论

跳转到指定楼层
zw2002 发表于 2015-4-24 16:23:45
感谢分享,暂时还看不懂。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条