OpenStack Neutron之持续测试

一、OpenStack持续测试概述

众所周知,OpenStack作为一个特大型软件开发项目,有着数千人的开发人员,每天要处理千计提交的代码,几千条Gerrit评论和投票,催生出数万个测试环境,还有数百次源代码的合并,十几个顶级项目,大量的文档,跨大洲大洋的协同开发。

因此,为了确保这些工作的实现,OpenStack构建了一套完善的CI(持续集成)-CT(持续测试)-CD(持续交付)基础设施平台和流程体系。如下图所示

图来自docs

CI方法已经在 OpenStack 项目中得到了大规模的实现,统一来管理所有不同的子项目的CI过程。

为了实现这些,OpenStack项目使用了下面这些组件:

  • Gerrit:代码审查和git资源库管理器。
  • Zuul、Git:代码库控制系统。
  • Jenkins:持续集成服务器。
  • Nodepool:部署在OpenStack云上的智能的Jenkins衍生工具。
  • Github:用于存放Gerrit上被Merge的代码
  • 其他

持续测试(CT)作为软件持续集成(CI)中的重要组成部分,为软件项目的成功提供了保证软件质量持续改进的重要手段。OpenStack当然也不列外,其持续测试系统如下所示:

  • Selenium:用于自动化测试Dashboard UI
  • Tempest:用于自动化测试OpenStack API
  • Rally:用于自动化测试OpenStack性能
  • Unit Tests:用于每个项目的单元测试
  • 其他

备注:这里我们关注的是OpenStack 项目中的Neutron CT部分。因此,这与理解其他项目是一致的。

二、Neutron中的持续测试

图来自Wiki

1. Mock & Mox单元测试

SmallTestingGuide:

http://wiki.openstack.org/SmallTestingGuide

Unit Tests:

http://docs.openstack.org/developer/neutron/devref/development.environment.html#testing-neutron

openstack单元测试组件一览:

http://blog.csdn.net/halcyonbaby/article/details/30344441

从总结性角度而言,单元测试追求的是速度、隔离以及可移植性。对于速度,需要测试代码不和数据库、文件系统交互,也不能和网络进行通信。另外,单元测试的粒度要足够小,确保一旦测试失败,能够很容易迅速的找到问题的根源;可移植性是指测试代码不依赖于特定的硬件资源,能够让任何开发者去运行。

OpenStack中单元测试的代码位于每个项目源码树的Project_name/tests/目录下,使用了oslo.test公共测试库提供的基础框架。通常单元测试的代码需要专注在对核心实现逻辑的测试上,如果需要测试的代码引入了其他的依赖,比如依赖于某个特定的环境,我们在编写单元测试代码的过程中,花费时间最多的可能就是如何处理这些依赖,否则,即便测试失败,也很难定位出问题所在。

在单元测试中,引入了一种Test Double(理解为模拟某个事物)的理念来替代测试中的每一个依赖。有多种类型的Test Double,比如Mock对象、Fake对象等。Mock和Fake都是Python中用于实现单元测试的模块库实现隔离,比如将相应的操作进行隔离,通过替换测试内容中的Class、Function等对象。专注在Function的核心实现逻辑的测试上,比如把DB操作、I/O、网络相关操所如socket、ssh等隔离掉。

在测试运行过程中,当执行到这些操作时,并不会深入到方法的内部去执行,而是直接返回我们事先假设的一些值。

如下在neutron/tests/functional/services/l3_router/test_l3_dvr_router_plugin.py测试模块中。便事先设置好了我们假设的subnet_cidr、ext_net值。

 72     def _test_remove_router_interface_leaves_snat_intact(self, by_subnet):
 73         with self.subnet() as subnet1, \
 74                 self.subnet(cidr='20.0.0.0/24') as subnet2:
 75             kwargs = {'arg_list': (external_net.EXTERNAL,),
 76                       external_net.EXTERNAL: True}
 77             with self.network(**kwargs) as ext_net, \
 78                     self.subnet(network=ext_net,
 79                                 cidr='30.0.0.0/24'):
 80                 router = self._create_router()
 81                 self.l3_plugin.add_router_interface(
 82                     self.context, router['id'],
 83                     {'subnet_id': subnet1['subnet']['id']})
 84                 self.l3_plugin.add_router_interface(
 85                     self.context, router['id'],
 86                     {'subnet_id': subnet2['subnet']['id']})
 87                 self.l3_plugin._update_router_gw_info(
 88                     self.context, router['id'],
 89                     {'network_id': ext_net['network']['id']})

