Implemented HW2HW network performance testing

Implemented HW2HW network performance test:
- Added K8S client manager and API methods
- Added method for collecting HW compute nodes
- Improved and extended global config
- Extended requirements.txt, pinned some modules
- Extended and improved SSH methods

Added some small improvements:
- extended .gitignore
- Added some custom exceptions instead of basic ones
- Renamed some classes
- Set iperf v2 to be default for multi-threads tests
- Updated README file

Related-PROD: PROD-36943
Change-Id: I265058967ccc01d96bf3bca532a8a0ae2a26f1f2
diff --git a/tests/test_hw2hw.py b/tests/test_hw2hw.py
new file mode 100644
index 0000000..38bd58d
--- /dev/null
+++ b/tests/test_hw2hw.py
@@ -0,0 +1,126 @@
+import logging
+import sys
+
+import pytest
+from texttable import Texttable
+
+import utils
+from utils import helpers
+from utils import ssh
+from utils.k8s_client import K8SCliActions
+
+
+logger = logging.getLogger(__name__)
+
+
+def test_hw2hw(hw_pair, k8s_v1_client, request, html_report):
+    """
+    Simplified Performance Test: HW to HW test via some specific interface
+    1. Get the nodes list from the config, or get computes from K8S nodes list
+    2. Connect to the nodes, install iperf, iperf3 there
+    3. Start iperf, iperf3 at some specific interface
+    4. Measure HW to HW nodes via some interface IP, 1 thread
+    5. Measure HW to HW nodes via some interface IP, 1 thread, multiple threads
+       (10 by default)
+    6. Draw the table with all pairs and results
+    """
+    k8s_actions = K8SCliActions(k8s_v1_client)
+    config = utils.get_configuration()
+    node_ssh_key_path = config.get("node_ssh_key_path", "")
+    node_ssh_username = config.get("node_ssh_username", "mcc-user")
+    iperf_time = int(config.get('iperf_time', 60))
+    threads = int(config.get('multiple_threads_number', 10))
+    iperf_utility = config.get('multiple_threads_iperf_utility', 'iperf3')
+    result_table = Texttable(max_width=120)
+    network_cidr = config.get('network_cidr', '')
+    cleanup_list = []
+
+    if not node_ssh_key_path:
+        pytest.skip("No private key for the MOSK K8S nodes is provided. "
+                    "Please set the ssh key path in the config option "
+                    "'node_ssh_key_path'.")
+
+    if not network_cidr:
+        # TODO: implement setting several comma-separated net ranges
+        # TODO: take some default (storage or tenant) interface if not set
+        pytest.skip("No network interface is provided. Please set the "
+                    "network range in the config option 'network_cidr'.")
+
+    # get K8S nodes' names and Internal IPs for the hw_pair list
+    nodes_info = k8s_actions.get_nodes_info_by_names(hw_pair)
+    # connect to the nodes and prepare iperf
+    node_connect = ssh.SSHTransport(address=nodes_info[0]["address"],
+                                    username=node_ssh_username,
+                                    private_key=node_ssh_key_path)
+    for node in nodes_info:
+        iperf_ip = node_connect.get_node_ip_addresses_from_cidr(
+            node['address'], network_cidr, user=node_ssh_username,
+            private_key=node_ssh_key_path)
+        node["iperf_ip"] = iperf_ip
+        logger.info(
+            f"Connecting to the node {node['name']}, IP {node['address']}")
+        iperf = ssh.IperfAtNode(
+            ip=node["address"], iperf_test_ip=iperf_ip,
+            user=node_ssh_username,
+            private_key=node_ssh_key_path)
+        if iperf.install_iperf:
+            cleanup_list.append(node["address"])
+
+    # Prepare the result table and run iperf3
+    table_rows = [[
+        'Test Case', 'Node 1', 'Node 2', 'Network', 'Result'
+    ]]
+    # Do iperf3 measurement #1
+    measurement1 = f"HW to HW via {network_cidr} interface, 1 thread; iperf3"
+    logger.info("Doing '{}' measurement...".format(measurement1))
+    command1 = "iperf3 -c {} -t {} -B {} | grep sender | tail -n 1".format(
+            nodes_info[1]["iperf_ip"], iperf_time, nodes_info[0]["iperf_ip"])
+    logger.info(f"Running the command: {command1}")
+    result1 = node_connect.exec_command(command1)
+    res1 = (b" ".join(result1.split()[-4:-2:])).decode('utf-8')
+    logger.info("Result #1 is {}".format(res1))
+    table_rows.append([measurement1,
+                       "{}".format(nodes_info[0]["name"]),
+                       "{}".format(nodes_info[1]["name"]),
+                       "{}".format(network_cidr),
+                       "{}".format(res1)])
+
+    # Do iperf/iperf3 measurement #2
+    measurement2 = (f"HW to HW via {network_cidr} interface, {threads} "
+                    f"threads; {iperf_utility}")
+    logger.info(f"Doing '{measurement2}' measurement...")
+    if iperf_utility == "iperf3":
+        command2 = '{} -c {} -P {} -t {} -B {} | grep sender | tail -n 1'\
+            .format(iperf_utility, nodes_info[1]["iperf_ip"],
+                    threads, iperf_time, nodes_info[0]["iperf_ip"])
+        logger.info(f"Running the command: {command2}")
+        result2 = node_connect.exec_command(command2)
+        res2 = (b" ".join(result2.split()[-4:-2:])).decode('utf-8')
+    else:
+        iperf_utility = "iperf"
+        command2 = '{} -c {} -P {} -t {} -B {} | tail -n 1'.format(
+            iperf_utility, nodes_info[1]["iperf_ip"], threads, iperf_time,
+            nodes_info[0]["iperf_ip"])
+        logger.info(f"Running the command: {command2}")
+        result2 = node_connect.exec_command(command2)
+        res2 = (b" ".join(result2.split()[-2::])).decode('utf-8')
+    logger.info("Result #2 is {}".format(res2))
+    table_rows.append([measurement2,
+                       "{}".format(nodes_info[0]["name"]),
+                       "{}".format(nodes_info[1]["name"]),
+                       "{}".format(network_cidr),
+                       "{}".format(res2)])
+
+    # Draw the results table
+    logger.info("Drawing the table with iperf results...")
+    result_table.add_rows(table_rows)
+    sys.stdout.write('\n{}\n'.format(result_table.draw()))
+
+    # Send the results to CSV file at reports/ directory
+    helpers.create_test_result_table_csv_file(
+        table_rows, request.node.name)
+
+    # Delete the iperf, iperf3 packages if they were installed
+    for ip in cleanup_list:
+        ssh.IperfAtNode.remove_iperf_packages(
+            ip, node_ssh_username, node_ssh_key_path)