OpenStack CI持续集成测试

社区OpenStack持续集成相关,这作者写得很详细清晰,直接COPY过来了,传送门标明原出处:http://stackeye.com/2014/06/openstack-ci/

 

本文档主要对Openstack社区目前的CI测试环境及其中用到的各个组件进行介绍。

测试的分类及必要性

测试的分类

测试的过程没太有太严格的分类标准,从使用的角度,我们可以从以下两个角度对测试进行分类。

按照测试过程分类

软件测试大概分为单元测试、集成测试、系统测试、验收测试。以下为软件测试V型图:
软件测试V型图

  • 一般由单元测试开始,集中对每一个程序单元进行测试,检查各个程序模块是否正确地实现了规定的功能。单元测试的测试对象为独立的模块,需要由开发完成。
  • 此外,开发阶段应该还需要通过代码评审等方式,进行静态测试。
  • 集成测试把已测试过的模块组装起来,主要对与设计相关的软件体系结构的构造进行测试,目的在于检验与软件设计相关的程序结构问题。系统测试把已经经过确认的软件纳入实际运行环境中,与其它系统成份组合在一起进行测试,验证软件的完整功能是否满足需求规格并可正常运行。此两个阶段应该由测试人员执行。
  • 在软件的迭代过程中,需要不断重复测试以验证bug修改及新功能添加,这称之为回归测试。
  • 为了快速验证一个新版本,可挑选部分重要测试用例(如出错后会导致阻断其它测试用例的用例)用来验证,称之为冒烟测试(Smoke Test)或接收测试。
  • 验收测试主要由软件的实际用户在实际应用场景中执行,包含α测试和β测试。

按照测试侧重点的不同分类

测试可分为功能测试、稳定性测试、性能测试等。
测试分类及阶段

  • 功能测试用以验证被测试对象功能的正确性。功能是软件最基础的属性,如果功能错误,其他一切测试都没有意义。因此功能测试是其他测试的前提。
  • 性能测试用于验证被测试对象的最大服务负载及其他性能拐点,并通过调查、优化比较给出性能瓶颈、优化建议。
  • 稳定性测试用于验证在一定负载压力情况下,被测试对象长时间运行时功能是否正常。与性能测试的差别在于,稳定性测试的负载不是100%,而且测试的目的在于验证负载下功能的正常,而非验证最大负载。
  • 如果需要,还可以添加场景测试。即根据实际使用情况或猜想得到被测试物的使用场景,在使用场景中进行测试。
  • 其他测试如安全测试、易用性测试,可根据实际需要添加。但是严格来讲,这些都属于功能测试的范畴。

软件测试的必要性

发现软件的bug只是手段,软件测试的目的在于保证并提高产品质量。

如果产品继承自开源社区,开源社区一般有严格的测试标准(如openstack),但是我们仍然需要验证:

  • 我们的部署是否正确?
  • 版本是否适用于我们的环境?有哪些不足值得我们改善?
  • 针对某个bug的修改是否适用于我们的环境?
  • 我们修改或新增后的新特性是否正确?对其他组件是否有影响?

相关概念

持续集成测试

持续集成(Continuous integration)测试,是指在持续基础上不断执行测试以验证完整应用环境正确的行为。“持续”,即要求每次改变提交后,测试工作都要进行。因为需要不断进行回归测试,而且一般都对测试时间有一定要求,因此会大量使用自动化测试。
持续集成测试

为什么需要持续集成?

如果项目开发的规模比较小,对外部系统的依赖很小,那么软件集成不是问题。但是随着软件项目复杂度的增加,就会对集成和确保软件组件能够在一起工作提出了更多的要求。如果开发人员在后期才进行集成,到后期才发现问题,解决问题代价会很大,很有可能导致项目延期或者项目失败。

因此要求“尽早集成、经常集成”,在早期发现项目风险和质量问题,更容易解决问题,并保证开发进度。持续集成就出现在这样的背景下。

持续集成测试是一种软件开发实践:团队开发成员经常集成他们的工作,每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件从而减少项目整体风险。

