1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Host Stack util library."""
16 from time import sleep
17 from robot.api import logger
19 from resources.libraries.python.Constants import Constants
20 from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
21 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
22 from resources.libraries.python.DUTSetup import DUTSetup
24 class HoststackUtil():
25 """Utilities for Host Stack tests."""
28 def get_vpp_echo_command(vpp_echo_attributes):
29 """Construct the vpp_echo command using the specified attributes.
31 :param vpp_echo_attributes: vpp_echo test program attributes.
32 :type vpp_echo_attributes: dict
33 :returns: Command line components of the vpp_echo command
35 'args' - command arguments.
38 # TODO: Use a python class instead of dictionary for the return type
39 proto = vpp_echo_attributes[u"uri_protocol"]
40 addr = vpp_echo_attributes[u"uri_ip4_addr"]
41 port = vpp_echo_attributes[u"uri_port"]
43 vpp_echo_cmd[u"name"] = u"vpp_echo"
44 vpp_echo_cmd[u"args"] = f"{vpp_echo_attributes[u'role']} " \
45 f"socket-name {vpp_echo_attributes[u'vpp_api_socket']} " \
46 f"{vpp_echo_attributes[u'json_output']} " \
47 f"uri {proto}://{addr}/{port} " \
48 f"nthreads {vpp_echo_attributes[u'nthreads']} " \
49 f"mq-size {vpp_echo_attributes[u'mq_size']} " \
50 f"nclients {vpp_echo_attributes[u'nclients']} " \
51 f"quic-streams {vpp_echo_attributes[u'quic_streams']} " \
52 f"time {vpp_echo_attributes[u'time']} " \
53 f"fifo-size {vpp_echo_attributes[u'fifo_size']} " \
54 f"TX={vpp_echo_attributes[u'tx_bytes']} " \
55 f"RX={vpp_echo_attributes[u'rx_bytes']}"
56 if vpp_echo_attributes[u"rx_results_diff"]:
57 vpp_echo_cmd[u"args"] += u" rx-results-diff"
58 if vpp_echo_attributes[u"tx_results_diff"]:
59 vpp_echo_cmd[u"args"] += u" tx-results-diff"
63 def get_iperf3_command(iperf3_attributes):
64 """Construct the iperf3 command using the specified attributes.
66 :param iperf3_attributes: iperf3 test program attributes.
67 :type iperf3_attributes: dict
68 :returns: Command line components of the iperf3 command
69 'env_vars' - environment variables
71 'args' - command arguments.
74 # TODO: Use a python class instead of dictionary for the return type
76 iperf3_cmd[u"env_vars"] = f"VCL_CONFIG={Constants.REMOTE_FW_DIR}/" \
77 f"{Constants.RESOURCES_TPL_VCL}/" \
78 f"{iperf3_attributes[u'vcl_config']}"
79 if iperf3_attributes[u"ld_preload"]:
80 iperf3_cmd[u"env_vars"] += \
81 f" LD_PRELOAD={Constants.VCL_LDPRELOAD_LIBRARY}"
82 if iperf3_attributes[u'transparent_tls']:
83 iperf3_cmd[u"env_vars"] += u" LDP_ENV_TLS_TRANS=1"
85 json_results = u" --json" if iperf3_attributes[u'json'] else u""
86 ip_address = f" {iperf3_attributes[u'ip_address']}" if u"ip_address" \
87 in iperf3_attributes else u""
88 iperf3_cmd[u"name"] = u"iperf3"
89 iperf3_cmd[u"args"] = f"--{iperf3_attributes[u'role']}{ip_address} " \
90 f"--interval 0{json_results} " \
91 f"--version{iperf3_attributes[u'ip_version']}"
93 if iperf3_attributes[u"role"] == u"server":
94 iperf3_cmd[u"args"] += u" --one-off"
96 iperf3_cmd[u"args"] += u" --get-server-output"
97 if u"parallel" in iperf3_attributes:
98 iperf3_cmd[u"args"] += \
99 f" --parallel {iperf3_attributes[u'parallel']}"
100 if u"time" in iperf3_attributes:
101 iperf3_cmd[u"args"] += \
102 f" --time {iperf3_attributes[u'time']}"
103 if iperf3_attributes[u"udp"]:
104 iperf3_cmd[u"args"] += u" --udp"
105 iperf3_cmd[u"args"] += f" --bandwidth {iperf3_attributes[u'bandwidth']}"
106 if iperf3_attributes[u"length"] > 0:
107 iperf3_cmd[u"args"] += f" --length {iperf3_attributes[u'length']}"
111 def set_hoststack_quic_fifo_size(node, fifo_size):
112 """Set the QUIC protocol fifo size.
114 :param node: Node to set the QUIC fifo size on.
115 :param fifo_size: fifo size, passed to the quic set fifo-size command.
119 cmd = f"quic set fifo-size {fifo_size}"
120 PapiSocketExecutor.run_cli_cmd(node, cmd)
123 def set_hoststack_quic_crypto_engine(node, quic_crypto_engine,
124 fail_on_error=False):
125 """Set the Hoststack QUIC crypto engine on node
127 :param node: Node to enable/disable HostStack.
128 :param quic_crypto_engine: type of crypto engine
130 :type quic_crypto_engine: str
132 vpp_crypto_engines = {u"openssl", u"native", u"ipsecmb"}
133 if quic_crypto_engine == u"nocrypto":
134 logger.trace(u"No QUIC crypto engine.")
137 if quic_crypto_engine in vpp_crypto_engines:
138 cmds = [u"quic set crypto api vpp",
139 f"set crypto handler aes-128-gcm {quic_crypto_engine}",
140 f"set crypto handler aes-256-gcm {quic_crypto_engine}"]
141 elif quic_crypto_engine == u"picotls":
142 cmds = [u"quic set crypto api picotls"]
144 raise ValueError(f"Unknown QUIC crypto_engine {quic_crypto_engine}")
148 PapiSocketExecutor.run_cli_cmd(node, cmd)
149 except AssertionError:
154 def get_hoststack_test_program_logs(node, program):
155 """Get HostStack test program stdout log.
157 :param node: DUT node.
158 :param program: test program.
162 program_name = program[u"name"]
163 cmd = f"sh -c \'cat /tmp/{program_name}_stdout.log\'"
164 stdout_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
165 message=f"Get {program_name} stdout log failed!")
167 cmd = f"sh -c \'cat /tmp/{program_name}_stderr.log\'"
168 stderr_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
169 message=f"Get {program_name} stderr log failed!")
170 return stdout_log, stderr_log
173 def get_nginx_command(nginx_attributes, nginx_version, nginx_ins_dir):
174 """Construct the NGINX command using the specified attributes.
176 :param nginx_attributes: NGINX test program attributes.
177 :param nginx_version: NGINX version.
178 :param nginx_ins_dir: NGINX install dir.
179 :type nginx_attributes: dict
180 :type nginx_version: str
181 :type nginx_ins_dir: str
182 :returns: Command line components of the NGINX command
183 'env_vars' - environment variables
184 'name' - program name
185 'args' - command arguments.
186 'path' - program path.
190 nginx_cmd[u"env_vars"] = f"VCL_CONFIG={Constants.REMOTE_FW_DIR}/" \
191 f"{Constants.RESOURCES_TPL_VCL}/" \
192 f"{nginx_attributes[u'vcl_config']}"
193 if nginx_attributes[u"ld_preload"]:
194 nginx_cmd[u"env_vars"] += \
195 f" LD_PRELOAD={Constants.VCL_LDPRELOAD_LIBRARY}"
196 if nginx_attributes[u'transparent_tls']:
197 nginx_cmd[u"env_vars"] += u" LDP_ENV_TLS_TRANS=1"
199 nginx_cmd[u"name"] = u"nginx"
200 nginx_cmd[u"path"] = f"{nginx_ins_dir}nginx-{nginx_version}/sbin/"
201 nginx_cmd[u"args"] = f"-c {nginx_ins_dir}/" \
202 f"nginx-{nginx_version}/conf/nginx.conf"
206 def start_hoststack_test_program(node, namespace, core_list, program):
207 """Start the specified HostStack test program.
209 :param node: DUT node.
210 :param namespace: Net Namespace to run program in.
211 :param core_list: List of cpu's to pass to taskset to pin the test
212 program to a different set of cores on the same numa node as VPP.
213 :param program: Test program.
220 :raises RuntimeError: If node subtype is not a DUT or startup failed.
222 if node[u"type"] != u"DUT":
223 raise RuntimeError(u"Node type is not a DUT!")
225 program_name = program[u"name"]
226 DUTSetup.kill_program(node, program_name, namespace)
228 if namespace == u"default":
231 shell_cmd = f"ip netns exec {namespace} sh -c"
233 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
234 args = program[u"args"]
235 program_path = program.get(u"path", u"")
236 # NGINX used `worker_cpu_affinity` in configuration file
237 taskset_cmd = u"" if program_name == u"nginx" else \
238 f"taskset --cpu-list {core_list}"
239 cmd = f"nohup {shell_cmd} \'{env_vars}{taskset_cmd} " \
240 f"{program_path}{program_name} {args} >/tmp/{program_name}_" \
241 f"stdout.log 2>/tmp/{program_name}_stderr.log &\'"
243 exec_cmd_no_error(node, cmd, sudo=True)
244 return DUTSetup.get_pid(node, program_name)[0]
246 stdout_log, stderr_log = \
247 HoststackUtil.get_hoststack_test_program_logs(node,
249 raise RuntimeError(f"Start {program_name} failed!\nSTDERR:\n" \
250 f"{stderr_log}\nSTDOUT:\n{stdout_log}")
254 def stop_hoststack_test_program(node, program, pid):
255 """Stop the specified Hoststack test program.
257 :param node: DUT node.
258 :param program: Test program.
259 :param pid: Process ID of test program.
264 program_name = program[u"name"]
265 if program_name == u"nginx":
266 cmd = u"nginx -s quit"
267 errmsg = u"Quit nginx failed!"
269 cmd = f'if [ -n "$(ps {pid} | grep {program_name})" ] ; ' \
270 f'then kill -s SIGTERM {pid}; fi'
271 errmsg = f"Kill {program_name} ({pid}) failed!"
273 exec_cmd_no_error(node, cmd, message=errmsg, sudo=True)
276 def hoststack_test_program_finished(node, program_pid):
277 """Wait for the specified HostStack test program process to complete.
279 :param node: DUT node.
280 :param program_pid: test program pid.
282 :type program_pid: str
283 :raises RuntimeError: If node subtype is not a DUT.
285 if node[u"type"] != u"DUT":
286 raise RuntimeError(u"Node type is not a DUT!")
288 cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
289 exec_cmd(node, cmd, sudo=True)
290 # Wait a bit for stdout/stderr to be flushed to log files
291 # TODO: see if sub-second sleep works e.g. sleep(0.1)
295 def analyze_hoststack_test_program_output(
296 node, role, nsim_attr, program):
297 """Gather HostStack test program output and check for errors.
299 The [defer_fail] return bool is used instead of failing immediately
300 to allow the analysis of both the client and server instances of
301 the test program for debugging a test failure. When [defer_fail]
302 is true, then the string returned is debug output instead of
303 JSON formatted test program results.
305 :param node: DUT node.
306 :param role: Role (client|server) of test program.
307 :param nsim_attr: Network Simulation Attributes.
308 :param program: Test program.
309 :param program_args: List of test program args.
312 :type nsim_attr: dict
314 :returns: tuple of [defer_fail] bool and either JSON formatted hoststack
315 test program output or failure debug output.
317 :raises RuntimeError: If node subtype is not a DUT.
319 if node[u"type"] != u"DUT":
320 raise RuntimeError(u"Node type is not a DUT!")
322 program_name = program[u"name"]
323 program_stdout, program_stderr = \
324 HoststackUtil.get_hoststack_test_program_logs(node, program)
325 if len(program_stdout) == 0 and len(program_stderr) == 0:
326 logger.trace(f"Retrying {program_name} log retrieval")
327 program_stdout, program_stderr = \
328 HoststackUtil.get_hoststack_test_program_logs(node, program)
330 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
331 program_cmd = f"{env_vars}{program_name} {program[u'args']}"
332 test_results = f"Test Results of '{program_cmd}':\n"
334 if nsim_attr[u"output_nsim_enable"] or \
335 nsim_attr[u"xc_nsim_enable"]:
336 if nsim_attr[u"output_nsim_enable"]:
337 feature_name = u"output"
339 feature_name = u"cross-connect"
341 f"NSIM({feature_name}): delay " \
342 f"{nsim_attr[u'delay_in_usec']} usecs, " \
343 f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \
344 f"bandwidth {nsim_attr[u'bw_in_bits_per_second']} " \
345 f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \
348 # TODO: Incorporate show error stats into results analysis
350 f"\n{role} VPP 'show errors' on host {node[u'host']}:\n" \
351 f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n"
353 if u"error" in program_stderr.lower():
354 test_results += f"ERROR DETECTED:\n{program_stderr}"
355 return (True, test_results)
356 if not program_stdout:
357 test_results += f"\nNo {program} test data retrieved!\n"
358 ls_stdout, _ = exec_cmd_no_error(node, u"ls -l /tmp/*.log",
360 test_results += f"{ls_stdout}\n"
361 return (True, test_results)
362 if program[u"name"] == u"vpp_echo":
363 if u"JSON stats" in program_stdout and \
364 u'"has_failed": "0"' in program_stdout:
365 json_start = program_stdout.find(u"{")
366 #TODO: Fix parsing once vpp_echo produces valid
367 # JSON output. Truncate for now.
368 json_end = program_stdout.find(u',\n "closing"')
369 json_results = f"{program_stdout[json_start:json_end]}\n}}"
370 program_json = json.loads(json_results)
372 test_results += u"Invalid test data output!\n" + program_stdout
373 return (True, test_results)
374 elif program[u"name"] == u"iperf3":
375 test_results += program_stdout
376 iperf3_json = json.loads(program_stdout)
377 program_json = iperf3_json[u"intervals"][0][u"sum"]
379 test_results += u"Unknown HostStack Test Program!\n" + \
381 return (True, program_stdout)
382 return (False, json.dumps(program_json))
385 def hoststack_test_program_defer_fail(server_defer_fail, client_defer_fail):
386 """Return True if either HostStack test program fail was deferred.
388 :param server_defer_fail: server no results value.
389 :param client_defer_fail: client no results value.
390 :type server_defer_fail: bool
391 :type client_defer_fail: bool
394 return server_defer_fail and client_defer_fail
397 def log_vpp_hoststack_data(node):
398 """Retrieve and log VPP HostStack data.
400 :param node: DUT node.
402 :raises RuntimeError: If node subtype is not a DUT or startup failed.
405 if node[u"type"] != u"DUT":
406 raise RuntimeError(u"Node type is not a DUT!")
408 PapiSocketExecutor.run_cli_cmd(node, u"show error")
409 PapiSocketExecutor.run_cli_cmd(node, u"show interface")