Mock、Mox 都是OpenStack中常见的实现隔离很好的单元测试模块,理解它们能够更快的做UT的编码。这种测试通常直接导入特定的代码功能,并运行它们以确保它们的返回值是有效的,比如通过调用期望函数等。Neutron项目的测试类别,如下图所示:

1)通过Mock带隔离的测试,包括单元测试,还有针对API和example的功能测试(在Mock环境中针对一个个具体的API和example做测试)。Neutron中的单元测试路径为neutron/tests/unit。

2)不带隔离的真实环境测试,比如功能测试(尽量在真实环境中辅以少量的mock串起来测试,neutron中的代码位于:$neutron/tests/functional, 运行时添加OS_SUDO_TESTING=True)和集成测试(在真实环境中将多个API串起来测试,即tempest项目)。

Mock单元测试,也就是经常说的最小测试,它强调隔离,也就是说我们只将精力集中在我们要测试的方法内,如果该方法调用了其他方法,都可以通过Mock方式来模拟返回一些假设的值。

代码隔离有如下几种方法:

1)一种是依赖注入,如下例子,想要测试FamilyTree类的话,应集中精力测试FamilyTree本身是否有错,至于它所依赖的Person_Gateway可以做一个假的FakePersonGateway注入进去。

class FamilyTree(object):
         def __init__(self, person_gateway):
           self._person_gateway = person_gateway
       person_gateway = FakePersonGateway()
        # ...
          tree = FamilyTree(person_gateway)

2)另外一种是利用Python的Monkey Patching的特性,在运行时可以动态替换命名空间,用FakePersonGateway替换掉命名空间mylibrary.dataaccess.PersonGateway。

class FamilyTree(object):
    def __init__(self):
       self._person_gateway = mylibrary.dataaccess.PersonGateway()
    mylibrary.dataaccess.PersonGateway = FakePersonGateway
    # ...
    tree = FamilyTree()

3)另外一种方式是使用Mox模块来实现单元测试。Mox模块是python实现单元测试的一个框架,如下面的列子所示,如果测试方法中调用了get_instance_type_by_name方法,则可以使用mox模块来模拟这个方法的输出。

inst_type = instance_types. get_instance_type_by_name(flavor_name, context)

现在可以使用Mox模块将其替换,返回不同的值:

self.mox.StubOutWithMock(flavor_dynamic.instance_types,
                                  'get_instance_type_by_name')
flavor_dynamic.instance_types.get_instance_type_by_name(
                    mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(None) flavor_dynamic.instance_types.get_instance_type_by_name(
                    mox.IgnoreArg(), mox.IgnoreArg()).AndReturn("{'a'='1'")
self.mox.ReplayAll()
self.mox.VerifyAll()

我们除了可以对一个个方法使用Mox实现Mock对象;还可以使用Mock模块来实现,对一个类的所有方法进行模拟。

self.utils_exec_p = mock.patch('neutron.agent.linux.utils.execute')
self.utils_exec = self.utils_exec_p.start()
self.addCleanup(self.utils_exec_p.stop)

列如,在测试方法中用到了self.router_info_inst,我们可以将它用Mock替换。

self.router_info_inst = mock.Mock()

4)Fake对象,即创建大量的假对象。

所以在考虑单元测试代码时,可以考虑以下两点作为标准:

1)是否使用了VerifyAll(),这意味着是否所有隔离方法的返回值都被单元测试所覆盖;

2)测试覆盖率,可通过工具查看,这意味着是否覆盖了每个方法的语句分支(条件分支覆盖测试);

备注:1)-4)部分,整理自《深入浅出Neutron OpenStack网络技术》10.1。

2. Tempest集成测试

除了上面的单元测试,还有针对和Keystone交互的集成测试,位于neutron/tests/tempest目录,单元测试、功能测试和集成测试的区别在于集成测试采用的是真实环境。

在Neutron项目中,Tempest涉及到的服务还是相对较少的,主要是identity和network。从中可以看出的一个显著趋势是,Neutron中的Tempest更趋向于Keystone V3版本发展,如下图所示:

当开发人员提交了一项代码到Gerrit中后评审的整个流程,如下图所示。

图来自pjoinfu.com