通常当我们提及CI的时候,我们指的是针对完整、现实使用的环境进行的测试。这种测试可以称为集成测试,可以确保提交的针对某个组件的修改不会导致其他组件的失效。集成测试对于如Openstack的多组件多项目的复杂系统尤为重要,而且适用于子系统间没有过多的耦合依赖的情况。

Openstack的代码评审系统

Openstack主要使用Gerrit进行代码评审及管理。社区的Gerrit运行于。Gerrit工作流如下:
gerrit工作流
当贡献者向Openstack提交一个patch时,他会将代码提交至由Girrit管理的git服务器上。Gerrit控制着哪个用户或组织可以提交代码、合并代码、管理代码库。当贡献者提交代码至review.openstack.org时,Gerrit会创建一个变更集(Changeset)来代表所提交的代码。原提交者和其它贡献者可以对变更集提交额外的修改。Gerrit会收集所有这些更改而汇集成变更集记录。以下为一个待评审的变更集,其中可以看到有很多Patch,而每个Patch都是对原始提交的一次修改。
gerrit patch界面
Gerrit中的每个Patch都有三个标签属性。每个人都可以评论变更集或评审代码。在Patch标签属性的“Code-Review”一列中显示了对代码的所有评审:
gerrit review

非Openstack核心团队的人员可以在评审代码后作出+1(好)、0(无评价)、-1(不建议合并)的评价。核心团队的成员可以给出非核心成员可给出的所有评价,此外还可以给出+2(好,approved)、-2评价(不要提交)。

标签属性中另外一列为“Verified”。只有Gerrit的非交互用户(non-interactive users)如Jenkins,才能在此属性上添加Verified标签。Verified一列可设置的值为+1(check pipeline测试通过)、-1(check pipeline测试未通过)、+2(gate pipeline测试通过)、-2(gate pipeline测试未通过)。此外,第三方搭建的外部测试平台也属于非交互用户,但是只能设置+1、-1。

标签属性中最后一列为Workflow(原为Approve)。只有核心团队的成员可对此列进行标注。此列值只能为+1(Approved)、未通过,展现形式为有无对号标记:
gerrit verify

The “Gate”

这里所说的Gate是一个流程、一个动作,通过这个流程、动作,相关条件达不到的代码将被隔离在源代码分支之外,从而完成对目标代码的“守卫”。
git工作流
Openstack采用一种称之为“Non-Human Gatekeeper”的模型来控制代码合并进特定代码分支。Gerrit就是其中的“the non-human”,它允许“Non-Interactive Users”合并代码到它管理的稳定的主干代码分支中。上游的Jenkins服务器以及运行于第三方环境中的Jenkins系统,就属于这些“Non-Interactive Users”。

那么这些“Non-Interactive Users”如何去判断是否将一个提交的Patch合并进目标代码分支呢?这是通过运行一些相关的测试来实现的。测试内容因项目不同而不同,但大体包括这些内容:单元测试、功能测试、集成测试、升级测试和代码风格测试。“Non-Interactive Users”通过这些测试来守卫(gate)一个特定项目的源代码树。

大多数Openstack项目都有单元测试和代码风格测试。单元测试在不同的Python版本上执行。风格测试用于验证代码风格是否符合Openstack Hacking和PEP8规定。单元测试和风格测试通过tox调用virtualenvs实现。

此外,还有针对完整安装的Openstack进行的集成测试。这部分测试时Tempest套件的一部分。最后,很多项目还有升级测试和数据库迁移测试包含在gate test中。

社区CI环境简介

背景

在未出现标准的自动化CI测试套件之前,社区的代码质量基本都由代码评审人员在本地手工测试保证。执行效率低,需要不断重复,而且因为对完整的环境测试很困难导致很难保证某个模块的修改对其它模块是否会产生影响。此外也无法验证在不同环境下是否运行正常。

而这一切都在社区的自动化CI测试套件出现后得到改善。这是一个完整的、标准化的、鲁棒性高的、自动化的持续集成测试平台,由社区的openstack-infra开发维护。它的核心是Gerrit代码控制评审平台、Zuul驱动的Gating System和Jenkins CI服务器。这个测试系统已被广泛使用,截止2013年9月,社区持续集成测试平台每小时执行720个测试任务,测试节点已增加至328个,不断并行进行测试。截止I版本,社区大概每天有400个测试通过的提交。

