设计说明
网络设备功能测试自动化时,有成千上万个测试案例,幸好拓扑总共只有几十种。但是仍然不能针对每种拓扑都去连好线,这样资源开销太大,又难以维护。实际中我们使用拓扑交换,每台DUT只需要和拓扑交换机相连即可。然后定义好这几十种逻辑拓扑的xml文件,测试用例中指明所使用的拓扑。运行用例时根据逻辑拓扑来划分拓扑交换机上的vlan,从而实现逻辑拓扑的自动生成。同时也会为每个DUT的接口自动配置IP地址。
这种逻辑拓扑生成的方式非常灵活,支持以任意顺序执行全部或者任意部分的测试用例。
扩展性好:增加新拓扑只需要定义新的逻辑拓扑xml文件。如果DUT数目不足,可以在物理拓扑中增加新的DUT。如果拓扑交换机的端口不足,可以轻松扩展为支持多个拓扑交换机。
代码实现
pysical_top.xml:
这里dut1使用了逻辑端口编号,而拓扑交换机使用了实际的接口编号。是因为DUT的端口常会变化,但是拓扑交换机的接口通常不变。这里只列出了三台DUT,实际中DUT比这个多。实际中还有测试仪接口连接在拓扑交换机上。但是因为对测试仪接口的拓扑生成和DUT的一样,所以这里没有额外再列出。这个框架可以轻松扩展为支持多个拓扑交换机,只需要将物理拓扑中的拓扑交换机即dst_dev也提取出来,后续和单机一样水平扩展即可。
<?xml version="1.0" encoding="UTF-8"?>
<Topology>
<Devices name="dut1" type="dut" model="router">
<Connections src_dev="dut1" src_port="port1" dst_dev="tsw1" dst_port="gigabitethernet 1/0/0"></Connections>
<Connections src_dev="dut1" src_port="port2" dst_dev="tsw1" dst_port="gigabitethernet 1/0/1"></Connections>
<Connections src_dev="dut1" src_port="port3" dst_dev="tsw1" dst_port="gigabitethernet 1/0/2"></Connections>
<Connections src_dev="dut1" src_port="port4" dst_dev="tsw1" dst_port="gigabitethernet 1/0/3"></Connections>
</Devices>
<Devices name="dut2" type="dut" model="router">
<Connections src_dev="dut2" src_port="port1" dst_dev="tsw1" dst_port="gigabitethernet 1/0/4"></Connections>
<Connections src_dev="dut2" src_port="port2" dst_dev="tsw1" dst_port="gigabitethernet 1/0/5"></Connections>
<Connections src_dev="dut2" src_port="port3" dst_dev="tsw1" dst_port="gigabitethernet 1/0/6"></Connections>
<Connections src_dev="dut2" src_port="port4" dst_dev="tsw1" dst_port="gigabitethernet 1/0/7"></Connections>
</Devices>
<Devices name="dut3" type="dut" model="router">
<Connections src_dev="dut3" src_port="port1" dst_dev="tsw1" dst_port="gigabitethernet 1/0/8"></Connections>
<Connections src_dev="dut3" src_port="port2" dst_dev="tsw1" dst_port="gigabitethernet 1/0/9"></Connections>
<Connections src_dev="dut3" src_port="port3" dst_dev="tsw1" dst_port="gigabitethernet 1/0/10"></Connections>
<Connections src_dev="dut3" src_port="port4" dst_dev="tsw1" dst_port="gigabitethernet 1/0/11"></Connections>
</Devices>
</Topology>
triangle.xml
本例中定义了三角形的逻辑拓扑xml文件triangle.xml。
<?xml version="1.0" encoding="UTF-8"?>
<Connections src_dev="dut1" src_port="port1" dst_dev="dut2" dst_port="port1"></Connections>
<Connections src_dev="dut1" src_port="port2" dst_dev="dut3" dst_port="port2"></Connections>
<Connections src_dev="dut2" src_port="port2" dst_dev="dut3" dst_port="port1"></Connections>
topology.py
#python3.8
#coding=utf-8
#author=liuyifan
VLAN_START = 4000
IP_ADDRESS_PATTERN = "51.0.{}.{}/24"
from bs4 import BeautifulSoup
class Topology():
# 解析物理拓扑文件,将所有connections存储到字典中,(src_dev,src_port)是key,拓扑交换机的接口即dst_port是value
def parse_pysical_top(self,xml_top_file):
d = {}
soup = BeautifulSoup(open(xml_top_file), 'html.parser')
for c in soup.find_all("connections"):
d[c.get("src_dev"),c.get("src_port")]=c.get("dst_port")
return d
# 解析逻辑拓扑文件,将所有connections存储到列表中,这是一个两层列表,第一层每一个元素代表一个connection,
# 对于每一个connection列表第一个元素是(src_dev,src_port),第二个元素是(dst_dev,dst_port)
def parse_logical_top(self,xml_top_file):
lst, inner_lst = [], []
soup = BeautifulSoup(open(xml_top_file), 'html.parser')
for c in soup.find_all("connections"):
inner_lst.append((c.get("src_dev"),c.get("src_port")))
inner_lst.append((c.get("dst_dev"),c.get("dst_port")))
lst.append(inner_lst)
inner_lst = []
return lst
# 输入为parse_logical_top()的返回值,为每一个接口分配一个ip地址
# 所有connections存储到列表中,这个一个三层列表,第一层每一个元素代表一个connection,
# 对于一个connection列表第一个元素是[src_dev,src_port,ip_address],第二个元素是[dst_dev,dst_port,ip_address]。
def generate_ip_address(self,logic):
lst, inner_lst, inner_inner_lst = [], [], []
for i in range(len(logic)):
for j in range(len(logic[i])):
inner_inner_lst.append(logic[i][j][0])
inner_inner_lst.append(logic[i][j][1])
inner_inner_lst.append(IP_ADDRESS_PATTERN.format(i+1,j+1))
inner_lst.append(inner_inner_lst)
inner_inner_lst = []
lst.append(inner_lst)
inner_lst = []
return lst
# 综合物理拓扑和逻辑拓扑的解析结果,得出拓扑交换机物理接口vlan划分关系。
# 返回一个2维列表,一级列表中每个元素是一个列表。内层列表的内容为两个拓扑交换机的接口和它们所属的vlan。
def partition_tsw_vlan(self,pysical,logic):
lst, inner_lst = [], []
vlan_offset = 0
for connection in logic:
for intf in connection:
# 找到dut该接口所连的拓扑交换机的物理接口
inner_lst.append(pysical[intf])
inner_lst.append(VLAN_START + vlan_offset)
vlan_offset += 1
lst.append(inner_lst)
inner_lst = []
return lst
if __name__ == '__main__':
top = Topology()
pysical = top.parse_pysical_top("pysical_top.xml")
print("物理拓扑解析结果:")
pprint.pprint(pysical)
logic = top.parse_logical_top("triangle.xml")
print("逻辑拓扑解析结果:")
pprint.pprint(logic)
vlans = top.partition_tsw_vlan(pysical,logic)
print("拓扑交换机VLAN划分方案:")
pprint.pprint(vlans)
addresses = top.generate_ip_address(logic)
print("DUT接口IP地址分配方案:")
pprint.pprint(addresses)
#最后根据vlans中指定的vlan关系,使用拓扑交换机实际的命令来划分出vlan,就完成了逻辑拓扑的生成。
#根据配置文件找出实际的DUT登录方式和对应的实际接口,配置相应的IP地址。
运行输出:
物理拓扑解析结果:
{('dut1', 'port1'): 'gigabitethernet 1/0/0',
('dut1', 'port2'): 'gigabitethernet 1/0/1',
('dut1', 'port3'): 'gigabitethernet 1/0/2',
('dut1', 'port4'): 'gigabitethernet 1/0/3',
('dut2', 'port1'): 'gigabitethernet 1/0/4',
('dut2', 'port2'): 'gigabitethernet 1/0/5',
('dut2', 'port3'): 'gigabitethernet 1/0/6',
('dut2', 'port4'): 'gigabitethernet 1/0/7',
('dut3', 'port1'): 'gigabitethernet 1/0/8',
('dut3', 'port2'): 'gigabitethernet 1/0/9',
('dut3', 'port3'): 'gigabitethernet 1/0/10',
('dut3', 'port4'): 'gigabitethernet 1/0/11'}
逻辑拓扑解析结果:
[[('dut1', 'port1'), ('dut2', 'port1')],
[('dut1', 'port2'), ('dut3', 'port2')],
[('dut2', 'port2'), ('dut3', 'port1')]]
拓扑交换机VLAN划分方案:
[['gigabitethernet 1/0/0', 'gigabitethernet 1/0/4', 4000],
['gigabitethernet 1/0/1', 'gigabitethernet 1/0/9', 4001],
['gigabitethernet 1/0/5', 'gigabitethernet 1/0/8', 4002]]
DUT接口IP地址分配方案:
[[['dut1', 'port1', '51.0.1.1/24'], ['dut2', 'port1', '51.0.1.2/24']],
[['dut1', 'port2', '51.0.2.1/24'], ['dut3', 'port2', '51.0.2.2/24']],
[['dut2', 'port2', '51.0.3.1/24'], ['dut3', 'port1', '51.0.3.2/24']]]