1 # Copyright (c) 2020 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."""
15 from time import sleep
16 from robot.api import logger
18 from resources.libraries.python.Constants import Constants
19 from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
20 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
21 from resources.libraries.python.DUTSetup import DUTSetup
23 class HoststackUtil():
24 """Utilities for Host Stack tests."""
27 def get_vpp_echo_command(vpp_echo_attributes):
28 """Construct the vpp_echo command using the specified attributes.
30 :param vpp_echo_attributes: vpp_echo test program attributes.
31 :type vpp_echo_attributes: dict
32 :returns: Command line components of the vpp_echo command
34 'args' - command arguments.
37 # TODO: Use a python class instead of dictionary for the return type
38 proto = vpp_echo_attributes[u"uri_protocol"]
39 addr = vpp_echo_attributes[u"uri_ip4_addr"]
40 port = vpp_echo_attributes[u"uri_port"]
42 vpp_echo_cmd[u"name"] = u"vpp_echo"
43 vpp_echo_cmd[u"args"] = f"{vpp_echo_attributes[u'role']} " \
44 f"socket-name {vpp_echo_attributes[u'vpp_api_socket']} " \
45 f"{vpp_echo_attributes[u'json_output']} " \
46 f"uri {proto}://{addr}/{port} " \
47 f"nthreads {vpp_echo_attributes[u'nthreads']} " \
48 f"mq-size {vpp_echo_attributes[u'mq_size']} " \
49 f"nclients {vpp_echo_attributes[u'nclients']} " \
50 f"quic-streams {vpp_echo_attributes[u'quic_streams']} " \
51 f"time {vpp_echo_attributes[u'time']} " \
52 f"fifo-size {vpp_echo_attributes[u'fifo_size']} " \
53 f"TX={vpp_echo_attributes[u'tx_bytes']} " \
54 f"RX={vpp_echo_attributes[u'rx_bytes']}"
55 if vpp_echo_attributes[u"rx_results_diff"]:
56 vpp_echo_cmd[u"args"] += u" rx-results-diff"
57 if vpp_echo_attributes[u"tx_results_diff"]:
58 vpp_echo_cmd[u"args"] += u" tx-results-diff"
62 def get_iperf3_command(iperf3_attributes):
63 """Construct the iperf3 command using the specified attributes.
65 :param iperf3_attributes: iperf3 test program attributes.
66 :type iperf3_attributes: dict
67 :returns: Command line components of the iperf3 command
68 'env_vars' - environment variables
70 'args' - command arguments.
73 # TODO: Use a python class instead of dictionary for the return type
75 iperf3_cmd[u"env_vars"] = f"VCL_CONFIG={Constants.REMOTE_FW_DIR}/" \
76 f"{Constants.RESOURCES_TPL_VCL}/" \
77 f"{iperf3_attributes[u'vcl_config']}"
78 if iperf3_attributes[u"ld_preload"]:
79 iperf3_cmd[u"env_vars"] += \
80 f" LD_PRELOAD={Constants.VCL_LDPRELOAD_LIBRARY}"
81 if iperf3_attributes[u'transparent_tls']:
82 iperf3_cmd[u"env_vars"] += u" LDP_ENV_TLS_TRANS=1"
84 json_results = u" --json" if iperf3_attributes[u'json'] else u""
85 ip_address = f" {iperf3_attributes[u'ip_address']}" if u"ip_address" \
86 in iperf3_attributes else u""
87 iperf3_cmd[u"name"] = u"iperf3"
88 iperf3_cmd[u"args"] = f"--{iperf3_attributes[u'role']}{ip_address} " \
89 f"--interval 0{json_results} " \
90 f"--version{iperf3_attributes[u'ip_version']}"
92 if iperf3_attributes[u"role"] == u"server":
93 iperf3_cmd[u"args"] += u" --one-off"
95 iperf3_cmd[u"args"] += u" --get-server-output"
96 if u"parallel" in iperf3_attributes:
97 iperf3_cmd[u"args"] += \
98 f" --parallel {iperf3_attributes[u'parallel']}"
99 if u"time" in iperf3_attributes:
100 iperf3_cmd[u"args"] += \
101 f" --time {iperf3_attributes[u'time']}"
105 def set_hoststack_quic_fifo_size(node, fifo_size):
106 """Set the QUIC protocol fifo size.
108 :param node: Node to set the QUIC fifo size on.
109 :param fifo_size: fifo size, passed to the quic set fifo-size command.
113 cmd = f"quic set fifo-size {fifo_size}"
114 PapiSocketExecutor.run_cli_cmd(node, cmd)
117 def set_hoststack_quic_crypto_engine(node, quic_crypto_engine,
118 fail_on_error=False):
119 """Set the Hoststack QUIC crypto engine on node
121 :param node: Node to enable/disable HostStack.
122 :param quic_crypto_engine: type of crypto engine
124 :type quic_crypto_engine: str
126 vpp_crypto_engines = {u"openssl", u"native", u"ipsecmb"}
127 if quic_crypto_engine == u"nocrypto":
128 logger.trace(u"No QUIC crypto engine.")
131 if quic_crypto_engine in vpp_crypto_engines:
132 cmds = [u"quic set crypto api vpp",
133 f"set crypto handler aes-128-gcm {quic_crypto_engine}",
134 f"set crypto handler aes-256-gcm {quic_crypto_engine}"]
135 elif quic_crypto_engine == u"picotls":
136 cmds = [u"quic set crypto api picotls"]
138 raise ValueError(f"Unknown QUIC crypto_engine {quic_crypto_engine}")
142 PapiSocketExecutor.run_cli_cmd(node, cmd)
143 except AssertionError:
148 def get_hoststack_test_program_logs(node, program):
149 """Get HostStack test program stdout log.
151 :param node: DUT node.
152 :param program: test program.
156 program_name = program[u"name"]
157 cmd = f"sh -c \'cat /tmp/{program_name}_stdout.log\'"
158 stdout_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
159 message=f"Get {program_name} stdout log failed!")
161 cmd = f"sh -c \'cat /tmp/{program_name}_stderr.log\'"
162 stderr_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
163 message=f"Get {program_name} stderr log failed!")
164 return stdout_log, stderr_log
167 def start_hoststack_test_program(node, namespace, core_list, program):
168 """Start the specified HostStack test program.
170 :param node: DUT node.
171 :param namespace: Net Namespace to run program in.
172 :param core_list: List of cpu's to pass to taskset to pin the test
173 program to a different set of cores on the same numa node as VPP.
174 :param program: Test program.
181 :raises RuntimeError: If node subtype is not a DUT or startup failed.
183 if node[u"type"] != u"DUT":
184 raise RuntimeError(u"Node type is not a DUT!")
186 program_name = program[u"name"]
187 DUTSetup.kill_program(node, program_name, namespace)
189 if namespace == u"default":
192 shell_cmd = f"ip netns exec {namespace} sh -c"
194 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
195 args = program[u"args"]
196 cmd = f"nohup {shell_cmd} \'{env_vars}taskset --cpu-list {core_list} " \
197 f"{program_name} {args} >/tmp/{program_name}_stdout.log " \
198 f"2>/tmp/{program_name}_stderr.log &\'"
200 exec_cmd_no_error(node, cmd, sudo=True)
201 return DUTSetup.get_pid(node, program_name)[0]
203 stdout_log, stderr_log = \
204 HoststackUtil.get_hoststack_test_program_logs(node,
206 raise RuntimeError(f"Start {program_name} failed!\nSTDERR:\n" \
207 f"{stderr_log}\nSTDOUT:\n{stdout_log}")
211 def stop_hoststack_test_program(node, program, pid):
212 """Stop the specified Hoststack test program.
214 :param node: DUT node.
215 :param program: Test program.
216 :param pid: Process ID of test program.
221 program_name = program[u"name"]
222 if program_name == u"nginx":
223 cmd = u"nginx -s quit"
224 errmsg = u"Quit nginx failed!"
226 cmd = f'if [ -n "$(ps {pid} | grep {program_name})" ] ; ' \
227 f'then kill -s SIGTERM {pid}; fi'
228 errmsg = f"Kill {program_name} ({pid}) failed!"
230 exec_cmd_no_error(node, cmd, message=errmsg, sudo=True)
233 def hoststack_test_program_finished(node, program_pid):
234 """Wait for the specified HostStack test program process to complete.
236 :param node: DUT node.
237 :param program_pid: test program pid.
239 :type program_pid: str
240 :raises RuntimeError: If node subtype is not a DUT.
242 if node[u"type"] != u"DUT":
243 raise RuntimeError(u"Node type is not a DUT!")
245 cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
246 exec_cmd(node, cmd, sudo=True)
247 # Wait a bit for stdout/stderr to be flushed to log files
248 # TODO: see if sub-second sleep works e.g. sleep(0.1)
252 def analyze_hoststack_test_program_output(node, role, nsim_attr,
254 """Gather HostStack test program output and check for errors.
256 :param node: DUT node.
257 :param role: Role (client|server) of test program.
258 :param nsim_attr: Network Simulation Attributes.
259 :param program: Test program.
260 :param program_args: List of test program args.
263 :type nsim_attr: dict
265 :returns: tuple of no results bool and test program results.
267 :raises RuntimeError: If node subtype is not a DUT.
269 if node[u"type"] != u"DUT":
270 raise RuntimeError(u"Node type is not a DUT!")
272 program_name = program[u"name"]
273 program_stdout, program_stderr = \
274 HoststackUtil.get_hoststack_test_program_logs(node, program)
275 if len(program_stdout) == 0 and len(program_stderr) == 0:
276 logger.trace(f"Retrying {program_name} log retrieval")
277 program_stdout, program_stderr = \
278 HoststackUtil.get_hoststack_test_program_logs(node, program)
281 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
282 program_cmd = f"{env_vars}{program_name} {program[u'args']}"
283 test_results = f"Test Results of '{program_cmd}':\n"
285 if nsim_attr[u"output_feature_enable"] or \
286 nsim_attr[u"cross_connect_feature_enable"]:
287 if nsim_attr[u"output_feature_enable"]:
288 feature_name = u"output"
290 feature_name = u"cross-connect"
292 f"NSIM({feature_name}): delay " \
293 f"{nsim_attr[u'delay_in_usec']} usecs, " \
294 f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \
295 f"bandwidth {nsim_attr[u'bandwidth_in_bits_per_second']} " \
296 f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \
299 if u"error" in program_stderr.lower():
300 test_results += f"ERROR DETECTED:\n{program_stderr}"
301 raise RuntimeError(test_results)
303 bad_test_results = False
304 if program[u"name"] == u"vpp_echo":
305 if u"JSON stats" in program_stdout:
306 test_results += program_stdout
307 # TODO: Decode vpp_echo output when JSON format is correct.
308 # json_start = program_stdout.find(u"{")
309 # vpp_echo_results = json.loads(program_stdout[json_start:])
310 if u'"has_failed": "0"' not in program_stdout:
311 bad_test_results = True
313 test_results += u"Invalid test data output!\n" + \
315 bad_test_results = True
317 test_results += program_stdout
319 raise RuntimeError(test_results)
322 test_results += f"\nNo {program} test data retrieved!\n"
323 cmd = u"ls -l /tmp/*.log"
324 ls_stdout, _ = exec_cmd_no_error(node, cmd, sudo=True)
325 test_results += f"{ls_stdout}\n"
327 # TODO: Incorporate show error stats into results analysis
330 f"\n{role} VPP 'show errors' on host {host}:\n" \
331 f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n"
333 return no_results, test_results
336 def no_hoststack_test_program_results(server_no_results, client_no_results):
337 """Return True if no HostStack test program output was gathered.
339 :param server_no_results: server no results value.
340 :param client_no_results: client no results value.
341 :type server_no_results: bool
342 :type client_no_results: bool
345 return server_no_results and client_no_results