Neutron CI系统会创建一个虚拟机,并使用devstack在虚拟机上部署OpenStack环境,打上提交评审的patch后运行一系列的测试(如单元测试、功能测试、upgrade、PEP8测试等),最后向开发者反馈测试结果。

与之前所述测试不同的是,集成测试采用的是真实环境调用真实的Rest API进行测试,不涉及Mock对象。目前,对于Neutron项目,主要是验证一些基本的网络场景,通过一个Network启动一个实例然后访问这个实例的浮动IP,测试覆盖率非常低。当然,今后将针对这些Core API(Port、Network、Subnet)和Extension API(LB、VPN)做更多的场景和集成测试。比如:

  • 跨Router的云主机连通性测试;
  • 公共和共享网络的测试;
  • 对一个Port设置Security Group的测试;
  • 跨Router上移动Floating ip时的连通性测试;
  • 其他;

再次强调,功能测试(functional)和集成测试(tempest)是有区别的。前者的测试粒度要比单元测试大一些,单元测试关注于方法层面,功能测试关注于功能层面,仍会涉及到Mock对象等;而集成测试所测试的对象是模块间的接口,其目的是找出模块接口之间(函数接口之间的数据传递是否准确无误或引起异常崩溃等),包括整体系统结构的问题。其测试的依据来自于系统的架构设计。不涉及到Mock对象等。

3. 如何执行单元测试

执行单元测试的途径有两种,Tox或者项目源码树根目录下的run_tests.sh脚本。

Tox是一个标准的Python虚拟环境管理器和命令行测试工具。可以用于检查软件包能否在不同的Python版本或解释器下正常安装;在不同的环境中运行测试代码;作为持续集成的组成部分,减少测试工作所需要的时间。

1)run_tests.sh脚本

run_tests.sh脚本封装了testr测试框架的用法。可以使用bash run_tests.sh –h命令来获取有效帮助,如果仅想对某个模块或功能做测试的话,可以运行相应的测试子集:

bash ./run_tests.sh neutron.tests.functional.services

#对功能测试的services模块做测试

当然,也可以指定对模块中的某个类或方法做测试,如下:

bash ./run_tests.sh l3_router.test_l3_dvr_router_plugin:L3DvrTestCase

#对test_l3_dvr_router_plugin模块中的L3DvrTestCase类做测试

bash ./run_tests.sh test_l3_dvr_router_plugin:L3DvrTestCase.test_get_router_ids

#对L3DvrTestCase类中的test_get_router_ids方法做测试

默认情况下,执行测试之后,会在控制台输出大量的测试信息,非常不方便查看结果。这时,我们在执行测试的时候添加一个–nologcapture参数就行。比如,这里我们可以打印print或log日志,加上后面2个参数:

bash ./run_tests.sh test_l3_dvr_router_plugin:L3DvrTestCase  --nocapture  --nologcapture

可以把test内容输出到指定文件:

bash ./run_tests.sh test_l3_dvr_router_plugin:L3DvrTestCase --nocapture > test.log 2>&1

备注:请尽可能在虚拟的环境中运行脚本。

2)Tox工具

官网资料:http://tox.readthedocs.org/en/latest/

Openstac工程的持续集成实践1—tox:http://blog.csdn.net/agileclipse/article/details/19044667

Tox是OpenStack持续集成中非常重要的一个通用的虚拟环境管理和测试命令行工具,每个项目源码树的根目录下都有一个Tox配置文件tox.ini,比如Neutron项目的tox.ini部分。

[tox]
envlist = docs,py34,py27,pep8    #测试的Python版本或环境 
minversion = 2.0
skipsdist = True
[testenv]
setenv = VIRTUAL_ENV={envdir}
passenv = TRACE_FAILONLY
usedevelop = True
install_command =
                  constraints: {[testenv:common-constraints]install_command}
                  pip install -U {opts} {packages}
deps = -r{toxinidir}/requirements.txt    #要安装的依赖关系
       -r{toxinidir}/test-requirements.txt
whitelist_externals = sh
commands =       #测试时要执行的命令
  dsvm-functional: {toxinidir}/tools/deploy_rootwrap.sh {toxinidir} {envdir}/etc {envdir}/bin
  ostestr --regex '{posargs}'

如下,我们执行下面的两个tox命令。

# tox –e pep8
# tox –e py27

