f-stack/dpdk/dts/framework/config/__init__.py

320 lines
9.2 KiB
Python
Raw Normal View History

2023-09-13 12:21:49 +00:00
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2021 Intel Corporation
2025-01-10 11:50:43 +00:00
# Copyright(c) 2022-2023 University of New Hampshire
# Copyright(c) 2023 PANTHEON.tech s.r.o.
2023-09-13 12:21:49 +00:00
"""
2025-01-10 11:50:43 +00:00
Yaml config parsing methods
2023-09-13 12:21:49 +00:00
"""
import json
import os.path
import pathlib
from dataclasses import dataclass
2025-01-10 11:50:43 +00:00
from enum import auto, unique
from typing import Any, TypedDict, Union
2023-09-13 12:21:49 +00:00
2025-01-10 11:50:43 +00:00
import warlock # type: ignore[import]
2023-09-13 12:21:49 +00:00
import yaml
from framework.settings import SETTINGS
2025-01-10 11:50:43 +00:00
from framework.utils import StrEnum
@unique
class Architecture(StrEnum):
i686 = auto()
x86_64 = auto()
x86_32 = auto()
arm64 = auto()
ppc64le = auto()
@unique
class OS(StrEnum):
linux = auto()
freebsd = auto()
windows = auto()
@unique
class CPUType(StrEnum):
native = auto()
armv8a = auto()
dpaa2 = auto()
thunderx = auto()
xgene1 = auto()
@unique
class Compiler(StrEnum):
gcc = auto()
clang = auto()
icc = auto()
msvc = auto()
@unique
class TrafficGeneratorType(StrEnum):
SCAPY = auto()
2023-09-13 12:21:49 +00:00
# Slots enables some optimizations, by pre-allocating space for the defined
# attributes in the underlying data structure.
#
# Frozen makes the object immutable. This enables further optimizations,
# and makes it thread safe should we every want to move in that direction.
2025-01-10 11:50:43 +00:00
@dataclass(slots=True, frozen=True)
class HugepageConfiguration:
amount: int
force_first_numa: bool
@dataclass(slots=True, frozen=True)
class PortConfig:
node: str
pci: str
os_driver_for_dpdk: str
os_driver: str
peer_node: str
peer_pci: str
@staticmethod
def from_dict(node: str, d: dict) -> "PortConfig":
return PortConfig(node=node, **d)
@dataclass(slots=True, frozen=True)
class TrafficGeneratorConfig:
traffic_generator_type: TrafficGeneratorType
@staticmethod
def from_dict(d: dict):
# This looks useless now, but is designed to allow expansion to traffic
# generators that require more configuration later.
match TrafficGeneratorType(d["type"]):
case TrafficGeneratorType.SCAPY:
return ScapyTrafficGeneratorConfig(
traffic_generator_type=TrafficGeneratorType.SCAPY
)
@dataclass(slots=True, frozen=True)
class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig):
pass
2023-09-13 12:21:49 +00:00
@dataclass(slots=True, frozen=True)
class NodeConfiguration:
name: str
hostname: str
user: str
password: str | None
2025-01-10 11:50:43 +00:00
arch: Architecture
os: OS
lcores: str
use_first_core: bool
hugepages: HugepageConfiguration | None
ports: list[PortConfig]
2023-09-13 12:21:49 +00:00
@staticmethod
2025-01-10 11:50:43 +00:00
def from_dict(d: dict) -> Union["SutNodeConfiguration", "TGNodeConfiguration"]:
hugepage_config = d.get("hugepages")
if hugepage_config:
if "force_first_numa" not in hugepage_config:
hugepage_config["force_first_numa"] = False
hugepage_config = HugepageConfiguration(**hugepage_config)
common_config = {
"name": d["name"],
"hostname": d["hostname"],
"user": d["user"],
"password": d.get("password"),
"arch": Architecture(d["arch"]),
"os": OS(d["os"]),
"lcores": d.get("lcores", "1"),
"use_first_core": d.get("use_first_core", False),
"hugepages": hugepage_config,
"ports": [PortConfig.from_dict(d["name"], port) for port in d["ports"]],
}
if "traffic_generator" in d:
return TGNodeConfiguration(
traffic_generator=TrafficGeneratorConfig.from_dict(d["traffic_generator"]),
**common_config,
)
else:
return SutNodeConfiguration(
memory_channels=d.get("memory_channels", 1), **common_config
)
@dataclass(slots=True, frozen=True)
class SutNodeConfiguration(NodeConfiguration):
memory_channels: int
@dataclass(slots=True, frozen=True)
class TGNodeConfiguration(NodeConfiguration):
traffic_generator: ScapyTrafficGeneratorConfig
@dataclass(slots=True, frozen=True)
class NodeInfo:
"""Class to hold important versions within the node.
This class, unlike the NodeConfiguration class, cannot be generated at the start.
This is because we need to initialize a connection with the node before we can
collect the information needed in this class. Therefore, it cannot be a part of
the configuration class above.
"""
os_name: str
os_version: str
kernel_version: str
@dataclass(slots=True, frozen=True)
class BuildTargetConfiguration:
arch: Architecture
os: OS
cpu: CPUType
compiler: Compiler
compiler_wrapper: str
name: str
@staticmethod
def from_dict(d: dict) -> "BuildTargetConfiguration":
return BuildTargetConfiguration(
arch=Architecture(d["arch"]),
os=OS(d["os"]),
cpu=CPUType(d["cpu"]),
compiler=Compiler(d["compiler"]),
compiler_wrapper=d.get("compiler_wrapper", ""),
name=f"{d['arch']}-{d['os']}-{d['cpu']}-{d['compiler']}",
2023-09-13 12:21:49 +00:00
)
2025-01-10 11:50:43 +00:00
@dataclass(slots=True, frozen=True)
class BuildTargetInfo:
"""Class to hold important versions within the build target.
This is very similar to the NodeInfo class, it just instead holds information
for the build target.
"""
dpdk_version: str
compiler_version: str
class TestSuiteConfigDict(TypedDict):
suite: str
cases: list[str]
@dataclass(slots=True, frozen=True)
class TestSuiteConfig:
test_suite: str
test_cases: list[str]
@staticmethod
def from_dict(
entry: str | TestSuiteConfigDict,
) -> "TestSuiteConfig":
if isinstance(entry, str):
return TestSuiteConfig(test_suite=entry, test_cases=[])
elif isinstance(entry, dict):
return TestSuiteConfig(test_suite=entry["suite"], test_cases=entry["cases"])
else:
raise TypeError(f"{type(entry)} is not valid for a test suite config.")
2023-09-13 12:21:49 +00:00
@dataclass(slots=True, frozen=True)
class ExecutionConfiguration:
2025-01-10 11:50:43 +00:00
build_targets: list[BuildTargetConfiguration]
perf: bool
func: bool
test_suites: list[TestSuiteConfig]
system_under_test_node: SutNodeConfiguration
traffic_generator_node: TGNodeConfiguration
vdevs: list[str]
skip_smoke_tests: bool
2023-09-13 12:21:49 +00:00
@staticmethod
2025-01-10 11:50:43 +00:00
def from_dict(
d: dict, node_map: dict[str, Union[SutNodeConfiguration | TGNodeConfiguration]]
) -> "ExecutionConfiguration":
build_targets: list[BuildTargetConfiguration] = list(
map(BuildTargetConfiguration.from_dict, d["build_targets"])
)
test_suites: list[TestSuiteConfig] = list(map(TestSuiteConfig.from_dict, d["test_suites"]))
sut_name = d["system_under_test_node"]["node_name"]
skip_smoke_tests = d.get("skip_smoke_tests", False)
2023-09-13 12:21:49 +00:00
assert sut_name in node_map, f"Unknown SUT {sut_name} in execution {d}"
2025-01-10 11:50:43 +00:00
system_under_test_node = node_map[sut_name]
assert isinstance(
system_under_test_node, SutNodeConfiguration
), f"Invalid SUT configuration {system_under_test_node}"
tg_name = d["traffic_generator_node"]
assert tg_name in node_map, f"Unknown TG {tg_name} in execution {d}"
traffic_generator_node = node_map[tg_name]
assert isinstance(
traffic_generator_node, TGNodeConfiguration
), f"Invalid TG configuration {traffic_generator_node}"
2023-09-13 12:21:49 +00:00
2025-01-10 11:50:43 +00:00
vdevs = (
d["system_under_test_node"]["vdevs"] if "vdevs" in d["system_under_test_node"] else []
)
2023-09-13 12:21:49 +00:00
return ExecutionConfiguration(
2025-01-10 11:50:43 +00:00
build_targets=build_targets,
perf=d["perf"],
func=d["func"],
skip_smoke_tests=skip_smoke_tests,
test_suites=test_suites,
system_under_test_node=system_under_test_node,
traffic_generator_node=traffic_generator_node,
vdevs=vdevs,
2023-09-13 12:21:49 +00:00
)
@dataclass(slots=True, frozen=True)
class Configuration:
executions: list[ExecutionConfiguration]
@staticmethod
def from_dict(d: dict) -> "Configuration":
2025-01-10 11:50:43 +00:00
nodes: list[Union[SutNodeConfiguration | TGNodeConfiguration]] = list(
2023-09-13 12:21:49 +00:00
map(NodeConfiguration.from_dict, d["nodes"])
)
assert len(nodes) > 0, "There must be a node to test"
node_map = {node.name: node for node in nodes}
assert len(nodes) == len(node_map), "Duplicate node names are not allowed"
executions: list[ExecutionConfiguration] = list(
2025-01-10 11:50:43 +00:00
map(ExecutionConfiguration.from_dict, d["executions"], [node_map for _ in d])
2023-09-13 12:21:49 +00:00
)
return Configuration(executions=executions)
def load_config() -> Configuration:
"""
Loads the configuration file and the configuration file schema,
validates the configuration file, and creates a configuration object.
"""
with open(SETTINGS.config_file_path, "r") as f:
config_data = yaml.safe_load(f)
2025-01-10 11:50:43 +00:00
schema_path = os.path.join(pathlib.Path(__file__).parent.resolve(), "conf_yaml_schema.json")
2023-09-13 12:21:49 +00:00
with open(schema_path, "r") as f:
schema = json.load(f)
config: dict[str, Any] = warlock.model_factory(schema, name="_Config")(config_data)
config_obj: Configuration = Configuration.from_dict(dict(config))
return config_obj
CONFIGURATION = load_config()