feat(model): Hoststack type
[csit.git] / resources / libraries / python / HoststackUtil.py
index d184b5a..35acdd7 100644 (file)
@@ -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,7 +38,6 @@ 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"]
@@ -71,7 +73,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 +101,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
@@ -164,6 +172,39 @@ class HoststackUtil():
             message=f"Get {program_name} stderr log failed!")
         return stdout_log, 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 +235,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]
@@ -246,7 +291,6 @@ class HoststackUtil():
         cmd = f"sh -c 'strace -qqe trace=none -p {program_pid}'"
         exec_cmd(node, cmd, sudo=True)
         # 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
@@ -289,9 +333,9 @@ class HoststackUtil():
         program_cmd = f"{env_vars}{program_name} {program[u'args']}"
         test_results = f"Test Results of '{program_cmd}':\n"
 
-        if nsim_attr[u"output_feature_enable"] or \
-            nsim_attr[u"cross_connect_feature_enable"]:
-            if nsim_attr[u"output_feature_enable"]:
+        if nsim_attr[u"output_nsim_enable"] or \
+            nsim_attr[u"xc_nsim_enable"]:
+            if nsim_attr[u"output_nsim_enable"]:
                 feature_name = u"output"
             else:
                 feature_name = u"cross-connect"
@@ -299,11 +343,10 @@ class HoststackUtil():
                 f"NSIM({feature_name}): delay " \
                 f"{nsim_attr[u'delay_in_usec']} usecs, " \
                 f"avg-pkt-size {nsim_attr[u'average_packet_size']}, " \
-                f"bandwidth {nsim_attr[u'bandwidth_in_bits_per_second']} " \
+                f"bandwidth {nsim_attr[u'bw_in_bits_per_second']} " \
                 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,11 +364,13 @@ 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=program_json["time"]
+                )
             else:
                 test_results += u"Invalid test data output!\n" + program_stdout
                 return (True, test_results)
@@ -333,6 +378,11 @@ class HoststackUtil():
             test_results += program_stdout
             iperf3_json = json.loads(program_stdout)
             program_json = iperf3_json[u"intervals"][0][u"sum"]
+            export_hoststack_results(
+                bandwidth=program_json["bits_per_second"],
+                duration=program_json["seconds"],
+                retransmits=program_json["retransmits"]
+            )
         else:
             test_results += u"Unknown HostStack Test Program!\n" + \
                             program_stdout
@@ -350,3 +400,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")