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"nclients {vpp_echo_attributes[u'nclients']} " \
48 f"quic-streams {vpp_echo_attributes[u'quic_streams']} " \
49 f"time {vpp_echo_attributes[u'time']} " \
50 f"fifo-size {vpp_echo_attributes[u'fifo_size']} " \
51 f"TX={vpp_echo_attributes[u'tx_bytes']} " \
52 f"RX={vpp_echo_attributes[u'rx_bytes']}"
53 if vpp_echo_attributes[u"rx_results_diff"]:
54 vpp_echo_cmd[u"args"] += u" rx-results-diff"
55 if vpp_echo_attributes[u"tx_results_diff"]:
56 vpp_echo_cmd[u"args"] += u" tx-results-diff"
60 def get_iperf3_command(iperf3_attributes):
61 """Construct the iperf3 command using the specified attributes.
63 :param iperf3_attributes: iperf3 test program attributes.
64 :type iperf3_attributes: dict
65 :returns: Command line components of the iperf3 command
66 'env_vars' - environment variables
68 'args' - command arguments.
71 # TODO: Use a python class instead of dictionary for the return type
73 iperf3_cmd[u"env_vars"] = f"VCL_CONFIG={Constants.REMOTE_FW_DIR}/" \
74 f"{Constants.RESOURCES_TPL_VCL}/" \
75 f"{iperf3_attributes[u'vcl_config']}"
76 if iperf3_attributes[u"ld_preload"]:
77 iperf3_cmd[u"env_vars"] += \
78 f" LD_PRELOAD={Constants.VCL_LDPRELOAD_LIBRARY}"
79 if iperf3_attributes[u'transparent_tls']:
80 iperf3_cmd[u"env_vars"] += u" LDP_ENV_TLS_TRANS=1"
82 json_results = u" --json" if iperf3_attributes[u'json'] else u""
83 ip_address = f" {iperf3_attributes[u'ip_address']}" if u"ip_address" \
84 in iperf3_attributes else u""
85 iperf3_cmd[u"name"] = u"iperf3"
86 iperf3_cmd[u"args"] = f"--{iperf3_attributes[u'role']}{ip_address} " \
87 f"--interval 0{json_results} " \
88 f"--version{iperf3_attributes[u'ip_version']}"
90 if iperf3_attributes[u"role"] == u"server":
91 iperf3_cmd[u"args"] += u" --one-off"
93 iperf3_cmd[u"args"] += u" --get-server-output"
94 if u"parallel" in iperf3_attributes:
95 iperf3_cmd[u"args"] += \
96 f" --parallel {iperf3_attributes[u'parallel']}"
97 if u"bytes" in iperf3_attributes:
98 iperf3_cmd[u"args"] += \
99 f" --bytes {iperf3_attributes[u'bytes']}"
103 def set_hoststack_quic_fifo_size(node, fifo_size):
104 """Set the QUIC protocol fifo size.
106 :param node: Node to set the QUIC fifo size on.
107 :param fifo_size: fifo size, passed to the quic set fifo-size command.
111 cmd = f"quic set fifo-size {fifo_size}"
112 PapiSocketExecutor.run_cli_cmd(node, cmd)
115 def set_hoststack_quic_crypto_engine(node, quic_crypto_engine,
116 fail_on_error=False):
117 """Set the Hoststack QUIC crypto engine on node
119 :param node: Node to enable/disable HostStack.
120 :param quic_crypto_engine: type of crypto engine
122 :type quic_crypto_engine: str
124 vpp_crypto_engines = {u"openssl", u"ia32", u"ipsecmb"}
125 if quic_crypto_engine == u"nocrypto":
126 logger.trace(u"No QUIC crypto engine.")
129 if quic_crypto_engine in vpp_crypto_engines:
130 cmds = [u"quic set crypto api vpp",
131 f"set crypto handler aes-128-gcm {quic_crypto_engine}",
132 f"set crypto handler aes-256-gcm {quic_crypto_engine}"]
133 elif quic_crypto_engine == u"picotls":
134 cmds = [u"quic set crypto api picotls"]
136 raise ValueError(f"Unknown QUIC crypto_engine {quic_crypto_engine}")
140 PapiSocketExecutor.run_cli_cmd(node, cmd)
141 except AssertionError:
146 def get_hoststack_test_program_logs(node, program):
147 """Get HostStack test program stdout log.
149 :param node: DUT node.
150 :param program: test program.
154 program_name = program[u"name"]
155 cmd = f"sh -c \'cat /tmp/{program_name}_stdout.log\'"
156 stdout_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
157 message=f"Get {program_name} stdout log failed!")
159 cmd = f"sh -c \'cat /tmp/{program_name}_stderr.log\'"
160 stderr_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
161 message=f"Get {program_name} stderr log failed!")
162 return stdout_log, stderr_log
165 def start_hoststack_test_program(node, namespace, program):
166 """Start the specified HostStack test program.
168 :param node: DUT node.
169 :param namespace: Net Namespace to run program in.
170 :param program: Test program.
176 :raises RuntimeError: If node subtype is not a DUT or startup failed.
178 # TODO: Pin test program to core(s) on same numa node as VPP.
179 if node[u"type"] != u"DUT":
180 raise RuntimeError(u"Node type is not a DUT!")
182 program_name = program[u"name"]
183 DUTSetup.kill_program(node, program_name, namespace)
185 if namespace == u"default":
188 shell_cmd = f"ip netns exec {namespace} sh -c"
190 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
191 args = program[u"args"]
192 cmd = f"nohup {shell_cmd} \'{env_vars}{program_name} {args} " \
193 f">/tmp/{program_name}_stdout.log " \
194 f"2>/tmp/{program_name}_stderr.log &\'"
196 exec_cmd_no_error(node, cmd, sudo=True)
197 return DUTSetup.get_pid(node, program_name)[0]
199 stdout_log, stderr_log = \
200 HoststackUtil.get_hoststack_test_program_logs(node,
202 raise RuntimeError(f"Start {program_name} failed!\nSTDERR:\n" \
203 f"{stderr_log}\nSTDOUT:\n{stdout_log}")
207 def stop_hoststack_test_program(node, program, pid):
208 """Stop the specified Hoststack test program.
210 :param node: DUT node.
211 :param program: Test program.
212 :param pid: Process ID of test program.
217 program_name = program[u"name"]
218 if program_name == u"nginx":
219 cmd = u"nginx -s quit"
220 errmsg = u"Quit nginx failed!"
222 cmd = f'if [ -n "$(ps {pid} | grep {program_name})" ] ; ' \
223 f'then kill -s SIGTERM {pid}; fi'
224 errmsg = f"Kill {program_name} ({pid}) failed!"
226 exec_cmd_no_error(node, cmd, message=errmsg, sudo=True)
229 def hoststack_test_program_finished(node, program_pid):
230 """Wait for the specified HostStack test program process to complete.
232 :param node: DUT node.
233 :param program_pid: test program pid.
235 :type program_pid: str
236 :raises RuntimeError: If node subtype is not a DUT.
238 if node[u"type"] != u"DUT":
239 raise RuntimeError(u"Node type is not a DUT!")
241 cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
242 exec_cmd(node, cmd, sudo=True)
243 # Wait a bit for stdout/stderr to be flushed to log files
244 # TODO: see if sub-second sleep works e.g. sleep(0.1)
248 def analyze_hoststack_test_program_output(node, role, nsim_attr,
250 """Gather HostStack test program output and check for errors.
252 :param node: DUT node.
253 :param role: Role (client|server) of test program.
254 :param nsim_attr: Network Simulation Attributes.
255 :param program: Test program.
256 :param program_args: List of test program args.
259 :type nsim_attr: dict
261 :returns: tuple of no results bool and test program results.
263 :raises RuntimeError: If node subtype is not a DUT.
265 if node[u"type"] != u"DUT":
266 raise RuntimeError(u"Node type is not a DUT!")
268 program_name = program[u"name"]
269 program_stdout, program_stderr = \
270 HoststackUtil.get_hoststack_test_program_logs(node, program)
271 if len(program_stdout) == 0 and len(program_stderr) == 0:
272 logger.trace(f"Retrying {program_name} log retrieval")
273 program_stdout, program_stderr = \
274 HoststackUtil.get_hoststack_test_program_logs(node, program)
277 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
278 program_cmd = f"{env_vars}{program_name} {program[u'args']}"
279 test_results = f"Test Results of '{program_cmd}':\n"
281 if nsim_attr[u"output_feature_enable"] or \
282 nsim_attr[u"cross_connect_feature_enable"]:
283 if nsim_attr[u"output_feature_enable"]:
284 feature_name = u"output"
286 feature_name = u"cross-connect"
288 f"NSIM({feature_name}): delay " \
289 f"{nsim_attr[u'delay_in_usec']} usecs, " \
290 f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \
291 f"bandwidth {nsim_attr[u'bandwidth_in_bits_per_second']} " \
292 f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \
295 if u"error" in program_stderr.lower():
296 test_results += f"ERROR DETECTED:\n{program_stderr}"
297 raise RuntimeError(test_results)
299 bad_test_results = False
300 if program == u"vpp_echo" and u"JSON stats" not in program_stdout:
301 test_results += u"Invalid test data output!\n"
302 bad_test_results = True
303 test_results += program_stdout
305 raise RuntimeError(test_results)
308 test_results += f"\nNo {program} test data retrieved!\n"
309 cmd = u"ls -l /tmp/*.log"
310 ls_stdout, _ = exec_cmd_no_error(node, cmd, sudo=True)
311 test_results += f"{ls_stdout}\n"
313 # TODO: Incorporate show error stats into results analysis
316 f"\n{role} VPP 'show errors' on host {host}:\n" \
317 f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n"
319 return no_results, test_results
322 def no_hoststack_test_program_results(server_no_results, client_no_results):
323 """Return True if no HostStack test program output was gathered.
325 :param server_no_results: server no results value.
326 :param client_no_results: client no results value.
327 :type server_no_results: bool
328 :type client_no_results: bool
331 return server_no_results and client_no_results