Skip to content

Commit 0db62d7

Browse files
authored
multi-asic support for copp testcase (#3060)
This is PR continuation of #2627 where infra changes were done to support copp on multi-asic platforms. In this PR test is enhanced to work on multi-asic platforms. Following are major changes: Multi-asic as of now will support installing of nanomsg and ptf_nn_agent in syncd directly mode. This way test case will randomly select any front-asic syncd for verification. To support this needed to add iptable rules for HTTP and PTF Traffic forwarding from host to asic namespace. swap_syncd mode is not supported as of now. All the docker name are translated to particular namespace/asic that is under test. (Added utility api for same). Remove disable_lldp_for_testing fixture as noted below since test case is not dependent on that.
1 parent 38ad99a commit 0db62d7

3 files changed

Lines changed: 137 additions & 67 deletions

File tree

tests/common/devices/multi_asic.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ def asic_instance(self, asic_index):
129129
return self.asics[0]
130130
return self.asics[asic_index]
131131

132+
def asic_instance_from_namespace(self, namespace=DEFAULT_NAMESPACE):
133+
if not namespace:
134+
return self.asics[0]
135+
136+
for asic in self.asics:
137+
if asic.namespace == namespace:
138+
return asic
139+
return None
140+
132141
def get_asic_ids(self):
133142
if self.sonichost.facts['num_asic'] == 1:
134143
return [DEFAULT_ASIC_ID]

tests/copp/copp_utils.py

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Todo:
55
Refactor ptfadapter so it can be leveraged in these test cases.
66
"""
7+
import re
78

89
DEFAULT_NN_TARGET_PORT = 3
910

@@ -12,20 +13,21 @@
1213
_UPDATE_COPP_SCRIPT = "copp/scripts/update_copp_config.py"
1314

1415
_BASE_COPP_CONFIG = "/tmp/base_copp_config.json"
15-
_APP_DB_COPP_CONFIG = "swss:/etc/swss/config.d/00-copp.config.json"
16+
_APP_DB_COPP_CONFIG = ":/etc/swss/config.d/00-copp.config.json"
1617
_CONFIG_DB_COPP_CONFIG = "/etc/sonic/copp_cfg.json"
1718
_TEMP_COPP_CONFIG = "/tmp/copp_config.json"
1819
_TEMP_COPP_TEMPLATE = "/tmp/copp.json.j2"
1920
_COPP_TEMPLATE_PATH = "/usr/share/sonic/templates/copp.json.j2"
20-
_SWSS_COPP_TEMPLATE = "swss:" + _COPP_TEMPLATE_PATH
21+
_SWSS_COPP_TEMPLATE = ":" + _COPP_TEMPLATE_PATH
2122

2223
_PTF_NN_TEMPLATE = "templates/ptf_nn_agent.conf.ptf.j2"
2324
_PTF_NN_DEST = "/etc/supervisor/conf.d/ptf_nn_agent.conf"
2425

2526
_SYNCD_NN_TEMPLATE = "templates/ptf_nn_agent.conf.dut.j2"
2627
_SYNCD_NN_DEST = "/tmp/ptf_nn_agent.conf"
28+
_SYNCD_NN_FILE = "ptf_nn_agent.conf"
2729

28-
def limit_policer(dut, pps_limit):
30+
def limit_policer(dut, pps_limit, nn_target_namespace):
2931
"""
3032
Updates the COPP configuration in the SWSS container to respect a given rate limit.
3133
@@ -37,8 +39,12 @@ def limit_policer(dut, pps_limit):
3739
pps_limit (int): The rate limit for COPP to enforce on ALL trap groups.
3840
"""
3941

42+
asichost = dut.asic_instance_from_namespace(nn_target_namespace)
43+
44+
swss_docker_name = asichost.get_docker_name("swss")
45+
4046
if "201811" in dut.os_version or "201911" in dut.os_version:
41-
dut.command("docker cp {} {}".format(_APP_DB_COPP_CONFIG, _BASE_COPP_CONFIG))
47+
dut.command("docker cp {} {}".format(swss_docker_name + _APP_DB_COPP_CONFIG, _BASE_COPP_CONFIG))
4248
config_format = "app_db"
4349
else:
4450
dut.command("cp {} {}".format(_CONFIG_DB_COPP_CONFIG, _BASE_COPP_CONFIG))
@@ -53,17 +59,17 @@ def limit_policer(dut, pps_limit):
5359
)
5460

5561
if config_format == "app_db":
56-
dut.command("docker cp {} {}".format(_TEMP_COPP_CONFIG, _APP_DB_COPP_CONFIG))
62+
dut.command("docker cp {} {}".format(_TEMP_COPP_CONFIG, swss_docker_name + _APP_DB_COPP_CONFIG))
5763

5864
# As copp config is regenerated each time swss starts need to replace the template with
5965
# config updated above. But before doing that need store the original template in a
6066
# temporary file for restore after test.
61-
dut.command("docker cp {} {}".format(_SWSS_COPP_TEMPLATE, _TEMP_COPP_TEMPLATE))
62-
dut.command("docker cp {} {}".format(_TEMP_COPP_CONFIG, _SWSS_COPP_TEMPLATE))
67+
dut.command("docker cp {} {}".format(swss_docker_name + _SWSS_COPP_TEMPLATE, _TEMP_COPP_TEMPLATE))
68+
dut.command("docker cp {} {}".format(_TEMP_COPP_CONFIG, swss_docker_name + _SWSS_COPP_TEMPLATE))
6369
else:
6470
dut.command("cp {} {}".format(_TEMP_COPP_CONFIG, _CONFIG_DB_COPP_CONFIG))
6571

66-
def restore_policer(dut):
72+
def restore_policer(dut, nn_target_namespace):
6773
"""
6874
Reloads the default COPP configuration in the SWSS container.
6975
@@ -72,10 +78,14 @@ def restore_policer(dut):
7278
7379
The SWSS container must be restarted for the config change to take effect.
7480
"""
81+
asichost = dut.asic_instance_from_namespace(nn_target_namespace)
82+
83+
swss_docker_name = asichost.get_docker_name("swss")
84+
7585
# Restore the copp template in swss
7686
if "201811" in dut.os_version or "201911" in dut.os_version:
77-
dut.command("docker cp {} {}".format(_BASE_COPP_CONFIG, _APP_DB_COPP_CONFIG))
78-
dut.command("docker cp {} {}".format(_TEMP_COPP_TEMPLATE, _SWSS_COPP_TEMPLATE))
87+
dut.command("docker cp {} {}".format(_BASE_COPP_CONFIG, swss_docker_name + _APP_DB_COPP_CONFIG))
88+
dut.command("docker cp {} {}".format(_TEMP_COPP_TEMPLATE, swss_docker_name + _SWSS_COPP_TEMPLATE))
7989
else:
8090
dut.command("cp {} {}".format(_BASE_COPP_CONFIG, _CONFIG_DB_COPP_CONFIG))
8191

@@ -114,7 +124,7 @@ def restore_ptf(ptf):
114124

115125
ptf.supervisorctl(name="ptf_nn_agent", state="restarted")
116126

117-
def configure_syncd(dut, nn_target_port, nn_target_interface, creds):
127+
def configure_syncd(dut, nn_target_port, nn_target_interface, nn_target_namespace, creds):
118128
"""
119129
Configures syncd to run the NN agent on the specified port.
120130
@@ -125,22 +135,38 @@ def configure_syncd(dut, nn_target_port, nn_target_interface, creds):
125135
Args:
126136
dut (SonicHost): The target device.
127137
nn_target_port (int): The port to run NN agent on.
128-
nn_target_interface (str): The Interface remote NN agents listens.
138+
nn_target_interface (str): The Interface remote NN agents listen to
139+
nn_target_namespace (str): The namespace remote NN agents listens
129140
creds (dict): Credential information according to the dut inventory
130141
"""
131142

132143
facts = {"nn_target_port": nn_target_port, "nn_target_interface": nn_target_interface}
133144
dut.host.options["variable_manager"].extra_vars.update(facts)
134145

135-
_install_nano(dut, creds)
146+
asichost = dut.asic_instance_from_namespace(nn_target_namespace)
147+
148+
syncd_docker_name = asichost.get_docker_name("syncd")
149+
150+
_install_nano(dut, creds, syncd_docker_name)
136151

137152
dut.template(src=_SYNCD_NN_TEMPLATE, dest=_SYNCD_NN_DEST)
138-
dut.command("docker cp {} syncd:/etc/supervisor/conf.d/".format(_SYNCD_NN_DEST))
139153

140-
dut.command("docker exec syncd supervisorctl reread")
141-
dut.command("docker exec syncd supervisorctl update")
154+
dut.command("docker cp {} {}:/etc/supervisor/conf.d/".format(_SYNCD_NN_DEST, syncd_docker_name))
155+
156+
dut.command("docker exec {} supervisorctl reread".format(syncd_docker_name))
157+
dut.command("docker exec {} supervisorctl update".format(syncd_docker_name))
158+
159+
def restore_syncd(dut, nn_target_namespace):
160+
asichost = dut.asic_instance_from_namespace(nn_target_namespace)
142161

143-
def _install_nano(dut, creds):
162+
syncd_docker_name = asichost.get_docker_name("syncd")
163+
164+
dut.command("docker exec {} rm -rf /etc/supervisor/conf.d/{}".format(syncd_docker_name, _SYNCD_NN_FILE))
165+
dut.command("docker exec {} supervisorctl reread".format(syncd_docker_name))
166+
dut.command("docker exec {} supervisorctl update".format(syncd_docker_name))
167+
168+
169+
def _install_nano(dut, creds, syncd_docker_name):
144170
"""
145171
Install nanomsg package to syncd container.
146172
@@ -149,13 +175,13 @@ def _install_nano(dut, creds):
149175
creds (dict): Credential information according to the dut inventory
150176
"""
151177

152-
output = dut.command("docker exec syncd bash -c '[ -d /usr/local/include/nanomsg ] || echo copp'")
178+
output = dut.command("docker exec {} bash -c '[ -d /usr/local/include/nanomsg ] || echo copp'".format(syncd_docker_name))
153179

154180
if output["stdout"] == "copp":
155181
http_proxy = creds.get('proxy_env', {}).get('http_proxy', '')
156182
https_proxy = creds.get('proxy_env', {}).get('https_proxy', '')
157183

158-
cmd = '''docker exec -e http_proxy={} -e https_proxy={} syncd bash -c " \
184+
cmd = '''docker exec -e http_proxy={} -e https_proxy={} {} bash -c " \
159185
rm -rf /var/lib/apt/lists/* \
160186
&& apt-get update \
161187
&& apt-get install -y python-pip build-essential libssl-dev python-dev python-setuptools wget cmake \
@@ -165,7 +191,7 @@ def _install_nano(dut, creds):
165191
&& rm -f 1.0.0.tar.gz && pip2 install cffi==1.7.0 && pip2 install --upgrade cffi==1.7.0 && pip2 install nnpy \
166192
&& mkdir -p /opt && cd /opt && wget https://raw.githubusercontent.com/p4lang/ptf/master/ptf_nn/ptf_nn_agent.py \
167193
&& mkdir ptf && cd ptf && wget https://raw.githubusercontent.com/p4lang/ptf/master/src/ptf/afpacket.py && touch __init__.py \
168-
" '''.format(http_proxy, https_proxy)
194+
" '''.format(http_proxy, https_proxy, syncd_docker_name)
169195
dut.command(cmd)
170196

171197
def _map_port_number_to_interface(dut, nn_target_port):
@@ -175,3 +201,14 @@ def _map_port_number_to_interface(dut, nn_target_port):
175201

176202
interfaces = dut.command("portstat")["stdout_lines"][2:]
177203
return interfaces[nn_target_port].split()[0]
204+
205+
def _get_http_and_https_proxy_ip(creds):
206+
"""
207+
Get the http and https proxy ip.
208+
209+
Args:
210+
creds (dict): Credential information according to the dut inventory
211+
"""
212+
213+
return (re.findall(r'[0-9]+(?:\.[0-9]+){3}', creds.get('proxy_env', {}).get('http_proxy', ''))[0],
214+
re.findall(r'[0-9]+(?:\.[0-9]+){3}', creds.get('proxy_env', {}).get('https_proxy', ''))[0])

tests/copp/test_copp.py

Lines changed: 71 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"topo",
4444
"myip",
4545
"peerip",
46-
"nn_target_interface"])
46+
"nn_target_interface",
47+
"nn_target_namespace"])
4748
_SUPPORTED_PTF_TOPOS = ["ptf32", "ptf64"]
4849
_SUPPORTED_T1_TOPOS = ["t1", "t1-lag", "t1-64-lag"]
4950
_TOR_ONLY_PROTOCOL = ["DHCP"]
@@ -112,8 +113,7 @@ def copp_testbed(
112113
creds,
113114
ptfhost,
114115
tbinfo,
115-
request,
116-
disable_lldp_for_testing # usefixtures not supported on fixtures
116+
request
117117
):
118118
"""
119119
Pytest fixture to handle setup and cleanup for the COPP tests.
@@ -125,10 +125,12 @@ def copp_testbed(
125125
pytest.skip("Topology not supported by COPP tests")
126126

127127
try:
128-
_setup_testbed(duthost, creds, ptfhost, test_params)
128+
_setup_multi_asic_proxy(duthost, creds, test_params, tbinfo)
129+
_setup_testbed(duthost, creds, ptfhost, test_params, tbinfo)
129130
yield test_params
130131
finally:
131-
_teardown_testbed(duthost, creds, ptfhost, test_params)
132+
_teardown_multi_asic_proxy(duthost, creds, test_params, tbinfo)
133+
_teardown_testbed(duthost, creds, ptfhost, test_params, tbinfo)
132134

133135
@pytest.fixture(autouse=True)
134136
def ignore_expected_loganalyzer_exceptions(rand_one_dut_hostname, loganalyzer):
@@ -143,10 +145,6 @@ def ignore_expected_loganalyzer_exceptions(rand_one_dut_hostname, loganalyzer):
143145
loganalyzer: Loganalyzer utility fixture
144146
"""
145147
ignoreRegex = [
146-
".*ERR monit.*'lldpd_monitor' process is not running.*",
147-
".*ERR monit.* 'lldp\|lldpd_monitor' status failed.*-- 'lldpd:' is not running.*",
148-
".*ERR monit.*'lldp_syncd' process is not running.*",
149-
".*ERR monit.*'lldp\|lldp_syncd' status failed.*'python2 -m lldp_syncd' is not running.*",
150148
".*snmp#snmp-subagent.*",
151149
".*kernel reports TIME_ERROR: 0x4041: Clock Unsynchronized.*"
152150
]
@@ -207,80 +205,106 @@ def _gather_test_params(tbinfo, duthost, request):
207205
peerip = bgp_peer["peer_addr"]
208206
break
209207

210-
logging.info("nn_target_port {} nn_target_interface {}".format(nn_target_port, nn_target_interface))
208+
nn_target_namespace = mg_facts["minigraph_neighbors"][nn_target_interface]['namespace']
209+
210+
logging.info("nn_target_port {} nn_target_interface {} nn_target_namespace {}".format(nn_target_port, nn_target_interface, nn_target_namespace))
211211

212212
return _COPPTestParameters(nn_target_port=nn_target_port,
213213
swap_syncd=swap_syncd,
214214
topo=topo,
215215
myip=myip,
216216
peerip = peerip,
217-
nn_target_interface=nn_target_interface)
217+
nn_target_interface=nn_target_interface,
218+
nn_target_namespace=nn_target_namespace)
218219

219-
def _setup_testbed(dut, creds, ptf, test_params):
220+
def _setup_testbed(dut, creds, ptf, test_params, tbinfo):
220221
"""
221222
Sets up the testbed to run the COPP tests.
222223
"""
223-
224224
logging.info("Set up the PTF for COPP tests")
225225
copp_utils.configure_ptf(ptf, test_params.nn_target_port)
226226

227227
logging.info("Update the rate limit for the COPP policer")
228-
copp_utils.limit_policer(dut, _TEST_RATE_LIMIT)
228+
copp_utils.limit_policer(dut, _TEST_RATE_LIMIT, test_params.nn_target_namespace)
229229

230-
if test_params.swap_syncd:
230+
# Multi-asic will not support this mode as of now.
231+
if test_params.swap_syncd and not dut.is_multi_asic:
231232
logging.info("Swap out syncd to use RPC image...")
232233
docker.swap_syncd(dut, creds)
233234
else:
235+
# Set sysctl RCVBUF parameter for tests
236+
dut.command("sysctl -w net.core.rmem_max=609430500")
237+
238+
# Set sysctl SENDBUF parameter for tests
239+
dut.command("sysctl -w net.core.wmem_max=609430500")
240+
234241
# NOTE: Even if the rpc syncd image is already installed, we need to restart
235242
# SWSS for the COPP changes to take effect.
236243
logging.info("Reloading config and restarting swss...")
237244
config_reload(dut)
238245

239246
logging.info("Configure syncd RPC for testing")
240-
copp_utils.configure_syncd(dut, test_params.nn_target_port, test_params.nn_target_interface, creds)
247+
copp_utils.configure_syncd(dut, test_params.nn_target_port, test_params.nn_target_interface,
248+
test_params.nn_target_namespace, creds)
241249

242-
def _teardown_testbed(dut, creds, ptf, test_params):
250+
def _teardown_testbed(dut, creds, ptf, test_params, tbinfo):
243251
"""
244252
Tears down the testbed, returning it to its initial state.
245253
"""
246-
247254
logging.info("Restore PTF post COPP test")
248255
copp_utils.restore_ptf(ptf)
249256

250257
logging.info("Restore COPP policer to default settings")
251-
copp_utils.restore_policer(dut)
258+
copp_utils.restore_policer(dut, test_params.nn_target_namespace)
252259

253-
if test_params.swap_syncd:
260+
if test_params.swap_syncd and not dut.is_multi_asic:
254261
logging.info("Restore default syncd docker...")
255262
docker.restore_default_syncd(dut, creds)
256263
else:
264+
copp_utils.restore_syncd(dut, test_params.nn_target_namespace)
257265
logging.info("Reloading config and restarting swss...")
258266
config_reload(dut)
259267

260-
261-
@pytest.fixture(scope="class")
262-
def disable_lldp_for_testing(
263-
duthosts,
264-
rand_one_dut_hostname,
265-
disable_container_autorestart,
266-
enable_container_autorestart
267-
):
268-
"""Disables LLDP during testing so that it doesn't interfere with the policer."""
269-
duthost = duthosts[rand_one_dut_hostname]
270-
271-
logging.info("Disabling LLDP for the COPP tests")
272-
273-
feature_list = ['lldp']
274-
disable_container_autorestart(duthost, testcase="test_copp", feature_list=feature_list)
275-
276-
duthost.command("docker exec lldp supervisorctl stop lldp-syncd")
277-
duthost.command("docker exec lldp supervisorctl stop lldpd")
278-
279-
yield
280-
281-
logging.info("Restoring LLDP after the COPP tests")
282-
283-
duthost.command("docker exec lldp supervisorctl start lldpd")
284-
duthost.command("docker exec lldp supervisorctl start lldp-syncd")
285-
286-
enable_container_autorestart(duthost, testcase="test_copp", feature_list=feature_list)
268+
def _setup_multi_asic_proxy(dut, creds, test_params, tbinfo):
269+
"""
270+
Sets up the testbed to run the COPP tests on multi-asic platfroms via setting proxy.
271+
"""
272+
if not dut.is_multi_asic:
273+
return
274+
275+
logging.info("Adding iptables rules and enabling eth0 port forwarding")
276+
http_proxy, https_proxy = copp_utils._get_http_and_https_proxy_ip(creds)
277+
# Add IP Table rule for http and ptf nn_agent traffic.
278+
dut.command("sudo sysctl net.ipv4.conf.eth0.forwarding=1")
279+
mgmt_ip = dut.host.options["inventory_manager"].get_host(dut.hostname).vars["ansible_host"]
280+
# Add Rule to communicate to http/s proxy from namespace
281+
dut.command("sudo iptables -t nat -A POSTROUTING -p tcp --dport 8080 -j SNAT --to-source {}".format(mgmt_ip))
282+
dut.command("sudo ip -n {} rule add from all to {} pref 1 lookup default".format(test_params.nn_target_namespace, http_proxy))
283+
if http_proxy != https_proxy:
284+
dut.command("sudo ip -n {} rule add from all to {} pref 2 lookup default".format(test_params.nn_target_namespace, https_proxy))
285+
# Add Rule to communicate to ptf nn agent client from namespace
286+
ns_ip = dut.shell("sudo ip -n {} -4 -o addr show eth0".format(test_params.nn_target_namespace) + " | awk '{print $4}' | cut -d'/' -f1")["stdout"]
287+
dut.command("sudo iptables -t nat -A PREROUTING -p tcp --dport 10900 -j DNAT --to-destination {}".format(ns_ip))
288+
dut.command("sudo ip -n {} rule add from {} to {} pref 3 lookup default".format(test_params.nn_target_namespace, ns_ip, tbinfo["ptf_ip"]))
289+
290+
def _teardown_multi_asic_proxy(dut, creds, test_params, tbinfo):
291+
"""
292+
Tears down multi asic proxy settings, returning it to its initial state.
293+
"""
294+
if not dut.is_multi_asic:
295+
return
296+
297+
logging.info("Removing iptables rules and disabling eth0 port forwarding")
298+
http_proxy, https_proxy = copp_utils._get_http_and_https_proxy_ip(creds)
299+
dut.command("sudo sysctl net.ipv4.conf.eth0.forwarding=0")
300+
# Delete IP Table rule for http and ptf nn_agent traffic.
301+
mgmt_ip = dut.host.options["inventory_manager"].get_host(dut.hostname).vars["ansible_host"]
302+
# Delete Rule to communicate to http/s proxy from namespace
303+
dut.command("sudo iptables -t nat -D POSTROUTING -p tcp --dport 8080 -j SNAT --to-source {}".format(mgmt_ip))
304+
dut.command("sudo ip -n {} rule delete from all to {} pref 1 lookup default".format(test_params.nn_target_namespace, http_proxy))
305+
if http_proxy != https_proxy:
306+
dut.command("sudo ip -n {} rule delete from all to {} pref 2 lookup default".format(test_params.nn_target_namespace, https_proxy))
307+
# Delete Rule to communicate to ptf nn agent client from namespace
308+
ns_ip = dut.shell("sudo ip -n {} -4 -o addr show eth0".format(test_params.nn_target_namespace) + " | awk '{print $4}' | cut -d'/' -f1")["stdout"]
309+
dut.command("sudo iptables -t nat -D PREROUTING -p tcp --dport 10900 -j DNAT --to-destination {}".format(ns_ip))
310+
dut.command("sudo ip -n {} rule delete from {} to {} pref 3 lookup default".format(test_params.nn_target_namespace, ns_ip, tbinfo["ptf_ip"]))

0 commit comments

Comments
 (0)