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.ssh import exec_cmd, exec_cmd_no_error
19 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
20 from resources.libraries.python.DUTSetup import DUTSetup
22 class HoststackUtil():
23 """Utilities for Host Stack tests."""
26 def get_vpp_echo_command(vpp_echo_attributes):
27 """Construct the vpp_echo command using the specified attributes.
29 :param vpp_echo_attributes: vpp_echo test program attributes.
30 :type vpp_echo_attributes: dict
31 :returns: Command line components of the vpp_echo command
33 'args' - command arguments.
36 # TODO: Use a python class instead of dictionary for the return type
37 proto = vpp_echo_attributes[u"uri_protocol"]
38 addr = vpp_echo_attributes[u"uri_ip4_addr"]
39 port = vpp_echo_attributes[u"uri_port"]
41 vpp_echo_cmd[u"name"] = u"vpp_echo"
42 vpp_echo_cmd[u"args"] = f"{vpp_echo_attributes[u'role']} " \
43 f"socket-name {vpp_echo_attributes[u'vpp_api_socket']} " \
44 f"{vpp_echo_attributes[u'json_output']} " \
45 f"uri {proto}://{addr}/{port} " \
46 f"nclients {vpp_echo_attributes[u'nclients']} " \
47 f"quic-streams {vpp_echo_attributes[u'quic_streams']} " \
48 f"time {vpp_echo_attributes[u'time']} " \
49 f"fifo-size {vpp_echo_attributes[u'fifo_size']} " \
50 f"TX={vpp_echo_attributes[u'tx_bytes']} " \
51 f"RX={vpp_echo_attributes[u'rx_bytes']}"
52 if vpp_echo_attributes[u"rx_results_diff"]:
53 vpp_echo_cmd[u"args"] += u" rx-results-diff"
54 if vpp_echo_attributes[u"tx_results_diff"]:
55 vpp_echo_cmd[u"args"] += u" tx-results-diff"
59 def set_hoststack_quic_fifo_size(node, fifo_size):
60 """Set the QUIC protocol fifo size.
62 :param node: Node to set the QUIC fifo size on.
63 :param fifo_size: fifo size, passed to the quic set fifo-size command.
67 cmd = f"quic set fifo-size {fifo_size}"
68 PapiSocketExecutor.run_cli_cmd(node, cmd)
71 def set_hoststack_quic_crypto_engine(node, quic_crypto_engine,
73 """Set the Hoststack QUIC crypto engine on node
75 :param node: Node to enable/disable HostStack.
76 :param quic_crypto_engine: type of crypto engine
78 :type quic_crypto_engine: str
80 vpp_crypto_engines = {u"openssl", u"ia32", u"ipsecmb"}
81 if quic_crypto_engine == u"nocrypto":
82 logger.trace(u"No QUIC crypto engine.")
85 if quic_crypto_engine in vpp_crypto_engines:
86 cmds = [u"quic set crypto api vpp",
87 f"set crypto handler aes-128-gcm {quic_crypto_engine}",
88 f"set crypto handler aes-256-gcm {quic_crypto_engine}"]
89 elif quic_crypto_engine == u"picotls":
90 cmds = [u"quic set crypto api picotls"]
92 raise ValueError(f"Unknown QUIC crypto_engine {quic_crypto_engine}")
96 PapiSocketExecutor.run_cli_cmd(node, cmd)
97 except AssertionError:
102 def get_hoststack_test_program_logs(node, program):
103 """Get HostStack test program stdout log.
105 :param node: DUT node.
106 :param program: test program.
110 program_name = program[u"name"]
111 cmd = f"sh -c \'cat /tmp/{program_name}_stdout.log\'"
112 stdout_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
113 message=f"Get {program_name} stdout log failed!")
115 cmd = f"sh -c \'cat /tmp/{program_name}_stderr.log\'"
116 stderr_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
117 message=f"Get {program_name} stderr log failed!")
118 return stdout_log, stderr_log
121 def start_hoststack_test_program(node, namespace, program):
122 """Start the specified HostStack test program.
124 :param node: DUT node.
125 :param namespace: Net Namespace to run program in.
126 :param program: Test program.
132 :raises RuntimeError: If node subtype is not a DUT or startup failed.
134 # TODO: Pin test program to core(s) on same numa node as VPP.
135 if node[u"type"] != u"DUT":
136 raise RuntimeError(u"Node type is not a DUT!")
138 program_name = program[u"name"]
139 DUTSetup.kill_program(node, program_name, namespace)
141 if namespace == u"default":
144 shell_cmd = f"ip netns exec {namespace} sh -c"
146 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
147 args = program[u"args"]
148 cmd = f"nohup {shell_cmd} \'{env_vars}{program_name} {args} " \
149 f">/tmp/{program_name}_stdout.log " \
150 f"2>/tmp/{program_name}_stderr.log &\'"
152 exec_cmd_no_error(node, cmd, sudo=True)
153 return DUTSetup.get_pid(node, program_name)[0]
155 stdout_log, stderr_log = \
156 HoststackUtil.get_hoststack_test_program_logs(node,
158 raise RuntimeError(f"Start {program_name} failed!\nSTDERR:\n" \
159 f"{stderr_log}\nSTDOUT:\n{stdout_log}")
163 def stop_hoststack_test_program(node, program, pid):
164 """Stop the specified Hoststack test program.
166 :param node: DUT node.
167 :param program: Test program.
168 :param pid: Process ID of test program.
173 program_name = program[u"name"]
174 if program_name == u"nginx":
175 cmd = u"nginx -s quit"
176 errmsg = u"Quit nginx failed!"
178 cmd = f'if [ -n "$(ps {pid} | grep {program_name})" ] ; ' \
179 f'then kill -s SIGTERM {pid}; fi'
180 errmsg = f"Kill {program_name} ({pid}) failed!"
182 exec_cmd_no_error(node, cmd, message=errmsg, sudo=True)
185 def hoststack_test_program_finished(node, program_pid):
186 """Wait for the specified HostStack test program process to complete.
188 :param node: DUT node.
189 :param program_pid: test program pid.
191 :type program_pid: str
192 :raises RuntimeError: If node subtype is not a DUT.
194 if node[u"type"] != u"DUT":
195 raise RuntimeError(u"Node type is not a DUT!")
197 cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
198 exec_cmd(node, cmd, sudo=True)
199 # Wait a bit for stdout/stderr to be flushed to log files
200 # TODO: see if sub-second sleep works e.g. sleep(0.1)
204 def analyze_hoststack_test_program_output(node, role, nsim_attr,
206 """Gather HostStack test program output and check for errors.
208 :param node: DUT node.
209 :param role: Role (client|server) of test program.
210 :param nsim_attr: Network Simulation Attributes.
211 :param program: Test program.
212 :param program_args: List of test program args.
215 :type nsim_attr: dict
217 :returns: tuple of no results bool and test program results.
219 :raises RuntimeError: If node subtype is not a DUT.
221 if node[u"type"] != u"DUT":
222 raise RuntimeError(u"Node type is not a DUT!")
224 program_name = program[u"name"]
225 program_stdout, program_stderr = \
226 HoststackUtil.get_hoststack_test_program_logs(node, program)
227 if len(program_stdout) == 0 and len(program_stderr) == 0:
228 logger.trace(f"Retrying {program_name} log retrieval")
229 program_stdout, program_stderr = \
230 HoststackUtil.get_hoststack_test_program_logs(node, program)
233 env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
234 program_cmd = f"{env_vars}{program_name} {program[u'args']}"
235 test_results = f"Test Results of '{program_cmd}':\n"
237 if nsim_attr[u"output_feature_enable"] or \
238 nsim_attr[u"cross_connect_feature_enable"]:
239 if nsim_attr[u"output_feature_enable"]:
240 feature_name = u"output"
242 feature_name = u"cross-connect"
244 f"NSIM({feature_name}): delay " \
245 f"{nsim_attr[u'delay_in_usec']} usecs, " \
246 f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \
247 f"bandwidth {nsim_attr[u'bandwidth_in_bits_per_second']} " \
248 f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \
251 if u"error" in program_stderr.lower():
252 test_results += f"ERROR DETECTED:\n{program_stderr}"
253 raise RuntimeError(test_results)
255 bad_test_results = False
256 if program == u"vpp_echo" and u"JSON stats" not in program_stdout:
257 test_results += u"Invalid test data output!\n"
258 bad_test_results = True
259 test_results += program_stdout
261 raise RuntimeError(test_results)
264 test_results += f"\nNo {program} test data retrieved!\n"
265 cmd = u"ls -l /tmp/*.log"
266 ls_stdout, _ = exec_cmd_no_error(node, cmd, sudo=True)
267 test_results += f"{ls_stdout}\n"
269 # TODO: Incorporate show error stats into results analysis
272 f"\n{role} VPP 'show errors' on host {host}:\n" \
273 f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n"
275 return no_results, test_results
278 def no_hoststack_test_program_results(server_no_results, client_no_results):
279 """Return True if no HostStack test program output was gathered.
281 :param server_no_results: server no results value.
282 :param client_no_results: client no results value.
283 :type server_no_results: bool
284 :type client_no_results: bool
287 return server_no_results and client_no_results