Test Run Style

根据测试目的、测试精度、测试可靠度等的不同,社区的测试运行方式分为以下五种。

  • Experimental
    根据开发者的需要进行的实验性质的、低可靠度的测试。执行失败后的处理并不由openstack-infra团队负责。
  • Silent
    因为已知问题无法进行的测试。其它同Check。社区会寻找解决办法并评定是否可进入到Check状态。
  • Third Party
    由第三方组织(非Openstack-Infra)在自己的测试环境执行的测试。因为第三方不可控,无法保证测试的可靠度。因此虽然执行后可根据结果对所测试的Patch做出评定,但是只能给出+1、-1,无法做出+2(verified)操作。
  • Check
    Check用于验证每个对Gerrit提交的patch。它和第三方的运行机制相同,都是对提交的patch单独进行测试,而不是针对patch被合并进主代码库后的状态进行测试。
    Check阶段出现的错误会使patch无法被approve,从而无法进入后续状态。
    因此,Check必须在高度可靠地环境中运行,必须由Infra团队完全控制且隔离。
  • Gate
    Gate旨在检测approve通过后的Patch。与Check不同的是,它针对patch被合并进主代码库后的状态进行测试,而不是提交的代码的状态。这样就会检测到Patch直接的语义冲突等问题(鉴于社区每天会有很多patch经过Gate,这样做是很重要的)。
    如果Gate发现错误,将会阻断patch的“着陆”(landing),而且它之后的所有等待的patch都要重新进行测试。
    同Check,Gate也要在高度可靠的环境中运行。

Openstack CI工作流程

  • (1a)贡献者提交新patch(主要执行Check测试),
    或者
  • (1b)核心团队成员Approve一个patch(主要执行Gate测试),
    之后,
  • (2)Geriit提交一个通知事件到它的事件流中(event stream),
  • (3)Zuul从Gerrit的事件流中读取事件,
  • (4)然后匹配事件到一个或多个pipeline,
  • (5)Zuul针对patch对应的项目执行pipeline(调用Jenkins执行Jenkins jobs),
  • (6)Jenkins被调用来执行具体任务(编译、执行等),并返回结果到Jenkins的事件流中,
  • (7)Zuul从Jenkins的事件流中读取事件,
  • (8)根据结果,Zuul在Gerrit中对patch添加一个review结果。

Openstack CI工作流程

社区CI使用的设备

社区并无自己的设备,所有环境都是Rackspace、HP等云厂商捐赠的虚拟机实例。
2014年6月:目前大概有340个实例并行执行。实例大多本身运行于Openstack之上,具有8G RAM及相关配置,使用Ubuntu Precise系统。

组件工作流程

各组件的工作流程图如下:
CI各组件工作流程

Gerrit

Gerrit主要负责代码的管理,具体可查看之前的章节。在Gerrit将通知事件添加到Gerrit事件流之后,Zuul组件开始发挥作用。

Zuul

Zuul通过Gerrit事件,找到对应的pipeline进行后续处理。而pipeline中定义了需要执行哪些Jenkins任务。这些都是在Zuul的配置文件layout.yaml中定义的。例如如下为一个gate这个pipeline的片段:

