简介

PTF主要功能是通过收发包来验证DUT设备的转发面协议功能工作是否正常。

PTF是由OFTest继承而来,和OFTest不同在于PTF主要聚焦于转发面的测试框架。

testbed 环境配置_docker

图 1. OFTest Framework

运行 PTF 所需软件:

  • Python 2.7
  • Scapy (recommend scapy-vxlan)
  • pypcap (optional - VLAN tests will fail without this)
  • tcpdump (optional - Scapy will complain if it's missing)

在SONiC Testbed中,PTF是以docker的形式运行在testbed server上,所以在搭建SONiC testbed之前,首先我们需要准备好PTF docker,并将它load进testbed server上。在整个testbed的测试中,PTF起到很大的作用,下面我们将结合testbed介绍PTF的相关功能,并解释其在testbed中的一些拓扑结构。

docker-ptf 编译

  1. Build docker-ptf image
git clone --recursive https://github.com/Azure/sonic-buildimage.git
cd sonic-buildimage
make configure PLATFORM=vs ;#takes about 1 hour or more
make target/docker-ptf.gz
  1. upload docker-ptf to your registry
clsnet@SDK-SONiC:~/george$ docker load -i docker-ptf.gz
f14da82c36e2: Loading layer [==================================================>]  119.3MB/119.3MB
5e2c7951bac5: Loading layer [==================================================>]  1.724GB/1.724GB
Loaded image: docker-ptf:latest


clsnet@SDK-SONiC:~$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
docker-ptf          latest    daeef4ab73fa   20 seconds ago   964MB


clsnet@SDK-SONiC:~$ docker tag docker-ptf:latest localhost:5000/docker-ptf:latest

clsnet@SDK-SONiC:~$ docker images
REPOSITORY                  TAG       IMAGE ID       CREATED              SIZE
docker-ptf                  latest    daeef4ab73fa   About a minute ago   964MB
localhost:5000/docker-ptf   latest    daeef4ab73fa   About a minute ago   964MB

clsnet@SDK-SONiC:~$ docker push localhost:5000/docker-ptf:latest
The push refers to repository [localhost:5000/docker-ptf]
f94269d57252: Pushed
latest: digest: sha256:40482ca2f40bca4bc391ef79a22472f74a572bad26dd3cd5241a7cebfa7ced64 size: 529

如果没有本地仓库,使用如下命令创建本地仓库:

docker run -d -p 5000:5000 --name registry registry:2.7

具体docker仓库配置参考如下blog:

how-to-use-your-own-registry

PTF端口类型

PTF的端口类型分为两类:直连口(direct port) 和 注入口(injected port)。

PTF with direct port

testbed 环境配置_docker_02

直连口就是DUT设备的面板口直接与PTF容器的端口相连。通常是PTF容器的eth0和DUT的Ethernet0相连,PTF的eth1和DUT的Ethernet4相连,依此类推。这种连接类型经常用于testbed PTF 拓扑以及T0 拓扑。

PTF with injected port

testbed 环境配置_docker_03

DUT的面板口和VMs的一个端口直连,但是与此同时PTF也挂载在这条连接上,报文从DUT物理端口可以同时发送给VMs和PTF docker。VM和PTF docker的报文都可以发送给DUT物理端口,这种连接方式允许我们通过PTF docker向VM和DUT之间的BGP域注入报文并验证报文,这种连接常用于testbed的T0及T1拓扑结构中。

这种连接的实现方式,是通过Open vSwitch 来实现,将VMs、PTF及DUT的端口连接到Open vSwitch上,通过配置Open vSwitch的规则,实现如上图红色箭头的报文转发规则。

testbed物理拓扑结构

testbed 环境配置_testbed 环境配置_04

  1. DUT的端口和leaf fanout的端口一一对应相连
  2. 每个leaf fanout和DUT互连的端口,都会有一个唯一的vlan表示
  3. Root fanout用于连接leaf fanout和testbed server,root fanout不是必须的,简化的topo中,可以去掉root fanout,直接将leaf fanout的端口连接到testbed server上
  4. 和Root fanout的相连的端口,一般都是配置成802.1Q trunk口
  5. testbed server通过在报文中加入相应的vlan tag可以访问任意DUT的任何端口

testbed server的逻辑拓扑

testbed 环境配置_python脚本_05

在Testbed测试中,主要由DUT、FANOUT及testbed server组成,其中Fanout设备主要用于如上图所示的testbed server拓扑,在整个Testbed测试中,除了DUT外最重要的就是testbed server的拓扑配置,该拓扑主要包含VMs及PTF docker等模块,通过OVS及VLAN的划分,实现不同的testbed测试拓扑。

下面以testbed T0 拓扑结构为例,分别从management模块、back-plane模块已经PTF端口模式配置等方面,介绍PTF在其中所起的作用,通过本文的介绍,大家会对testbed如何管理PTF,bgp的实现,已经PTF端口模式的配置有一个大概的了解。

management port逻辑拓扑

管理口是用来管理设备的通道,用户可以通过ssh等方式登录到设备上做一些操作。testbed server上是通过创建一个bridge,将VMs、 PTF以及server的management port添加到该bridge中实现相管理口互通。

br1             8000.6ab9a190c7c4       no              VM0300-m
                                                        VM0301-m
                                                        VM0302-m
                                                        VM0303-m
                                                        eno1
                                                        ptf-ptf1-m

拓扑结构如下图所示,每个VM有一个VMxxx-m的端口、PTF docker也有一个ptf-ptfx-m的端口,将这些端口和server的一个management端口绑定在一个bridge中,这样用户就可以通过server management端口访问所有的虚拟设备。

testbed 环境配置_docker_06

back-plane逻辑拓扑

back-plane口之间的互联的主要用是实现VMs和PTF之间通过exabgp交换路由信息。同management port一样,也是将VMs和PTF的back-plane口加入一个bridge实现相互间通信,如下图所示。

testbed 环境配置_bc_07

br-b-ptf1               8000.8245a6e5194d       no      VM0300-back
                                                        VM0301-back
                                                        VM0302-back
                                                        VM0303-back
                                                        ptf-ptf1-b

进入ptf docker可以查看ptf上运行的exabgp服务:

root@6884a0fcd031:~# supervisorctl status
exabgp-ARISTA01T1                RUNNING   pid 118, uptime 2 days, 8:16:45
exabgp-ARISTA01T1-v6             RUNNING   pid 131, uptime 2 days, 8:16:42
exabgp-ARISTA02T1                RUNNING   pid 92, uptime 2 days, 8:16:53
exabgp-ARISTA02T1-v6             RUNNING   pid 105, uptime 2 days, 8:16:49
exabgp-ARISTA03T1                RUNNING   pid 66, uptime 2 days, 8:17:00
exabgp-ARISTA03T1-v6             RUNNING   pid 79, uptime 2 days, 8:16:57
exabgp-ARISTA04T1                RUNNING   pid 40, uptime 2 days, 8:17:07
exabgp-ARISTA04T1-v6             RUNNING   pid 53, uptime 2 days, 8:17:04
ptf_nn_agent                     RUNNING   pid 14, uptime 2 days, 10:35:21
sshd                             RUNNING   pid 13, uptime 2 days, 10:35:21

使用方法:

ptfhost.exabgp(name=k,
               state="started", \
               router_id = 10.0.0.1, \
               local_ip  = 10.0.0.1, \
               peer_ip   = 10.0.0.2, \
               local_asn = 65100, \
               peer_asn  = 65100, \
               port = 6000)

ptf direct port的逻辑拓扑

ptf和DUT端口对应关系由ptf docker中brcm_interface_to_front_map.ini文件配置,具体配置如下:

brcm_interface_to_front_map.ini                                                         
 # ptf host interface @ switch front port name
 0@Ethernet0
 1@Ethernet4
 2@Ethernet8
 …………
 29@Ethernet116
 30@Ethernet120
 31@Ethernet124

具体是将端口配置成direct port 或者 inject port模式,是由testbed中ansible/vars/topo-xxx.yml文件中的配置决定,我们以T0拓扑为例,在该拓扑中,只定义了4个inject port,其它都市direct port。

topology:
  host_interfaces:
    - 0
    - 1
…………
    - 26
    - 27
  disabled_host_interfaces:
    - 0
    - 25
    - 26
    - 27
  VMs:
    ARISTA01T1:
      vlans:
        - 28
      vm_offset: 0
    ARISTA02T1:
      vlans:
        - 29
      vm_offset: 1
    ARISTA03T1:
      vlans:
        - 30
      vm_offset: 2
    ARISTA04T1:
      vlans:
        - 31
      vm_offset: 3

PTF端口和DUT的端口对应关系,如下图所示:

testbed 环境配置_python脚本_08

PTF docker 802.1Q info:

root@62960e1f493f:/# cat /proc/net/vlan/config
VLAN Dev name    | VLAN ID
Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
eth0           | 100  | enp175s0f1
eth1           | 101  | enp175s0f1
eth2           | 102  | enp175s0f1
eth3           | 103  | enp175s0f1
eth4           | 104  | enp175s0f1
eth5           | 105  | enp175s0f1
eth6           | 106  | enp175s0f1
eth7           | 107  | enp175s0f1
eth8           | 108  | enp175s0f1
eth9           | 109  | enp175s0f1
eth10          | 110  | enp175s0f1
eth11          | 111  | enp175s0f1
eth12          | 112  | enp175s0f1
eth13          | 113  | enp175s0f1
eth14          | 114  | enp175s0f1
eth15          | 115  | enp175s0f1
eth16          | 116  | enp175s0f1
eth17          | 117  | enp175s0f1
eth18          | 118  | enp175s0f1
eth19          | 119  | enp175s0f1
eth20          | 120  | enp175s0f1
eth21          | 121  | enp175s0f1
eth22          | 122  | enp175s0f1
eth23          | 123  | enp175s0f1
eth24          | 124  | enp175s0f1
eth25          | 125  | enp175s0f1
eth26          | 126  | enp175s0f1

ptf injected port的拓扑

注入口相对于直连口要复杂一些,上面介绍PTF端口类型的时候有说过,injected port是通过Open vSwitch将PTF、VM及server Trunk口连接在一起,具体连接如下图所示:

testbed 环境配置_docker_09

查看server 上的802.1Q 信息:

clsnet@clsnet-PowerEdge-R540:~$ sudo cat /proc/net/vlan/config
[sudo] password for clsnet:
VLAN Dev name    | VLAN ID
Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
enp175s0f1.127 | 127  | enp175s0f1
enp175s0f1.128 | 128  | enp175s0f1
enp175s0f1.129 | 129  | enp175s0f1
enp175s0f1.130 | 130  | enp175s0f1

server上ovs bridge 信息如下,每个VM会创建一个bridge,将相应的端口加入进来,实现injected port功能。

clsnet@clsnet-PowerEdge-R540:~$ sudo ovs-vsctl show
51fa9729-7bc7-4225-87ce-f8bc845cf396
    Bridge "br-VM0300-0"
        Port "enp175s0f1.127"
            Interface "enp175s0f1.127"
        Port "VM0300-t0"
            Interface "VM0300-t0"
        Port "inje-ptf1-27"
            Interface "inje-ptf1-27"
        Port "br-VM0300-0"
            Interface "br-VM0300-0"
                type: internal

使用范例

利用vlan的一个testcase,具体解析一下ptf发包及收包验证的流程。

测试步骤

1、构建报文

首先,在ansible测试框架下,通过python脚本调用ptf库中封装的构包函数,封装一个以太网报文,示例代码如下所示。

import ptf.packet as scapy
import ptf.testutils as testutils
from ptf.mask import Mask

def build_icmp_packet(vlan_id, src_mac="00:22:00:00:00:02", dst_mac="ff:ff:ff:ff:ff:ff",
                         src_ip="192.168.0.1", dst_ip="192.168.0.2", ttl=64):

     pkt = testutils.simple_icmp_packet(pktlen=100 if vlan_id == 0 else 104,
                                 eth_dst=dst_mac,
                                 eth_src=src_mac,
                                 dl_vlan_enable=False if vlan_id == 0 else True,
                                 vlan_vid=vlan_id,
                                 vlan_pcp=0,
                                 ip_src=src_ip,
                                 ip_dst=dst_ip,
                                 ip_ttl=ttl)


pkt = build_icmp_packet(0)
         logger.info("Send untagged packet from {} ...".format(vlan_port["port_index"][0]))
         logger.info(pkt.sprintf("%Ether.src% %IP.src% -> %Ether.dst% %IP.dst%"))

2、发送报文

python脚本通过调用ptf中的send函数,将上一步封装的报文,从指定的port发往DUT设备处理。

def send(test, port_id, pkt, count=1):

3、验证报文

python脚本在发送完报文后通过使用ptf中dp_poll函数,在ptf中收包,poll指定端口是否收到和exp_pkt一致的报文,如收到表示测试成功,没有收到,则测试失败。

def dp_poll(test, device_number=0, port_number=None, timeout=None, exp_pkt=None):

vlan testcase 拓扑

testbed 环境配置_docker_10

dut vlan配置信息

admin@cel-silverstone-01:~$ show vlan brief
+--------+------------------+-----------------+----------+--------------+-------------+
|VLAN ID | IP Address       | Ports           | Port     | DHCP Helper  | Proxy ARP   |
|        |                  |                 | Tagging  |  Address     |             |
+========+==================+=================+==========+==============+=============+
|    100 | 192.168.100.1/24 | Ethernet0       | tagged   |              | disabled    |
|        |                  | Ethernet4       | untagged |              |             |
|        |                  | Ethernet8       | untagged |              |             |
|        |                  | Ethernet100     | tagged   |              |             |
|        |                  | PortChannel0001 | untagged |              |             |
|        |                  | PortChannel0003 | tagged   |              |             |
+--------+------------------+-----------------+----------+--------------+-------------+
|    200 | 192.168.200.1/24 | Ethernet0       | untagged |              | disabled    |
|        |                  | Ethernet4       | tagged   |              |             |
|        |                  | Ethernet8       | tagged   |              |             |
|        |                  | Ethernet100     | untagged |              |             |
|        |                  | PortChannel0001 | tagged   |              |             |
|        |                  | PortChannel0003 | untagged |              |             |
+--------+------------------+-----------------+----------+--------------+-------------+
|   1000 | 192.168.0.1/21   | Ethernet12      | untagged | 192.0.0.1    | disabled    |
|        | fc02:1000::1/64  | Ethernet16      | untagged | 192.0.0.2    |             |
|        |                  | Ethernet20      | untagged | 192.0.0.3    |             |
|        |                  | Ethernet24      | untagged | 192.0.0.4    |             |
|        |                  | Ethernet28      | untagged |              |             |
|        |                  | Ethernet32      | untagged |              |             |
|        |                  | Ethernet36      | untagged |              |             |
|        |                  | Ethernet40      | untagged |              |             |
|        |                  | Ethernet44      | untagged |              |             |
|        |                  | Ethernet48      | untagged |              |             |
|        |                  | Ethernet52      | untagged |              |             |
|        |                  | Ethernet56      | untagged |              |             |
|        |                  | Ethernet60      | untagged |              |             |
|        |                  | Ethernet64      | untagged |              |             |
|        |                  | Ethernet68      | untagged |              |             |
|        |                  | Ethernet72      | untagged |              |             |
|        |                  | Ethernet76      | untagged |              |             |
|        |                  | Ethernet80      | untagged |              |             |
|        |                  | Ethernet84      | untagged |              |             |
|        |                  | Ethernet88      | untagged |              |             |
|        |                  | Ethernet92      | untagged |              |             |
|        |                  | Ethernet96      | untagged |              |             |
+--------+------------------+-----------------+----------+--------------+-------------+

dut route配置信息

S>* 0.0.0.0/0 [200/0] via 10.250.0.1, eth0, weight 1, 01:50:35
C>* 10.1.0.32/32 is directly connected, Loopback0, 01:50:26
C>* 10.250.0.0/24 is directly connected, eth0, 01:50:37
K>* 100.1.1.2/32 [0/0] via 192.168.100.2, Vlan100, 00:00:10
K>* 100.1.1.4/32 [0/0] via 192.168.100.4, Vlan100, 00:00:09
K>* 100.1.1.5/32 [0/0] via 192.168.100.5, Vlan100, 00:00:11
K>* 100.1.1.8/32 [0/0] via 192.168.100.8, Vlan100, 00:00:08
K>* 100.1.1.10/32 [0/0] via 192.168.100.10, Vlan100, 00:00:07
K>* 100.1.1.23/32 [0/0] via 192.168.100.23, Vlan100, 00:00:10
C>* 192.168.0.0/21 is directly connected, Vlan1000, 01:50:16
C>* 192.168.100.0/24 is directly connected, Vlan100, 00:01:40
C>* 192.168.200.0/24 is directly connected, Vlan200, 00:01:38
K>* 200.1.1.2/32 [0/0] via 192.168.200.2, Vlan200, 00:00:10
K>* 200.1.1.4/32 [0/0] via 192.168.200.4, Vlan200, 00:00:09
K>* 200.1.1.5/32 [0/0] via 192.168.200.5, Vlan200, 00:00:11
K>* 200.1.1.8/32 [0/0] via 192.168.200.8, Vlan200, 00:00:08
K>* 200.1.1.10/32 [0/0] via 192.168.200.10, Vlan200, 00:00:08
K>* 200.1.1.23/32 [0/0] via 192.168.200.23, Vlan200, 00:00:11