From b09d77d523eb482acbc4679d0b0986c3cfe8da4a Mon Sep 17 00:00:00 2001 From: Peng LEI Date: Sat, 7 Dec 2024 21:36:56 +0800 Subject: [PATCH] refactor: defined Test --- src/containernet/containernet.py | 2 +- src/containernet/containernet_network.py | 4 +- src/{containernet => core}/config.py | 126 ++++++++++++------ src/{containernet => core}/linear_topology.py | 0 src/core/network_mgr.py | 4 +- src/core/runner.py | 2 +- src/core/testbed_mgr.py | 2 +- src/{containernet => core}/topology.py | 44 +++++- src/interfaces/network.py | 2 +- src/interfaces/network_mgr.py | 2 +- src/run_test.py | 107 +++------------ src/testbed/testbed_network.py | 2 +- 12 files changed, 155 insertions(+), 142 deletions(-) rename src/{containernet => core}/config.py (57%) rename src/{containernet => core}/linear_topology.py (100%) rename src/{containernet => core}/topology.py (75%) diff --git a/src/containernet/containernet.py b/src/containernet/containernet.py index 687ae52..80dc3e5 100644 --- a/src/containernet/containernet.py +++ b/src/containernet/containernet.py @@ -5,7 +5,7 @@ import yaml from tools.util import is_same_path from var.global_var import g_root_path -from .config import (NestedConfig) +from core.config import (NestedConfig) def load_nested_config(nested_config_file: str, diff --git a/src/containernet/containernet_network.py b/src/containernet/containernet_network.py index 533d641..5452588 100644 --- a/src/containernet/containernet_network.py +++ b/src/containernet/containernet_network.py @@ -2,11 +2,11 @@ import os from mininet.net import Containernet # type: ignore from mininet.util import ipStr, netParse -from containernet.topology import (ITopology, MatrixType) +from core.config import (NodeConfig) +from core.topology import (ITopology, MatrixType) from containernet.containernet_host import ContainernetHostAdapter from interfaces.network import INetwork from interfaces.routing import IRoutingStrategy -from .config import (NodeConfig) def subnets(base_ip, parent_ip): diff --git a/src/containernet/config.py b/src/core/config.py similarity index 57% rename from src/containernet/config.py rename to src/core/config.py index 23fd1db..38c0331 100644 --- a/src/containernet/config.py +++ b/src/core/config.py @@ -5,31 +5,12 @@ from abc import ABC from dataclasses import dataclass, field from typing import Optional, List, Dict, Any -from enum import IntEnum import logging import os import yaml - -class MatrixType(IntEnum): - # Adjacency matrix to describe the network topology - ADJACENCY_MATRIX = 0 - # Bandwidth matrix to describe the network bandwidth link-by-link - BANDW_MATRIX = 1 - # Loss matrix to describe the network loss link-by-link - LOSS_MATRIX = 2 - # Latency matrix to describe the network latency link-by-link - LATENCY_MATRIX = 3 - # Jitter matrix to describe the network jitter link-by-link - JITTER_MATRIX = 4 - - -class TopologyType(IntEnum): - linear = 0 # Linear chain topology - star = 1 # Star topology - tree = 2 # Complete Binary Tree - butterfly = 3 # Butterfly topology - mesh = 5 # Random Mesh topology +from core.topology import (ITopology, TopologyConfig) +from core.linear_topology import LinearTopology @dataclass @@ -58,25 +39,6 @@ class NestedConfig: mounts: Optional[List[str]] = field(default=None) -@dataclass -class Parameter: - name: str - init_value: List[int] - - -@dataclass -class TopologyConfig: - """Configuration for the network topology. - """ - name: str - nodes: int - topology_type: TopologyType - # @array_description: the array description of the topology - array_description: Optional[List[Parameter]] = field(default=None) - # @json_description: the json description of the topology - json_description: Optional[str] = field(default=None) - - supported_config_keys = ["topology", "node_config"] @@ -155,3 +117,87 @@ def load_yaml_config(config_base_path: str, yaml_description: Dict[str, Any], co if config_key == "topology": return TopologyConfig(**yaml_description) return None + + +class Test: + """Class to hold the test configuration(yaml) for one cases + """ + + def __init__(self, test_yaml: Dict[str, Any], name: str): + self.name = name + self.test_yaml = test_yaml + + def yaml(self): + return self.test_yaml + + def is_active(self): + if self.test_yaml is None: + return False + if "if" in self.test_yaml and not self.test_yaml["if"]: + return False + return True + + def load_topology(self, config_base_path) -> Optional[ITopology]: + """Load network related configuration from the yaml file. + """ + if 'topology' not in self.test_yaml: + logging.error("Error: missing key topology in the test case yaml.") + return None + local_yaml = self.test_yaml['topology'] + logging.info(f"Test: local_yaml %s", + local_yaml) + if local_yaml is None: + logging.error("Error: content of topology is None.") + return None + loaded_conf = IConfig.load_yaml_config(config_base_path, + local_yaml, + 'topology') + if loaded_conf is None: + logging.error("Error: loaded_conf of topology is None.") + return None + if not isinstance(loaded_conf, TopologyConfig): + logging.error("Error: loaded_conf of topology is None.") + return None + if loaded_conf.topology_type == "linear": + return LinearTopology(loaded_conf) + logging.error("Error: unsupported topology type.") + return None + + +def load_all_tests(test_yaml_file: str, test_name: str = "all") -> List[Test]: + """ + Load the test case configuration from a yaml file. + """ + logging.info( + "########################## Oasis Loading Tests " + "##########################") + # List of active cases. + test_cases = None + with open(test_yaml_file, 'r', encoding='utf-8') as stream: + try: + yaml_content = yaml.safe_load(stream) + test_cases = yaml_content["tests"] + except yaml.YAMLError as exc: + logging.error(exc) + return [] + # ------------------------------------------------ + if test_cases is None: + logging.error("No test cases are loaded from %s", test_yaml_file) + return [] + active_test_list = [] + if test_name in ("all", "All", "ALL"): + test_name = "" + for name in test_cases.keys(): + if test_name not in ("", name): + logging.debug("Oasis skips the test case %s", name) + continue + test_cases[name]["name"] = name + test = Test(test_cases[name], name) + if test.is_active(): + active_test_list.append(test) + logging.info(f"case %s is enabled!", name) + else: + logging.info(f"case %s is disabled!", name) + if len(active_test_list) == 0: + logging.info(f"No active test case in %s", test_yaml_file) + return active_test_list diff --git a/src/containernet/linear_topology.py b/src/core/linear_topology.py similarity index 100% rename from src/containernet/linear_topology.py rename to src/core/linear_topology.py diff --git a/src/core/network_mgr.py b/src/core/network_mgr.py index 41e6001..1968bc4 100644 --- a/src/core/network_mgr.py +++ b/src/core/network_mgr.py @@ -1,8 +1,8 @@ import logging -from containernet.config import NodeConfig +from core.config import NodeConfig +from core.topology import ITopology from containernet.containernet_network import ContainerizedNetwork -from containernet.topology import ITopology from routing.routing_factory import RoutingFactory, route_string_to_enum from interfaces.network_mgr import (INetworkManager, NetworkType) diff --git a/src/core/runner.py b/src/core/runner.py index ad84bda..f4d55a4 100644 --- a/src/core/runner.py +++ b/src/core/runner.py @@ -8,7 +8,7 @@ from interfaces.network_mgr import INetworkManager from interfaces.network import INetwork -from containernet.topology import ITopology +from core.topology import ITopology from testsuites.test import (TestType, TestConfig) from testsuites.test_iperf import IperfTest from testsuites.test_ping import PingTest diff --git a/src/core/testbed_mgr.py b/src/core/testbed_mgr.py index 02f43f6..06a66a9 100644 --- a/src/core/testbed_mgr.py +++ b/src/core/testbed_mgr.py @@ -1,6 +1,6 @@ import logging -from containernet.topology import ITopology +from core.topology import ITopology from interfaces.network_mgr import (INetworkManager, NetworkType) from testbed.config import HostConfig diff --git a/src/containernet/topology.py b/src/core/topology.py similarity index 75% rename from src/containernet/topology.py rename to src/core/topology.py index 31035b5..c864b58 100644 --- a/src/containernet/topology.py +++ b/src/core/topology.py @@ -1,9 +1,11 @@ from abc import ABC, abstractmethod from enum import IntEnum + +from dataclasses import dataclass, field +from typing import Optional, List import logging import os import json -from .config import (TopologyConfig, MatrixType) class LinkAttr(IntEnum): @@ -14,6 +16,19 @@ class LinkAttr(IntEnum): link_bandwidth_backward = 4 +class MatrixType(IntEnum): + # Adjacency matrix to describe the network topology + ADJACENCY_MATRIX = 0 + # Bandwidth matrix to describe the network bandwidth link-by-link + BANDW_MATRIX = 1 + # Loss matrix to describe the network loss link-by-link + LOSS_MATRIX = 2 + # Latency matrix to describe the network latency link-by-link + LATENCY_MATRIX = 3 + # Jitter matrix to describe the network jitter link-by-link + JITTER_MATRIX = 4 + + # mapping MatrixType to the link attribute except for the adjacency matrix MatType2LinkAttr = { MatrixType.LOSS_MATRIX: LinkAttr.link_loss, @@ -23,6 +38,33 @@ class LinkAttr(IntEnum): } +class TopologyType(IntEnum): + linear = 0 # Linear chain topology + star = 1 # Star topology + tree = 2 # Complete Binary Tree + butterfly = 3 # Butterfly topology + mesh = 5 # Random Mesh topology + + +@dataclass +class Parameter: + name: str + init_value: List[int] + + +@dataclass +class TopologyConfig: + """Configuration for the network topology. + """ + name: str + nodes: int + topology_type: TopologyType + # @array_description: the array description of the topology + array_description: Optional[List[Parameter]] = field(default=None) + # @json_description: the json description of the topology + json_description: Optional[str] = field(default=None) + + class ITopology(ABC): def __init__(self, top: TopologyConfig, init_all_mat: bool = True): self.all_mats = {} diff --git a/src/interfaces/network.py b/src/interfaces/network.py index d723e07..a5c839a 100644 --- a/src/interfaces/network.py +++ b/src/interfaces/network.py @@ -2,7 +2,7 @@ import copy from abc import ABC, abstractmethod from typing import List -from containernet.topology import (ITopology) +from core.topology import (ITopology) from protosuites.proto import IProtoSuite from interfaces.routing import IRoutingStrategy from interfaces.host import IHost diff --git a/src/interfaces/network_mgr.py b/src/interfaces/network_mgr.py index f15f132..23ac757 100644 --- a/src/interfaces/network_mgr.py +++ b/src/interfaces/network_mgr.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from enum import IntEnum -from containernet.topology import ITopology +from core.topology import ITopology # alphabet table alphabet = ['h', 'i', 'j', 'k', 'l', 'm', diff --git a/src/run_test.py b/src/run_test.py index 263782f..ac17cc3 100755 --- a/src/run_test.py +++ b/src/run_test.py @@ -2,64 +2,19 @@ import sys import logging import platform -from typing import Optional import yaml -# from mininet.cli import CLI from mininet.log import setLogLevel + from interfaces.network_mgr import NetworkType -from containernet.topology import ITopology -from containernet.linear_topology import LinearTopology -from containernet.config import (IConfig, NodeConfig, TopologyConfig) from tools.util import (is_same_path, is_base_path, parse_test_file_name) from var.global_var import g_root_path +from core.config import (IConfig, NodeConfig, load_all_tests) from core.network_factory import (create_network_mgr) from core.runner import TestRunner -def load_test(test_yaml_file: str, test_name: str = "all"): - """ - Load the test case configuration from a yaml file. - """ - logging.info( - "########################## Oasis Loading Tests " - "##########################") - # List of active cases. - test_cases = None - with open(test_yaml_file, 'r', encoding='utf-8') as stream: - try: - yaml_content = yaml.safe_load(stream) - test_cases = yaml_content["tests"] - except yaml.YAMLError as exc: - logging.error(exc) - return None - # ------------------------------------------------ - test_names = test_cases.keys() - active_test_list = [] - if test_name in ("all", "All", "ALL"): - test_name = "" - for name in test_names: - if test_name not in ("", name): - logging.debug("Oasis skips the test case %s", name) - continue - test_cases[name]['name'] = name - active_test_list.append(test_cases[name]) - for case in active_test_list.copy(): - # ignore the inactive case by "if: False" - if "if" in case and not case["if"]: - logging.info( - f"case %s is disabled!", case['name']) - active_test_list.remove(case) - else: - logging.info( - f"case %s is enabled!", case['name']) - if len(active_test_list) == 0: - logging.info(f"No active test case in %s", test_yaml_file) - return None - return active_test_list - - -def load_node_config(config_base_path, file_path) -> NodeConfig: +def containernet_node_config(config_base_path, file_path) -> NodeConfig: """Load node related configuration from the yaml file. """ node_config_yaml = None @@ -85,10 +40,9 @@ def load_node_config(config_base_path, file_path) -> NodeConfig: return NodeConfig(name="", img="") -def prepare_node_config(mapped_config_path, yaml_test_file, source_workspace, original_config_path): +def load_containernet_config(mapped_config_path, yaml_test_file, source_workspace, original_config_path): # print all input parameters - node_config = load_node_config( - mapped_config_path, yaml_test_file) + node_config = containernet_node_config(mapped_config_path, yaml_test_file) if node_config is None or node_config.name == "": logging.error("Error: no containernet node config.") sys.exit(1) @@ -100,33 +54,6 @@ def prepare_node_config(mapped_config_path, yaml_test_file, source_workspace, or return node_config -def load_topology(config_base_path, test_case_yaml) -> Optional[ITopology]: - """Load network related configuration from the yaml file. - """ - if 'topology' not in test_case_yaml: - logging.error("Error: missing key topology in the test case yaml.") - return None - local_yaml = test_case_yaml['topology'] - logging.info(f"Test: local_yaml %s", - local_yaml) - if local_yaml is None: - logging.error("Error: content of topology is None.") - return None - loaded_conf = IConfig.load_yaml_config(config_base_path, - local_yaml, - 'topology') - if loaded_conf is None: - logging.error("Error: loaded_conf of topology is None.") - return None - if not isinstance(loaded_conf, TopologyConfig): - logging.error("Error: loaded_conf of topology is None.") - return None - if loaded_conf.topology_type == "linear": - return LinearTopology(loaded_conf) - logging.error("Error: unsupported topology type.") - return None - - def load_testbed_config(name, yaml_base_path_input): absolute_path_of_testbed_config_file = os.path.join( yaml_base_path_input + "/", 'testbed/predefined.testbed.yaml') @@ -202,38 +129,36 @@ def load_testbed_config(name, yaml_base_path_input): sys.exit(1) is_using_testbed = False - cur_node_config = None + cur_hosts_config = None network_manager = None if not is_using_testbed: logging.info("##### running tests on containernet.") network_manager = create_network_mgr(NetworkType.containernet) - cur_node_config = prepare_node_config( + cur_hosts_config = load_containernet_config( config_path, yaml_test_file_path, oasis_workspace, yaml_config_base_path) else: logging.info("##### running tests on testbed.") network_manager = create_network_mgr(NetworkType.testbed) - cur_node_config = load_testbed_config( + cur_hosts_config = load_testbed_config( 'testbed_nhop_shenzhen', config_path) if network_manager is None: logging.error("Error: failed to load proper network manager") sys.exit(1) # 1. execute all the tests on all constructed networks - all_tests = load_test(yaml_test_file_path, cur_selected_test) - if all_tests is None: + loaded_tests = load_all_tests(yaml_test_file_path, cur_selected_test) + if loaded_tests is None or len(loaded_tests) == 0: logging.error("Error: no test case found.") sys.exit(1) - for test in all_tests: - cur_topology = load_topology(config_path, test) + for test in loaded_tests: + cur_topology = test.load_topology(config_path) if not cur_topology: continue # 1.1 The topology in one case can be composed of multiple topologies: # Traverse all the topologies in the test case. for index, cur_top_ins in enumerate(cur_topology): - test_runner = TestRunner( - test, config_path, network_manager) - test_runner.init(cur_node_config, - cur_top_ins) + test_runner = TestRunner(test.yaml(), config_path, network_manager) + test_runner.init(cur_hosts_config, cur_top_ins) if not test_runner.is_ready(): test_runner.handle_failure() # 1.3 Load test to the network instance @@ -248,8 +173,8 @@ def load_testbed_config(name, yaml_base_path_input): res = test_runner.handle_test_results(index) if res is False: test_runner.handle_failure() - # > for index, cur_top_ins in enumerate(cur_topology): - # > for test in all_tests + # # for index, cur_top_ins in enumerate(cur_topology): + # # for test in loaded_tests: # create a regular file to indicate the test success with open(f"{g_root_path}test.success", 'w', encoding='utf-8') as f_success: f_success.write(f"test.success") diff --git a/src/testbed/testbed_network.py b/src/testbed/testbed_network.py index 93e4d13..4b0ca3b 100644 --- a/src/testbed/testbed_network.py +++ b/src/testbed/testbed_network.py @@ -1,6 +1,6 @@ import logging from typing import List -from containernet.topology import (ITopology, MatrixType) +from core.topology import (ITopology, MatrixType) from interfaces.network import INetwork from interfaces.routing import IRoutingStrategy from testbed.linux_host import LinuxHost