aboutsummaryrefslogtreecommitdiffstats
path: root/resources/libraries/python/HoststackUtil.py
diff options
context:
space:
mode:
Diffstat (limited to 'resources/libraries/python/HoststackUtil.py')
-rw-r--r--resources/libraries/python/HoststackUtil.py184
1 files changed, 159 insertions, 25 deletions
diff --git a/resources/libraries/python/HoststackUtil.py b/resources/libraries/python/HoststackUtil.py
index c307946698..399395d41a 100644
--- a/resources/libraries/python/HoststackUtil.py
+++ b/resources/libraries/python/HoststackUtil.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2023 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
@@ -17,9 +17,12 @@ from time import sleep
from robot.api import logger
from resources.libraries.python.Constants import Constants
-from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
-from resources.libraries.python.PapiExecutor import PapiSocketExecutor
from resources.libraries.python.DUTSetup import DUTSetup
+from resources.libraries.python.model.ExportResult import (
+ export_hoststack_results
+)
+from resources.libraries.python.PapiExecutor import PapiSocketExecutor
+from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
class HoststackUtil():
"""Utilities for Host Stack tests."""
@@ -35,14 +38,13 @@ class HoststackUtil():
'args' - command arguments.
:rtype: dict
"""
- # TODO: Use a python class instead of dictionary for the return type
proto = vpp_echo_attributes[u"uri_protocol"]
addr = vpp_echo_attributes[u"uri_ip4_addr"]
port = vpp_echo_attributes[u"uri_port"]
vpp_echo_cmd = {}
vpp_echo_cmd[u"name"] = u"vpp_echo"
vpp_echo_cmd[u"args"] = f"{vpp_echo_attributes[u'role']} " \
- f"socket-name {vpp_echo_attributes[u'vpp_api_socket']} " \
+ f"socket-name {vpp_echo_attributes[u'app_api_socket']} " \
f"{vpp_echo_attributes[u'json_output']} " \
f"uri {proto}://{addr}/{port} " \
f"nthreads {vpp_echo_attributes[u'nthreads']} " \
@@ -57,6 +59,8 @@ class HoststackUtil():
vpp_echo_cmd[u"args"] += u" rx-results-diff"
if vpp_echo_attributes[u"tx_results_diff"]:
vpp_echo_cmd[u"args"] += u" tx-results-diff"
+ if vpp_echo_attributes[u"use_app_socket_api"]:
+ vpp_echo_cmd[u"args"] += u" use-app-socket-api"
return vpp_echo_cmd
@staticmethod
@@ -71,7 +75,6 @@ class HoststackUtil():
'args' - command arguments.
:rtype: dict
"""
- # TODO: Use a python class instead of dictionary for the return type
iperf3_cmd = {}
iperf3_cmd[u"env_vars"] = f"VCL_CONFIG={Constants.REMOTE_FW_DIR}/" \
f"{Constants.RESOURCES_TPL_VCL}/" \
@@ -100,6 +103,13 @@ class HoststackUtil():
if u"time" in iperf3_attributes:
iperf3_cmd[u"args"] += \
f" --time {iperf3_attributes[u'time']}"
+ if iperf3_attributes[u"udp"]:
+ iperf3_cmd[u"args"] += u" --udp"
+ iperf3_cmd[u"args"] += \
+ f" --bandwidth {iperf3_attributes[u'bandwidth']}"
+ if iperf3_attributes[u"length"] > 0:
+ iperf3_cmd[u"args"] += \
+ f" --length {iperf3_attributes[u'length']}"
return iperf3_cmd
@staticmethod
@@ -146,15 +156,14 @@ class HoststackUtil():
raise
@staticmethod
- def get_hoststack_test_program_logs(node, program):
+ def _get_hoststack_test_program_logs(node, program_name):
"""Get HostStack test program stdout log.
:param node: DUT node.
- :param program: test program.
+ :param program_name: test program.
:type node: dict
- :type program: dict
+ :type program_name: str
"""
- program_name = program[u"name"]
cmd = f"sh -c \'cat /tmp/{program_name}_stdout.log\'"
stdout_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
message=f"Get {program_name} stdout log failed!")
@@ -162,9 +171,63 @@ class HoststackUtil():
cmd = f"sh -c \'cat /tmp/{program_name}_stderr.log\'"
stderr_log, _ = exec_cmd_no_error(node, cmd, sudo=True, \
message=f"Get {program_name} stderr log failed!")
+
return stdout_log, stderr_log
@staticmethod
+ def get_hoststack_test_program_logs(node, program):
+ """Get HostStack test program stdout log.
+
+ :param node: DUT node.
+ :param program: test program.
+ :type node: dict
+ :type program: dict
+ """
+ program_name = program[u"name"]
+ program_stdout_log, program_stderr_log = \
+ HoststackUtil._get_hoststack_test_program_logs(node,
+ program_name)
+ if len(program_stdout_log) == 0 and len(program_stderr_log) == 0:
+ logger.trace(f"Retrying {program_name} log retrieval")
+ program_stdout_log, program_stderr_log = \
+ HoststackUtil._get_hoststack_test_program_logs(node,
+ program_name)
+ return program_stdout_log, program_stderr_log
+
+ @staticmethod
+ def get_nginx_command(nginx_attributes, nginx_version, nginx_ins_dir):
+ """Construct the NGINX command using the specified attributes.
+
+ :param nginx_attributes: NGINX test program attributes.
+ :param nginx_version: NGINX version.
+ :param nginx_ins_dir: NGINX install dir.
+ :type nginx_attributes: dict
+ :type nginx_version: str
+ :type nginx_ins_dir: str
+ :returns: Command line components of the NGINX command
+ 'env_vars' - environment variables
+ 'name' - program name
+ 'args' - command arguments.
+ 'path' - program path.
+ :rtype: dict
+ """
+ nginx_cmd = dict()
+ nginx_cmd[u"env_vars"] = f"VCL_CONFIG={Constants.REMOTE_FW_DIR}/" \
+ f"{Constants.RESOURCES_TPL_VCL}/" \
+ f"{nginx_attributes[u'vcl_config']}"
+ if nginx_attributes[u"ld_preload"]:
+ nginx_cmd[u"env_vars"] += \
+ f" LD_PRELOAD={Constants.VCL_LDPRELOAD_LIBRARY}"
+ if nginx_attributes[u'transparent_tls']:
+ nginx_cmd[u"env_vars"] += u" LDP_ENV_TLS_TRANS=1"
+
+ nginx_cmd[u"name"] = u"nginx"
+ nginx_cmd[u"path"] = f"{nginx_ins_dir}nginx-{nginx_version}/sbin/"
+ nginx_cmd[u"args"] = f"-c {nginx_ins_dir}/" \
+ f"nginx-{nginx_version}/conf/nginx.conf"
+ return nginx_cmd
+
+ @staticmethod
def start_hoststack_test_program(node, namespace, core_list, program):
"""Start the specified HostStack test program.
@@ -194,9 +257,13 @@ class HoststackUtil():
env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
args = program[u"args"]
- cmd = f"nohup {shell_cmd} \'{env_vars}taskset --cpu-list {core_list} " \
- f"{program_name} {args} >/tmp/{program_name}_stdout.log " \
- f"2>/tmp/{program_name}_stderr.log &\'"
+ program_path = program.get(u"path", u"")
+ # NGINX used `worker_cpu_affinity` in configuration file
+ taskset_cmd = u"" if program_name == u"nginx" else \
+ f"taskset --cpu-list {core_list}"
+ cmd = f"nohup {shell_cmd} \'{env_vars}{taskset_cmd} " \
+ f"{program_path}{program_name} {args} >/tmp/{program_name}_" \
+ f"stdout.log 2>/tmp/{program_name}_stderr.log &\'"
try:
exec_cmd_no_error(node, cmd, sudo=True)
return DUTSetup.get_pid(node, program_name)[0]
@@ -231,22 +298,69 @@ class HoststackUtil():
exec_cmd_no_error(node, cmd, message=errmsg, sudo=True)
@staticmethod
- def hoststack_test_program_finished(node, program_pid):
+ def hoststack_test_program_finished(node, program_pid, program,
+ other_node, other_program):
"""Wait for the specified HostStack test program process to complete.
:param node: DUT node.
:param program_pid: test program pid.
+ :param program: test program
+ :param other_node: DUT node of other hoststack program
+ :param other_program: other test program
:type node: dict
:type program_pid: str
+ :type program: dict
+ :type other_node: dict
+ :type other_program: dict
:raises RuntimeError: If node subtype is not a DUT.
"""
if node[u"type"] != u"DUT":
raise RuntimeError(u"Node type is not a DUT!")
+ if other_node[u"type"] != u"DUT":
+ raise RuntimeError(u"Other node type is not a DUT!")
cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
- exec_cmd(node, cmd, sudo=True)
+ try:
+ exec_cmd(node, cmd, sudo=True)
+ except:
+ sleep(180)
+ if u"client" in program[u"args"]:
+ role = u"client"
+ else:
+ role = u"server"
+ program_stdout, program_stderr = \
+ HoststackUtil.get_hoststack_test_program_logs(node, program)
+ if len(program_stdout) > 0:
+ logger.debug(f"{program[u'name']} {role} stdout log:\n"
+ f"{program_stdout}")
+ else:
+ logger.debug(f"Empty {program[u'name']} {role} stdout log :(")
+ if len(program_stderr) > 0:
+ logger.debug(f"{program[u'name']} stderr log:\n"
+ f"{program_stderr}")
+ else:
+ logger.debug(f"Empty {program[u'name']} stderr log :(")
+ if u"client" in other_program[u"args"]:
+ role = u"client"
+ else:
+ role = u"server"
+ program_stdout, program_stderr = \
+ HoststackUtil.get_hoststack_test_program_logs(other_node,
+ other_program)
+ if len(program_stdout) > 0:
+ logger.debug(f"{other_program[u'name']} {role} stdout log:\n"
+ f"{program_stdout}")
+ else:
+ logger.debug(f"Empty {other_program[u'name']} "
+ f"{role} stdout log :(")
+ if len(program_stderr) > 0:
+ logger.debug(f"{other_program[u'name']} {role} stderr log:\n"
+ f"{program_stderr}")
+ else:
+ logger.debug(f"Empty {other_program[u'name']} "
+ f"{role} stderr log :(")
+ raise
# Wait a bit for stdout/stderr to be flushed to log files
- # TODO: see if sub-second sleep works e.g. sleep(0.1)
sleep(1)
@staticmethod
@@ -280,10 +394,6 @@ class HoststackUtil():
program_name = program[u"name"]
program_stdout, program_stderr = \
HoststackUtil.get_hoststack_test_program_logs(node, program)
- if len(program_stdout) == 0 and len(program_stderr) == 0:
- logger.trace(f"Retrying {program_name} log retrieval")
- program_stdout, program_stderr = \
- HoststackUtil.get_hoststack_test_program_logs(node, program)
env_vars = f"{program[u'env_vars']} " if u"env_vars" in program else u""
program_cmd = f"{env_vars}{program_name} {program[u'args']}"
@@ -303,7 +413,6 @@ class HoststackUtil():
f"bits/sec, pkt-drop-rate {nsim_attr[u'packets_per_drop']} " \
f"pkts/drop\n"
- # TODO: Incorporate show error stats into results analysis
test_results += \
f"\n{role} VPP 'show errors' on host {node[u'host']}:\n" \
f"{PapiSocketExecutor.run_cli_cmd(node, u'show error')}\n"
@@ -321,18 +430,28 @@ class HoststackUtil():
if u"JSON stats" in program_stdout and \
u'"has_failed": "0"' in program_stdout:
json_start = program_stdout.find(u"{")
- #TODO: Fix parsing once vpp_echo produces valid
- # JSON output. Truncate for now.
json_end = program_stdout.find(u',\n "closing"')
json_results = f"{program_stdout[json_start:json_end]}\n}}"
program_json = json.loads(json_results)
+ export_hoststack_results(
+ bandwidth=program_json["rx_bits_per_second"],
+ duration=float(program_json["time"])
+ )
else:
test_results += u"Invalid test data output!\n" + program_stdout
return (True, test_results)
elif program[u"name"] == u"iperf3":
test_results += program_stdout
- iperf3_json = json.loads(program_stdout)
- program_json = iperf3_json[u"intervals"][0][u"sum"]
+ program_json = json.loads(program_stdout)[u"intervals"][0][u"sum"]
+ try:
+ retransmits = program_json["retransmits"]
+ except KeyError:
+ retransmits = None
+ export_hoststack_results(
+ bandwidth=program_json["bits_per_second"],
+ duration=program_json["seconds"],
+ retransmits=retransmits
+ )
else:
test_results += u"Unknown HostStack Test Program!\n" + \
program_stdout
@@ -350,3 +469,18 @@ class HoststackUtil():
:rtype: bool
"""
return server_defer_fail and client_defer_fail
+
+ @staticmethod
+ def log_vpp_hoststack_data(node):
+ """Retrieve and log VPP HostStack data.
+
+ :param node: DUT node.
+ :type node: dict
+ :raises RuntimeError: If node subtype is not a DUT or startup failed.
+ """
+
+ if node[u"type"] != u"DUT":
+ raise RuntimeError(u"Node type is not a DUT!")
+
+ PapiSocketExecutor.run_cli_cmd(node, u"show error")
+ PapiSocketExecutor.run_cli_cmd(node, u"show interface")