Skip to content

Commit b78c4b2

Browse files
authored
[testbed] ptf data plane connection for multi-servers testbed (#15881)
What is the motivation for this PR? We need to make sure ptf data plane connectivity during tests running. How did you do it? Start ptf_nn_agent on every ptf container and connect them with ptf dataplane. How did you verify/test it? Run test case with ptf dataplane on single server testbed and multi-servers testbed.
1 parent 17fd7df commit b78c4b2

File tree

9 files changed

+164
-98
lines changed

9 files changed

+164
-98
lines changed

tests/common/multi_servers_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../ansible/module_utils/multi_servers_utils.py

tests/common/plugins/ptfadapter/__init__.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
import time
55

6-
from .ptfadapter import PtfTestAdapter
6+
from .ptfadapter import PtfTestAdapter, PtfAgent
77
import ptf.testutils
88

99
from tests.common import constants
@@ -112,7 +112,7 @@ def get_ifaces_map(ifaces, ptf_port_mapping_mode, need_backplane=False):
112112

113113

114114
@pytest.fixture(scope='module')
115-
def ptfadapter(ptfhost, tbinfo, request, duthost):
115+
def ptfadapter(ptfhosts, tbinfo, request, duthost):
116116
"""return ptf test adapter object.
117117
The fixture is module scope, because usually there is not need to
118118
restart PTF nn agent and reinitialize data plane thread on every
@@ -127,22 +127,13 @@ def ptfadapter(ptfhost, tbinfo, request, duthost):
127127
else:
128128
ptf_port_mapping_mode = 'use_orig_interface'
129129

130-
need_backplane = False
131-
if 'ciscovs-7nodes' in tbinfo['topo']['name']:
132-
need_backplane = True
133-
134-
# get the eth interfaces from PTF and initialize ifaces_map
135-
res = ptfhost.command('cat /proc/net/dev')
136-
ifaces = get_ifaces(res['stdout'])
137-
ifaces_map = get_ifaces_map(ifaces, ptf_port_mapping_mode, need_backplane)
138-
139-
def start_ptf_nn_agent():
130+
def start_ptf_nn_agent(device_num):
140131
for i in range(MAX_RETRY_TIME):
141132
ptf_nn_port = random.randint(*DEFAULT_PTF_NN_PORT_RANGE)
142133

143134
# generate supervisor configuration for ptf_nn_agent
144135
ptfhost.host.options['variable_manager'].extra_vars.update({
145-
'device_num': DEFAULT_DEVICE_NUM,
136+
'device_num': device_num,
146137
'ptf_nn_port': ptf_nn_port,
147138
'ifaces_map': ifaces_map,
148139
})
@@ -163,8 +154,17 @@ def start_ptf_nn_agent():
163154
return ptf_nn_port
164155
return None
165156

166-
ptf_nn_agent_port = start_ptf_nn_agent()
167-
assert ptf_nn_agent_port is not None
157+
need_backplane = False
158+
if 'ciscovs-7nodes' in tbinfo['topo']['name']:
159+
need_backplane = True
160+
ptfagents = []
161+
for seq, ptfhost in enumerate(ptfhosts):
162+
res = ptfhost.command('cat /proc/net/dev')
163+
ifaces = get_ifaces(res['stdout'])
164+
ifaces_map = get_ifaces_map(ifaces, ptf_port_mapping_mode, need_backplane)
165+
ptf_nn_agent_port = start_ptf_nn_agent(seq)
166+
ptfagents.append(PtfAgent(ptfhost.mgmt_ip, ptfhost.mgmt_ipv6, ptf_nn_agent_port, seq, ifaces_map))
167+
assert ptf_nn_agent_port is not None
168168

169169
def check_if_use_minigraph_from_tbinfo(tbinfo):
170170
if 'properties' in tbinfo['topo'] and "init_cfg_profile" in tbinfo['topo']['properties']:
@@ -174,8 +174,7 @@ def check_if_use_minigraph_from_tbinfo(tbinfo):
174174
return False
175175
return True
176176

177-
with PtfTestAdapter(tbinfo['ptf_ip'], tbinfo['ptf_ipv6'],
178-
ptf_nn_agent_port, 0, list(ifaces_map.keys()), ptfhost) as adapter:
177+
with PtfTestAdapter(ptfagents, ptfhosts) as adapter:
179178
if not request.config.option.keep_payload:
180179
override_ptf_functions()
181180
node_id = request.module.__name__
@@ -189,12 +188,12 @@ def check_if_use_minigraph_from_tbinfo(tbinfo):
189188

190189

191190
@pytest.fixture(scope='module')
192-
def nbr_device_numbers(nbrhosts):
191+
def nbr_device_numbers(nbrhosts, ptfhosts):
193192
"""return the mapping of neighbor devices name to ptf device number.
194193
"""
195194
numbers = sorted(nbrhosts.keys())
196195
device_numbers = {
197-
nbr_name: numbers.index(nbr_name) + DEFAULT_DEVICE_NUM + 1
196+
nbr_name: numbers.index(nbr_name) + len(ptfhosts)
198197
for nbr_name in list(nbrhosts.keys())}
199198
return device_numbers
200199

tests/common/plugins/ptfadapter/ptfadapter.py

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
import logging
1212

1313

14+
class PtfAgent:
15+
def __init__(self, ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set):
16+
self.ptf_ip = ptf_ip
17+
self.ptf_ipv6 = ptf_ipv6
18+
self.ptf_nn_port = ptf_nn_port
19+
self.device_num = device_num
20+
self.ptf_port_set = ptf_port_set
21+
22+
1423
class PtfAdapterNNConnectionError(Exception):
1524

1625
def __init__(self, remote_sock_addr):
@@ -30,21 +39,17 @@ class PtfTestAdapter(BaseTest):
3039
# the number of currently established connections
3140
NN_STAT_CURRENT_CONNECTIONS = 201
3241

33-
def __init__(self, ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set, ptfhost):
42+
def __init__(self, ptfagents, ptfhosts):
3443
""" initialize PtfTestAdapter
35-
:param ptf_ip: PTF host IP
36-
:param ptf_ipv6: PTF host IPv6 address
37-
:param ptf_nn_port: PTF nanomessage agent port
38-
:param device_num: device number
39-
:param ptf_port_set: PTF ports
40-
:return:
4144
"""
4245
self.runTest = lambda: None # set a no op runTest attribute to satisfy BaseTest interface
4346
super(PtfTestAdapter, self).__init__()
4447
self.payload_pattern = ""
4548
self.connected = False
46-
self.ptfhost = ptfhost
47-
self._init_ptf_dataplane(ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set)
49+
self.ptfhosts = ptfhosts
50+
self.ptfagents = ptfagents
51+
self.ptf_port_set = [k for a in ptfagents for k in a.ptf_port_set.keys()]
52+
self._init_ptf_dataplane()
4853

4954
def __enter__(self):
5055
""" enter in 'with' block """
@@ -65,42 +70,33 @@ def _check_ptf_nn_agent_availability(self, socket_addr):
6570
finally:
6671
sock.close()
6772

68-
def _init_ptf_dataplane(self, ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set, ptf_config=None):
73+
def _init_ptf_dataplane(self, ptf_config=None):
6974
"""
7075
initialize ptf framework and establish connection to ptf_nn_agent
7176
running on PTF host
72-
:param ptf_ip: PTF host IP
73-
:param ptf_ipv6: PTF host IPv6 address
74-
:param ptf_nn_port: PTF nanomessage agent port
75-
:param device_num: device number
76-
:param ptf_port_set: PTF ports
7777
:return:
7878
"""
79-
self.ptf_ip = ptf_ip
80-
self.ptf_ipv6 = ptf_ipv6
81-
self.ptf_nn_port = ptf_nn_port
82-
self.device_num = device_num
83-
self.ptf_port_set = ptf_port_set
8479
self.connected = False
8580

8681
ptfutils.default_timeout = self.DEFAULT_PTF_TIMEOUT
8782
ptfutils.default_negative_timeout = self.DEFAULT_PTF_NEG_TIMEOUT
8883

89-
ptf_nn_sock_addr = 'tcp://{}:{}'.format(ptf_ip, ptf_nn_port)
90-
9184
ptf.config.update({
9285
'platform': 'nn',
93-
'device_sockets': [
94-
(device_num, ptf_port_set, ptf_nn_sock_addr)
95-
],
86+
'device_sockets': [],
9687
'qlen': self.DEFAULT_PTF_QUEUE_LEN,
9788
'relax': True,
9889
})
90+
9991
if ptf_config is not None:
10092
ptf.config.update(ptf_config)
10193

102-
if not self._check_ptf_nn_agent_availability(ptf_nn_sock_addr):
103-
raise PtfAdapterNNConnectionError(ptf_nn_sock_addr)
94+
for ptfagent in self.ptfagents:
95+
ptf_nn_sock_addr = 'tcp://{}:{}'.format(ptfagent.ptf_ip, ptfagent.ptf_nn_port)
96+
ptf.config['device_sockets'].append((ptfagent.device_num, ptfagent.ptf_port_set, ptf_nn_sock_addr))
97+
98+
if not self._check_ptf_nn_agent_availability(ptf_nn_sock_addr):
99+
raise PtfAdapterNNConnectionError(ptf_nn_sock_addr)
104100

105101
# update ptf.config based on NN platform and create dataplane instance
106102
nn.platform_config_update(ptf.config)
@@ -112,6 +108,26 @@ def _init_ptf_dataplane(self, ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_por
112108
device_id, port_id = id
113109
ptf.dataplane_instance.port_add(ifname, device_id, port_id)
114110
self.connected = True
111+
ptf.dataplane_instance.port_device_map = {p: d for d, p in ptf.dataplane_instance.ports.keys()}
112+
ptf.dataplane_instance.port_to_device = lambda port: ptf.dataplane_instance.port_device_map[port]
113+
ptf.dataplane_instance.port_to_tuple = lambda port: (ptf.dataplane_instance.port_device_map[port], port)
114+
ptf.dataplane_instance._poll = ptf.dataplane_instance.poll
115+
ptf.dataplane_instance.poll = lambda device_number, port_number=None, timeout=None, exp_pkt=None, filters=[]: \
116+
ptf.dataplane_instance._poll(
117+
ptf.dataplane_instance.port_to_device(port_number)
118+
if port_number is not None else device_number if device_number is not None else None,
119+
port_number,
120+
timeout,
121+
exp_pkt,
122+
filters
123+
)
124+
ptf.dataplane_instance._send = ptf.dataplane_instance.send
125+
ptf.dataplane_instance.send = lambda device_number, port_number, packet: \
126+
ptf.dataplane_instance._send(
127+
ptf.dataplane_instance.port_to_device(port_number),
128+
port_number,
129+
packet
130+
)
115131
self.dataplane = ptf.dataplane_instance
116132

117133
def kill(self):
@@ -136,12 +152,12 @@ def reinit(self, ptf_config=None):
136152

137153
# Restart ptf_nn_agent to close any TCP connection from the server side
138154
logging.info("Restarting ptf_nn_agent")
139-
self.ptfhost.command('supervisorctl reread')
140-
self.ptfhost.command('supervisorctl update')
141-
self.ptfhost.command('supervisorctl restart ptf_nn_agent')
155+
for ptfhost in self.ptfhosts:
156+
ptfhost.command('supervisorctl reread')
157+
ptfhost.command('supervisorctl update')
158+
ptfhost.command('supervisorctl restart ptf_nn_agent')
142159

143-
self._init_ptf_dataplane(self.ptf_ip, self.ptf_ipv6, self.ptf_nn_port,
144-
self.device_num, self.ptf_port_set, ptf_config)
160+
self._init_ptf_dataplane(ptf_config)
145161

146162
def update_payload(self, pkt):
147163
"""Update the payload of packet to the default pattern when certain conditions are met.

tests/common/ptf_agent_updater.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,61 +5,66 @@ class PtfAgentUpdater(object):
55
"""
66
PtfAgentUpdater class for updating ptf_nn_agent on PTF host
77
"""
8-
def __init__(self, ptfhost, ptfadapter, ptf_nn_agent_template):
8+
def __init__(self, ptfhosts, ptfadapter, ptf_nn_agent_template):
99
"""
1010
Initialize an object for updating ptf_nn_agent
1111
1212
Args:
13-
ptfhost: PTF host object
13+
ptfhosts: PTF hosts object
1414
ptfadapter: PTF adapter
1515
ptf_nn_agent_template: ptf_nn_agent template
1616
"""
17-
self.ptfhost = ptfhost
17+
self.ptfhosts = ptfhosts
1818
self.ptfadapter = ptfadapter
1919
self.ptf_nn_agent_template = ptf_nn_agent_template
2020

21-
def configure_ptf_nn_agent(self, ifaces):
21+
def configure_ptf_nn_agent(self, ifaces, ptf_index=0):
2222
"""
2323
Add new interfaces to interfaces map of ptfadapter
2424
2525
Args:
2626
ifaces: List of interface names
27+
ptf_index: The index of ptfhost in ptfhosts to configure ptf_nn_agent
2728
"""
29+
ptfhost = self.ptfhosts[ptf_index]
2830
ifaces = [ifaces] if not isinstance(ifaces, list) else ifaces
29-
last_iface_id = sorted(self.ptfhost.host.options['variable_manager'].extra_vars['ifaces_map'].keys())[-1]
31+
last_iface_id = sorted(ptfhost.host.options['variable_manager'].extra_vars['ifaces_map'].keys())[-1]
3032

3133
for iface_id, iface in enumerate(ifaces, last_iface_id+1):
32-
self.ptfhost.host.options['variable_manager'].extra_vars['ifaces_map'][iface_id] = iface
34+
ptfhost.host.options['variable_manager'].extra_vars['ifaces_map'][iface_id] = iface
3335
self.ptfadapter.ptf_port_set.append(iface_id)
3436

35-
self._restart_ptf_nn_agent()
37+
self._restart_ptf_nn_agent(ptf_index)
3638

3739
self.ptfadapter.reinit()
3840

39-
def cleanup_ptf_nn_agent(self, ifaces):
41+
def cleanup_ptf_nn_agent(self, ifaces, ptf_index=0):
4042
"""
4143
Remove interfaces from interfaces map of ptfadapter
4244
4345
Args:
4446
ifaces: List of interface names
47+
ptf_index: The index of ptfhost in ptfhosts to configure ptf_nn_agent
4548
"""
49+
ptfhost = self.ptfhosts[ptf_index]
4650
ifaces = [ifaces] if not isinstance(ifaces, list) else ifaces
47-
ifaces_map = self.ptfhost.host.options['variable_manager'].extra_vars['ifaces_map']
51+
ifaces_map = ptfhost.host.options['variable_manager'].extra_vars['ifaces_map']
4852
config_port_indices = {v: k for k, v in list(ifaces_map.items())}
4953

5054
for iface in ifaces:
51-
self.ptfhost.host.options['variable_manager'].extra_vars['ifaces_map'].pop(config_port_indices[iface])
55+
ptfhost.host.options['variable_manager'].extra_vars['ifaces_map'].pop(config_port_indices[iface])
5256
self.ptfadapter.ptf_port_set.remove(config_port_indices[iface])
5357

54-
self._restart_ptf_nn_agent()
58+
self._restart_ptf_nn_agent(ptf_index)
5559

5660
self.ptfadapter.reinit()
5761

58-
def _restart_ptf_nn_agent(self):
62+
def _restart_ptf_nn_agent(self, ptf_index=0):
5963
"""
6064
Restart ptf_nn_agent
6165
"""
62-
self.ptfhost.template(src=self.ptf_nn_agent_template, dest=PTF_NN_AGENT_CONF)
63-
self.ptfhost.command('supervisorctl reread')
64-
self.ptfhost.command('supervisorctl update')
65-
self.ptfhost.command('supervisorctl restart ptf_nn_agent')
66+
ptfhost = self.ptfhosts[ptf_index]
67+
ptfhost.template(src=self.ptf_nn_agent_template, dest=PTF_NN_AGENT_CONF)
68+
ptfhost.command('supervisorctl reread')
69+
ptfhost.command('supervisorctl update')
70+
ptfhost.command('supervisorctl restart ptf_nn_agent')

tests/common/testbed.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ def _read_testbed_topo_from_yaml(self):
107107
with open(self.testbed_filename) as f:
108108
tb_info = yaml.safe_load(f)
109109
for tb in tb_info:
110-
if tb["ptf_ip"]:
110+
if "ptf_ip" in tb and tb["ptf_ip"]:
111111
tb["ptf_ip"], tb["ptf_netmask"] = \
112112
self._cidr_to_ip_mask(tb["ptf_ip"])
113-
if tb["ptf_ipv6"]:
113+
if "ptf_ipv6" in tb and tb["ptf_ipv6"]:
114114
tb["ptf_ipv6"], tb["ptf_netmask_v6"] = \
115115
self._cidr_to_ip_mask(tb["ptf_ipv6"])
116116
tb["duts"] = tb.pop("dut")

0 commit comments

Comments
 (0)