– name: gate
description: Changes that have been approved by core developers…
failure-message: Build failed. For information on how to proceed…
manager: DependentPipelineManager
precedence: low
trigger:
gerrit:
– event: comment-added
approval:
– approved: 1
– event: comment-added
comment_filter: (?i)^\s*reverify( (?:bug|lp)[\s#:]*(\d+))\s*$
start:
gerrit:
verified: 0
success:
gerrit:
verified: 2
submit: true
failure:
gerrit:
verified: -2

 

从中我们可以看到gate这个pipeline由gerrit的commit-added + approved或 commit-added +无bug标识的reverify事件触发。成功后会返回verified:2消息给gerrit,失败返回verified:-2。

类似,check pipeline会被gerrit的patchset-created或recheck事件触发,成功后返回verified:1,失败后返回verified:-1,与Test Run Style描述一致。Openstack的CI环境中主要用到了check、gate、post、pre-release、release、silent、experimental、periodic这几个pipeline,并分别给出了每个项目中这些pipeline对于哪些Jenkins任务。

而对于每个项目的每个pipeline,需要执行哪些任务,在此配置文件中project一节定义:

– name: openstack/cinder
template:
– name: python-jobs
…snip…
gate:
gate-cinder-requirements
gate-tempest-dsvm-full
gate-tempest-dsvm-postgres-full
gate-tempest-dsvm-neutron
gate-tempest-dsvm-large-ops
gate-tempest-dsvm-neutron-large-ops
gate-grenade-dsvm

 

上述配置为对于cinder项目的gate这个pipeline,需要执行gate-cinder-requirement到gate-grenade-dsvm这些任务。

引入Zuul而不是直接使用Gerrit触发Jenkins的原因是,Jenkins每次只能执行一个任务。这种线性执行是因为考虑到复杂的依赖关系,但是这在需要测试的修改数量增加时就会验证影响效率:如果一个测试需要执行1小时,每天只能执行24次测试,只能对24个修改做出验证。通过引入Zuul使Jenkins并发测试成为可能。

Zuul同时还可以很好的处理具有复杂依赖关系的多个patch。它能监控正在执行的任务,并可提前结束掉因依赖的patch测试失败而必定失败的测试任务。

Gearman

在原来的实现中,Zuul完成“事件-pipeline-任务”的匹配后,就可以调用Jenkins执行具体的任务开始实际的测试了。Jenkins用的是master/slave架构,一台master管理所有slave节点。但是Jenkins的设计初衷并不用于并行执行,它设计中某些点使用到了全局锁,因此在Jenkins的slave节点增加到一定数量后(大约100台),Jenkins的master节点就会出现问题而成为瓶颈。同时master节点是单点部署,无法完成HA等处理。为了扩展Jenkins而引入了Gearman。

加入Gearman后,Zuul不再与Jenkins直接交互,而是提交执行任务的请求给Gearman服务器,由Gearman服务器完成任务的分发。测试节点通过注册到Gearman服务器,使得Gearman获知其可用,并被分配任务。如Jenkins Master节点通过Gearman的插件连接到Gearman服务器并获得任务。

通过Gearman,CI测试架构具有了弹性:触发事件的可以不只是Zuul,而执行任务的可以不只是Jenkins。而通过多个Jenkins Master的部署,获得了HA功能。

Jenkins、JJB

经过上述流程,终于到了Jenkins实际执行的流程。CI环境中使用的是Master/Salve架构,同一个Master同时控制多个Slave节点进行工作。

每个Jenkins任务都需要配置Jenkins的config.xml文件实现。而在任务数量达到一定级别后,手工去配置每个任务会变得非常复杂。因此引入了Jenkins Job Builder (JJB)这个Python工具。JJB通过解析用户配置的YAML文件来自动生成config.xml。而YAML文件使用比较易读的语法,支持弹性的模板系统,且支持版本控制便于不断修改。这些特性使得配置大量Jenkins任务变得容易。

Zuul并不去指定具体Jenkins任务是什么,而是只指定对一个项目的一个pipeline需要去执行哪些Jenkins任务。具体指定每个Jenkins任务的内容是由JJB来做的。

在Jenkins的任务中,完成对代码的check out、build,然后进行按照定义进行实际的测试。

Devstack、Nodepool和Backup

对于单元测试等可能不需要实际的Openstack环境去执行,而对于集成测试等则需要一整套的Openstack组件。对于这些测试, Jenkins任务会去实际搭建完整的Openstack环境。这个工作通过Devstack-Gate调用Devstack脚本实现。但是因为部署也属于Openstack的一部分特性,因此H版本之后社区正在考虑将部署部分也纳入测试项,替代Devstack。

测试中支持配置不同的组件,如替换数据库PostgreSQL,替换消息队列为ZMQ。

而在搭建环境之前还需要去申请搭建环境需要的资源(目前社区使用的是虚拟机实例)。Nodepool实现了资源的有效调度,并可方便的添加资源进入资源池。

在资源使用后,需要清空配置还原环境。这个是通过脚本恢复备份来实现的。

实际执行的测试用例

实际执行的测试用例也是在Jenkins的任务中定义,大多通过调用Tempest集成测试套件中的用例实现。

ELK集群、elastic-recheck

日志及检索,通过ELK技术栈,实现日志的实时监控:

  • Logstash收集日志,实际环境可考虑使用更加节省资源的Heka替换。
  • Elasticsearch进行日志分析
  • Kibana进行可视化展示
  • elastic-recheck跟踪Openstack Gate失败情况

其它组件

  • Puppet,用于自动化部署;
  • Cacti等,用于监控。

测试覆盖内容

Style测试

严格的pep8要求,及Openstack自身对代码风格的要求。

单元测试

通过Python脚本实现。主要通过python的unittest、mock和nose等单元测试库实现。

Tempest

Tempest是一整套Openstack集成测试框架,它的实现基于python的unittest2测试框架和nose测试框架。Tempest对Openstack终端发起一系列API请求,并且对终端的响应进行验证。Tempest通过config文件来描述整个测试环境,包括compute API端点,Keystone server以及Glance server安装的镜像的UUID等信息。

测试的主要模块有如下几部分:

  • api主要测试OpenStack API部分的功能
  • cli主要测试OpenStack CLI接口
  • scenario主要根据一些复杂场景进行测试
  • stress压力测试部分,目前可以结合rally进行压力测试
  • thirdparty这部分主要针对于EC2的API测试用例。

稳定性测试

可以通过改写Tempest实现,增加功能测试的时间、场景的复杂度,增加一定的负载,即可实现满足要求的稳定性测试。

性能/压力测试

目前有Rally、scalability testing项目去实现。因为对运行环境有要求、负载压力较大,而且可能包含调优工作(此时可能会改变部署环境等),所以虽然已集成到CI环境中,但后续可能需要单独进行测试。

其它测试

很多项目中还在gate的测试中包含升级和数据库迁移测试(如turbo-hipster)。在原有数据量比较大的情况下,就需要考虑升级测试。
还有很多测试未包含,但是社区在不断完善加入新的测试。

总结

使用方式

作为Openstack开发厂商,如何集成社区的CI框架到我们的开发过程中呢?

  • 测试我们的产品质量
    Openstack社区有兼容性认证,对社区版本做出修改后仍能通过社区测试用例即可获得此认证。
    另外,为社区提交的patch都需要有严格全面的测试用例。
    还可以考虑将社区的CI流程集成进自己的开发流程内,但修改对应的测试用例需要自行编写维护。
  • 为社区捐赠云主机加入社区的测试环境
    可及时获得社区的修改,是否适用于我们的环境,并跟踪bug修改。
    目前各大openstack公有云厂商都有参与。

问题

  • 测试覆盖不全面
    旧版本只有Nova测试比较多,其他测试待完善。定制化的特性缺少。而且测试大多由开发人员编写,难以保证覆盖度。
    经过几个大版本的更新,测试覆盖度已达到提高,足够满足要求。
    只有在新创建的项目里,测试用例要求不严,覆盖度不高。
  • Gerrit组件带来的修改
    我们现在一般都使用gitlab维护代码。而gerrit本身也最适合只做代码review。
    因此需要迁移代码至gerrit,改变开发review流程,并最终自动同步代码至gitlab。
    相关配置可参考社区文档。
  • 自动化部署
    官方使用devstack进行自动化部署,定制化的部分需要修改。
  • 组件多,部署配置复杂
    部分组件如zuul,是Openstack CI团队独立开发,文档较少。
    而这些组件并非必须,实际环境中乐意考虑简化,最简单的CI框架只需要Gerrit+Jenkins+测试机器+测试用例即可。
  • 社区CI运行于Ubuntu系统
    只有针对此系统的依赖及puppet脚本。
    其它系统部署时在依赖及自动化部署方面可能会遇到很多问题。

发表回复