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"time" in iperf3_attributes:
98 iperf3_cmd[u"args"] += \
99 f" --time {iperf3_attributes[u'time']}"
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, core_list, program):
166 """Start the specified HostStack test program.
168 :param node: DUT node.
169 :param namespace: Net Namespace to run program in.
170 :param core_list: List of cpu's to pass to taskset to pin the test
171 program to a different set of cores on the same numa node as VPP.
172 :param program: Test program.
179 :raises RuntimeError: If node subtype is not a DUT or startup failed.
181 if node[u"type"] != u"DUT":
182 raise RuntimeError(u"Node type is not a DUT!")
184 program_name = program[u"name"]
185 DUTSetup.kill_program(node, program_name, namespace)
187 if namespace == u"default":
190 shell_cmd = f"ip netns exec {namespace} sh -c"
192 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
193 args = program[u"args"]
194 cmd = f"nohup {shell_cmd} \'{env_vars}taskset --cpu-list {core_list} " \
195 f"{program_name} {args} >/tmp/{program_name}_stdout.log " \
196 f"2>/tmp/{program_name}_stderr.log &\'"
198 exec_cmd_no_error(node, cmd, sudo=True)
199 return DUTSetup.get_pid(node, program_name)[0]
201 stdout_log, stderr_log = \
202 HoststackUtil.get_hoststack_test_program_logs(node,
204 raise RuntimeError(f"Start {program_name} failed!\nSTDERR:\n" \
205 f"{stderr_log}\nSTDOUT:\n{stdout_log}")
209 def stop_hoststack_test_program(node, program, pid):
210 """Stop the specified Hoststack test program.
212 :param node: DUT node.
213 :param program: Test program.
214 :param pid: Process ID of test program.
219 program_name = program[u"name"]
220 if program_name == u"nginx":
221 cmd = u"nginx -s quit"
222 errmsg = u"Quit nginx failed!"
224 cmd = f'if [ -n "$(ps {pid} | grep {program_name})" ] ; ' \
225 f'then kill -s SIGTERM {pid}; fi'
226 errmsg = f"Kill {program_name} ({pid}) failed!"
228 exec_cmd_no_error(node, cmd, message=errmsg, sudo=True)
231 def hoststack_test_program_finished(node, program_pid):
232 """Wait for the specified HostStack test program process to complete.
234 :param node: DUT node.
235 :param program_pid: test program pid.
237 :type program_pid: str
238 :raises RuntimeError: If node subtype is not a DUT.
240 if node[u"type"] != u"DUT":
241 raise RuntimeError(u"Node type is not a DUT!")
243 cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
244 exec_cmd(node, cmd, sudo=True)
245 # Wait a bit for stdout/stderr to be flushed to log files
246 # TODO: see if sub-second sleep works e.g. sleep(0.1)
250 def analyze_hoststack_test_program_output(node, role, nsim_attr,
252 """Gather HostStack test program output and check for errors.
254 :param node: DUT node.
255 :param role: Role (client|server) of test program.
256 :param nsim_attr: Network Simulation Attributes.
257 :param program: Test program.
258 :param program_args: List of test program args.
261 :type nsim_attr: dict
263 :returns: tuple of no results bool and test program results.
265 :raises RuntimeError: If node subtype is not a DUT.
267 if node[u"type"] != u"DUT":
268 raise RuntimeError(u"Node type is not a DUT!")
270 program_name = program[u"name"]
271 program_stdout, program_stderr = \
272 HoststackUtil.get_hoststack_test_program_logs(node, program)
273 if len(program_stdout) == 0 and len(program_stderr) == 0:
274 logger.trace(f"Retrying {program_name} log retrieval")
275 program_stdout, program_stderr = \
276 HoststackUtil.get_hoststack_test_program_logs(node, program)
279 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
280 program_cmd = f"{env_vars}{program_name} {program[u'args']}"
281 test_results = f"Test Results of '{program_cmd}':\n"
283 if nsim_attr[u"output_feature_enable"] or \
284 nsim_attr[u"cross_connect_feature_enable"]:
285 if nsim_attr[u"output_feature_enable"]:
286 feature_name = u"output"
288 feature_name = u"cross-connect"
290 f"NSIM({feature_name}): delay " \
291 f"{nsim_attr[u'delay_in_usec']} usecs, " \
292 f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \
293 f"bandwidth {nsim_attr[u'bandwidth_in_bits_per_second']} " \
294 f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \
297 if u"error" in program_stderr.lower():
298 test_results += f"ERROR DETECTED:\n{program_stderr}"
299 raise RuntimeError(test_results)
301 bad_test_results = False
302 if program == u"vpp_echo" and u"JSON stats" not in program_stdout:
303 test_results += u"Invalid test data output!\n"
304 bad_test_results = True
305 test_results += program_stdout
307 raise RuntimeError(test_results)
310 test_results += f"\nNo {program} test data retrieved!\n"
311 cmd = u"ls -l /tmp/*.log"
312 ls_stdout, _ = exec_cmd_no_error(node, cmd, sudo=True)
313 test_results += f"{ls_stdout}\n"
315 # TODO: Incorporate show error stats into results analysis
318 f"\n{role} VPP 'show errors' on host {host}:\n" \
319 f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n"
321 return no_results, test_results
324 def no_hoststack_test_program_results(server_no_results, client_no_results):
325 """Return True if no HostStack test program output was gathered.
327 :param server_no_results: server no results value.
328 :param client_no_results: client no results value.
329 :type server_no_results: bool
330 :type client_no_results: bool
333 return server_no_results and client_no_results