第一次执行时,会自动安装一些依赖的软件包,如果自动安装失败,我们可以根据提示信息手动执行安装。如果我们只希望执行特定的单元测试代码,不喜欢浪费时间去等待所有单元测试的执行,可以加参数指定,比如为了执行neutron/tests/api/admin/ test_routers_dvr.py文件:

# tox –e py27 -- test_routers_dvr.py

小结

通过以上的分析和理解,我们能够懂得OpenStack的整个持续测试流程和单元测试运行机制,以及每个项目中(这里以Neutron为例)的Mock & Mox单元测试、Tempest集成测试、功能测试,和如何更有效的执行Unit Tests。至于如何为OpenStack中的项目编写单元测试,可按需参考其他资料,比如这篇资料Neutron集成ONOS源码分析中的networking_onos/tests部分。

推荐资料

  • Neutron/TempestAPITests:https://wiki.openstack.org/wiki/Neutron/TempestAPITests
  • Neutron/InTreeTests:https://wiki.openstack.org/wiki/Neutron/InTreeTests
  • NeutronThirdPartyTesting:http://git.openstack.org/cgit/openstack/neutron/tree/doc/source/policies/thirdparty-ci.rst
  • Network/Testing:https://wiki.openstack.org/wiki/Network/Testing
  • Infrastructure:http://docs.openstack.org/infra/system-config/
  • OpenStack Developer and Community Infrastructure
  • Documentation:http://docs.openstack.org/infra/
  • OpenStack git repository browser:https://git.openstack.org/cgit/openstack-infra

作者介绍:徐超,任职于九州云信息科技有限公司(上海),从事OpenStack相关工作。个人倾向于研究CI-CT-CD-CD。

原文发布于微信公众号 - CSDN技术头条(CSDN_Tech)

原文发表时间:2016-01-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程坑太多

『高级篇』docker之DockerSwarm的了解(27)

PS:假定运行了一个nginx服务2个实例,nginx1 和nginx2,容器内的端口是80,主机内的端口是8080, 这2个容器分别运行在node2和node...

13210
来自专栏CSDN技术头条

Redis错误配置详解

Redis提供了许多提高和维护高效内存数据库使用的工具。在无需额外配置应用层的前提下,Redis独特的数据类型、指令和命令调优就可以满足应用的需求,但是错误的配...

233100
来自专栏KID的专栏

【腾讯云的1001种玩法】从购买服务器到建站,从0打造自己的网络领地

记得当年我萌生出要建立一个自己的网站的时候,在网络上搜索了很多教程,但是都不怎么能看懂,于是建站这个事情折腾了我很长的时间。在学习了很多知识之后,我终于能够熟练...

3.6K30
来自专栏月色的自留地

把路由器改装成git服务器(OpenWRT环境的GIT服务器搭建)

在单位中,通常都标配了git服务器用来管理代码。 对于家庭或者小办公室,这种方式有点不经济。当然如果是开源项目就简单了,刚刚被微软收购的github是理想...

1.1K20
来自专栏小狼的世界

Awstats性能问题及其他工具的对比分析

在之前的一篇文章中,我通过资料的查阅分析了一些比较流行的日志分析工具,最后选用了 Awstats + Jawstats 的组合,既能够对现有的日志进行分析,也能...

15160
来自专栏云计算

Kubernetes中的Service Mesh(第5部分):Dogfood环境和入口

原文地址:https://dzone.com/articles/a-service-mesh-for-kubernetes-part-v-dogfood-env...

28880

容纳有状态的应用程序

像Docker Engine这样的应用程序容器技术提供了底层应用程序组件的基于标准的打包和运行时的管理。

225100
来自专栏编程坑太多

『中级篇』docker企业版本地安装之UCP(57)

PS:详细不介绍,就是一个图形化的,没啥介绍的。下次在阿里平台是建立下用云端玩玩。

9820
来自专栏DevOps时代的专栏

基于 Docker 持续交付平台建设的实践

作为创业公司和推行 DevOps 工程师们来说,都遇到过这样的问题: 1. 硬件资源利用率的问题,造成部分成本的浪费 在网站功能中不同的业务场景有计算型的,有...

29570
来自专栏IT技术精选文摘

如何利用配置中心规范构建PaaS服务配置

在上一篇文章中,我们以MQ和ACM为例,讨论了如何借助配置中心对消息进行限流管理的场景。在本文中,我们继续以该场景为例,讲述如何以规范的配置命名格式来进行限流设...

42480

扫码关注云+社区

领取腾讯云代金券