浅谈分布式存储系统自动化测试

2019年02月 · XSKY

​​

《如何基于公有云构建SDS大规模测试环境?》文章中介绍了我们开发的Endurance项目,通过使用公有云来为SDS构建大规模的测试环境。在公有云之外,我们在本地和IDC数据中心还拥有各类品牌及规模庞大的服务器资源,基于这些物理机资源,我们搭建了分布式存储系统自动化测试系统框架,这样与Endurance项目互为补充。

在差不多3年的时间里,我们用这套系统跑了接近10w个用例。每个用例的耗时在0.5-2小时之间,平均耗时接近1小时。测试用例通过率在89%左右。在11%的失败用例中,有5%左右是代码bug导致的,其它的失败是由于操作系统环境问题或硬件问题导致的。

一、分布式存储系统自动化测试的特点

分布式存储系统一般是由多个服务器、网络设备和很多存储介质组成。即使在手动部署测试环境的时候,所涉及的系统架构也会是比较复杂的,包括:

  • 硬件多样性。在测试前,需要根据不同的测试类型采用不同的硬件设备。譬如硬盘的规格(SATA盘还是SSD盘),内存规格,以及不同的网络类型(TCP/IP,FC,RDMA)等;
  • 网络要求。分布式系统的特点决定了测试环境可能运行在多个服务器、多个机架,甚至是多个数据中心上。在测试前,需要模拟构建出所需要的网络带宽和时延要求;
  • 配置复杂性。由于设计的软硬件平台非常多,整个系统的可配置参数就非常多,整个配置过程耗时长且容易出错。

大型分布式系统的测试用例都是上万的级别,随着功能的不断增加,测试用例也是呈指数级增长。而分布式系统测试的以上特点决定了人工手动进行测试,不仅效率低下,而且容易出错。如果完全依赖人工测试,根本无法做到快速迭代,必须实现高度的自动化。

二、XSKY自动化测试系统架构

现有的一些自动化测试框架大部分是针对单元测试,用来做集成和功能测试的少之又少。特别是针对分布式环境的则更少了。因此,XSKY针对分布式系统的特点开发了一套自动化功能测试系统的框架,由Python+Shell+YAML语言实现。其采用CS架构,如下图所示。

该架构由一台调度器和上百台测试机器组成。调度器负责测试用例的生成和调度,具体的测试执行由调度器从测试集群机器里选出来的测试机器完成。

调度器分为两部分:作业生成器和一组agent。作业生成器通过不同测试参数的组合排列,生成一次测试作业的测试用例集合。然后根据优先级的不同,分发给不同agent来执行。

每个agent同一时间负责一个测试用例的执行,分配不到agent的测试用例会在队列中进行等待。agent在接收到测试用例后,将会负责这个测试用例的整个生命周期。它首先从测试集群中选择指定数目且符合测试硬件要求的物理机,将其锁定用于该用例的测试。然后agent会发出安装指令在这几台物理机上搭建起一个存储集群。

测试环境搭建成功后,agent发出各种测试指令来执行相应的测试操作。最后在作业完成后,会收集好测试日志,并清理这几台物理测试机的软硬件环境,然后解锁它们供后续测试用例使用。

测试机器的数目不定,可以根据需要动态的往测试集群里面添加。由于每个agent的相互独立性和轻量性,添加测试机器用于新的测试用例并不会影响到集群内其它用例的测试性能。也即该系统的可扩展性是非常好的,可以支持成百上千个测试用例的同时高并发执行。

三、XSKY自动化测试系统的特点

这套自动化测试系统从一开始就用于支持我们的分布式存储系统的测试,其具有以下特点:

1、丰富的测试用例

通过使用YAML语言来编写测试用例,做到了以测试内容为中心,便于阅读理解。同时测试用例的编写也非常直观容易,相比起其它语言如XML,JSON简单很多。

另一方面,在该框架中,测试用例的编写做到了模块化。一个YAML文件只需要描述测试用例的一个模块。调度器里的作业生成器会根据这个测试用例里的所有模块,采用不同排列组合的方式来生成最终的用例。这样一方面进一步简化了测试用例的编写,另一方面极大的丰富了测试用例的个数。目前我们的整个测试用例集合达到了10多万个,随着进一步的功能开发,将会达到上百万个

2、错误注入和数据服务

错误注入包括了在测试过程中对磁盘,网络,服务器以及操作系统的各种可能的错误的模拟,最大限度的测试软件程序在各种错误场景下的鲁棒性。还包括了同时注入多种错误,以验证系统在同时发生两种或以上错误时的行为是否符合预期。同时,我们在这套框架中提供了丰富的数据服务:

首先,数据的生成是完全随机的,且可以满足一些情况下的数据对齐,写全零,追加写和数据属性生成等要求。

其次,针对程序里的每一个操作的API接口,都提供了相应的测试操作类型。可以在测试命令中指定执行哪些操作以及其权重。测试命令在执行的时候,会根据权重随机的执行指定的数据操作。由于数据操作的随机性,可以覆盖代码在各种操作场景下的健壮性。权重的设置则可以让测试命令着重覆盖某些方面的操作,突出重点。

数据服务的另一大功能是提供了数据校验,包括了端到端的CRC校验以及数据的逐字节对比,对存储系统的数据完整性提供了很好的验证支持。利用此项功能,我们在代码的测试过程中发现了很多数据的一致性问题,并得以修复。

3、测试结果的分析

对于测试结果的分析,我们通常分析以下几种情形。

  • 返回结果。这里也分两种结果:第一,agent根据测试用例测试的功能点和流程,分析其返回结果是否符合业务预期;第二,测试服务器的操作结果(通常是重启、当机、断网、插拔硬盘等操作),分析这些结果是否符合操作预期。
  • 分析代码日志。在测试过程中,一般需要设定适当的日志级别,记录系统的行为。并通过关键字的方式来进行分类,这将便于工程师进行日志分析,快速定位问题原因。
  • 分析操作系统的一些重要的日志信息。除了详细分析代码日志以外,我们还需要对操作系统的一些重要数据信息进行分析,从而判断我们的程序是否存在异常。以linux为例,我们常常会检查syslog文件,内核日志文件,并使用各种命令,譬如top, iostat, netstat等获取操作系统在运行时的一些重要信息。
  • 其他分析工具。譬如利用valgrind工具来检查是否有内存泄漏,利用google gperftool里的工具来检查非法内存使用和做profiling,利用锁依赖工具来检查代码里锁的错误使用等。

4、代码覆盖度

代码覆盖度是程序测试时的一个重要指标。一个很显然的道理就是,如果你的测试覆盖度很低的话,程序质量就会很差。比如说低于50%,那就是说你有超过一半的代码在测试中没有被覆盖到,那这些代码就相当于定时炸弹,在线上随时都有可能出问题。

因此,在这套自动化框架中我们加入了对代码覆盖检查工具的支持,目前主要用到的是gconv。通过使用gconv,我们可以得到测试过程中实际执行了哪些代码,以及每一行代码的执行频率。然后得到反馈该补充哪些类型的测试用例。同时,通过配合其它profiling工具,如gprof,更可以找到程序的热点代码,从而对其进行优化。

5、节省测试人力

目前我们只有一个程序员兼职用于该系统的维护,以及添加随着新功能的开发而增加的测试用例。这对于很多需要很大测试团队的公司来说是不可思议的事情,而我们实实在在的利用了该系统支撑了多个稳定版本的发布。​​​​