diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..9696b38 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,25 @@ +# .github/release.yml + +changelog: + exclude: + labels: + - ignore-for-release + categories: + - title: 💥 Breaking Changes + labels: + - breaking-change + - title: 🎉 Exciting New Features + labels: + - feature + - title: 🎨 Improvements + labels: + - improvement + - title: 🐛 Bug Fixes + labels: + - bug + - title: 📝 Documentation + labels: + - documentation + - title: Other Changes + labels: + - "*" \ No newline at end of file diff --git a/plugin2544/meta.yml b/plugin2544/meta.yml index c0e47bd..aee0c54 100644 --- a/plugin2544/meta.yml +++ b/plugin2544/meta.yml @@ -1,5 +1,5 @@ name: "RFC-2544" -version: "1.0.0" +version: "1.0.2" core_version: ">=1.0.0" author: - "MC" diff --git a/plugin2544/plugin/learning.py b/plugin2544/plugin/learning.py index 9318dfd..04e52c2 100644 --- a/plugin2544/plugin/learning.py +++ b/plugin2544/plugin/learning.py @@ -59,7 +59,7 @@ def get_bytes_from_macaddress(dmac: "MacAddress") -> Iterator[str]: def get_link_local_uci_ipv6address(dmac: "MacAddress") -> str: b = get_bytes_from_macaddress(dmac) - return f"FE80000000000000{int(next(b)) | 2 }{next(b)}{next(b)}FFFE{next(b)}{next(b)}{next(b)}" + return f"FE80000000000000{int(next(b), 16) | 2 }{next(b)}{next(b)}FFFE{next(b)}{next(b)}{next(b)}"[:-1] def get_address_list( @@ -125,7 +125,7 @@ async def get_address_learning_packet( packet = NDPPacket( smac=smac, source_ip=IPv6Address(source_ip), - destination_ip=IPv6Address(destination_ip), + destination_ip=IPv6Address(int(destination_ip, 16)), dmac=dmac, ).make_ndp_packet() packet_list.append(packet) diff --git a/plugin2544/plugin/structure.py b/plugin2544/plugin/structure.py index 6fdad6f..2910f1d 100644 --- a/plugin2544/plugin/structure.py +++ b/plugin2544/plugin/structure.py @@ -13,7 +13,6 @@ from .stream_struct import StreamStruct from ..utils import exceptions, constants as const from ..utils.field import MacAddress, NonNegativeDecimal - if TYPE_CHECKING: from xoa_core.core.test_suites.datasets import PortIdentity from xoa_driver import ports as xoa_ports, testers as xoa_testers @@ -108,7 +107,9 @@ async def set_broadr_reach_mode(self, broadr_reach_mode: const.BRRModeStr) -> No await self.port_ins.brr_mode.set(broadr_reach_mode.to_xmp()) async def set_mdi_mdix_mode(self, mdi_mdix_mode: const.MdiMdixMode) -> None: - if self.port_ins.info.capabilities.can_mdi_mdix == enums.YesNo.NO: + is_port_can_mdi_mdix = self.port_ins.info.capabilities.can_mdi_mdix == enums.YesNo.YES + is_port_can_set_speed = self.port_ins.info.port_possible_speed_modes + if not is_port_can_mdi_mdix or (is_port_can_mdi_mdix and not is_port_can_set_speed): self._xoa_out.send_warning( exceptions.MdiMdixModeNotSupport(self._port_identity.name) ) diff --git a/plugin2889/__init__.py b/plugin2889/__init__.py index e69de29..0ac3d8b 100644 --- a/plugin2889/__init__.py +++ b/plugin2889/__init__.py @@ -0,0 +1,85 @@ +import hashlib +import traceback +from pathlib import Path +from typing import TYPE_CHECKING +from xoa_core.types import PluginAbstract + +if TYPE_CHECKING: + from plugin2889.dataset import TestSuiteConfiguration2889 + +from plugin2889.plugin.dataset import TestSuiteDataSharing +from plugin2889.const import TestType +from plugin2889.util.logger import logger +from plugin2889.plugin.test_abstract import PluginParameter +from plugin2889.plugin.test_rate import RateTest +from plugin2889.plugin.test_congestion_control import CongestionControlTest +from plugin2889.plugin.test_forward_pressure import ForwardPressureTest +from plugin2889.plugin.test_max_forwarding_rate import MaxForwardingRateTest +from plugin2889.plugin.test_address_caching_capacity import AddressCachingCapacityTest +from plugin2889.plugin.test_address_learning_rate import AddressLearningRateTest +from plugin2889.plugin.test_errored_frames_filtering import ErroredFramesFilteringTest +from plugin2889.plugin.test_broadcast_forwarding import BroadcastForwardingTest + + +TEST_TYPE_CLASS = { + TestType.RATE_TEST: RateTest, + TestType.CONGESTION_CONTROL: CongestionControlTest, + TestType.FORWARD_PRESSURE: ForwardPressureTest, + TestType.MAX_FORWARDING_RATE: MaxForwardingRateTest, + TestType.ADDRESS_CACHING_CAPACITY: AddressCachingCapacityTest, + TestType.ADDRESS_LEARNING_RATE: AddressLearningRateTest, + TestType.ERRORED_FRAMES_FILTERING: ErroredFramesFilteringTest, + TestType.BROADCAST_FORWARDING: BroadcastForwardingTest, +} + +TEST_ERROR_PATH = Path().resolve() / 'test_error' + + +class TestSuite2889(PluginAbstract["TestSuiteConfiguration2889"]): + def prepare(self) -> None: + pass + + async def __do_test(self) -> None: + plugin_params = PluginParameter( + testers=self.testers, + port_identities=self.port_identities, + xoa_out=self.xoa_out, + full_test_config=self.cfg, + data_sharing=TestSuiteDataSharing(), + state_conditions=self.state_conditions, + ) + for test_suit_config in self.cfg.enabled_test_suit_config_list: + test_suit_class = TEST_TYPE_CLASS[test_suit_config.test_type] + logger.debug(f"init {test_suit_class}") + await test_suit_class(plugin_params, test_suit_config).start() + + async def __post_test(self) -> None: + logger.info("test finish") + + async def start(self) -> None: + await self.__do_test() + await self.__post_test() + + + + + + +class TestSuite2889Testing(TestSuite2889): + def get_error_id(self, tb_exc: str) -> str: + return hashlib.md5(tb_exc.encode('utf-8')).hexdigest() + + async def start(self) -> None: + TEST_ERROR_PATH.mkdir(exist_ok=True) + try: + await super().start() + except Exception: + tb_exc = traceback.format_exc() + error_id = self.get_error_id(tb_exc) + current_error_path = TEST_ERROR_PATH / error_id + current_error_path.mkdir(exist_ok=True) + with open(current_error_path / 'traceback.txt', 'w') as error_log: + traceback.print_exc(file=error_log) + self.xoa_out.send_statistics({ + 'error_id': error_id + }) \ No newline at end of file diff --git a/plugin2889/const.py b/plugin2889/const.py index bac3b38..00d3890 100644 --- a/plugin2889/const.py +++ b/plugin2889/const.py @@ -13,12 +13,15 @@ DELAY_LEARNING_MAC = 1 DELAY_LEARNING_ADDRESS = 1 DELAY_CREATE_PORT_PAIR = 3 +DELAY_WAIT_RESET_PORT = 5 +DELAY_WAIT_RESET_STATS = 2 INTERVAL_CHECK_SHOULD_STOP_TRAFFIC = 0.01 INTERVAL_CHECK_PORT_SYNC = 1 INTERVAL_CHECK_PORT_RESERVE = 0.5 INTERVAL_CLEAR_STATISTICS = 0.01 INTERVAL_INJECT_FCS_ERROR = 0.2 +CHECK_SYNC_MAX_RETRY = 30 # https://en.wikipedia.org/wiki/Ethernet_frame # 20 = Preamble + Start frame delimiter + Interpacket gap @@ -27,6 +30,34 @@ DECIMAL_100 = Decimal(100) WAIT_SYNC_STATE_TIMEOUT = 30 INVALID_PORT_ROLE = 'invalid port role' +DEFAULT_MIXED_PACKET_SIZE = ( + 56, + 60, + 64, + 70, + 78, + 92, + 256, + 496, + 512, + 570, + 576, + 594, + 1438, + 1518, + 9216, + 16360, +) +DEFAULT_IETF_PACKET_SIZE = ( + 64, + 128, + 256, + 512, + 1024, + 1280, + 1518, +) +MIXED_DEFAULT_WEIGHTS = (0, 0, 0, 0, 57, 3, 5, 1, 2, 5, 1, 4, 4, 18, 0, 0) class Enum(CaseSensitiveEnum): @@ -34,7 +65,7 @@ class Enum(CaseSensitiveEnum): def _missing_(cls, value): if isinstance(value, str): for member in cls: - if member.value == value.lower(): + if member.name == value: return member @@ -156,8 +187,8 @@ def is_destination(self) -> bool: class PortRateCapProfile(Enum): - PHYSICAL = "physical_port_rate" - CUSTOM = "custom_rate_cap" + PHYSICAL_PORT_RATE = "physical_port_rate" + CUSTOM = "custom" @property def is_custom(self) -> bool: @@ -189,9 +220,9 @@ class StreamRateType(Enum): class PortRateCapUnitInt(Enum): - GBPS = 1e9 - MBPS = 1e6 - KBPS = 1e3 + FIELD_1E9_BPS = 1e9 + FIELD_1E6_BPS = 1e6 + FIELD_1E3_BPS = 1e3 BPS = 1 @@ -280,7 +311,7 @@ def is_pair_topology(self) -> bool: return self == type(self).PAIRS -class LatencyModeStr(Enum): +class LatencyMode(Enum): FIRST2LAST = "first_to_last" LAST2LAST = "last_to_last" FIRST2FIRST = "first_to_first" @@ -310,9 +341,9 @@ def is_config_scope(self) -> bool: class FECModeStr(Enum): - ON = "on" - OFF = "off" - FC_FEC = "fc_fec" + ON = "ON" + OFF = "OFF" + FC_FEC = "FIRECODE" def to_xmp(self) -> "enums.FECMode": return enums.FECMode[self.name] @@ -320,7 +351,7 @@ def to_xmp(self) -> "enums.FECMode": class PacketSizeType(Enum): IETF_DEFAULT = "ietf_default" - CUSTOM = "custom_sizes" + CUSTOM_SIZES = "custom_sizes" RANGE = "specified" INCREMENTING = "incrementing" BUTTERFLY = "butterfly" @@ -329,7 +360,7 @@ class PacketSizeType(Enum): @property def is_custom(self) -> bool: - return self == type(self).CUSTOM + return self == type(self).CUSTOM_SIZES @property def is_mix(self) -> bool: @@ -337,7 +368,7 @@ def is_mix(self) -> bool: @property def is_fix(self) -> bool: - return self in [type(self).IETF_DEFAULT, type(self).CUSTOM, type(self).RANGE] + return self in [type(self).IETF_DEFAULT, type(self).CUSTOM_SIZES, type(self).RANGE] def to_xmp(self): if self.is_fix: diff --git a/plugin2889/dataset.py b/plugin2889/dataset.py index 3ad4c12..889b0b9 100644 --- a/plugin2889/dataset.py +++ b/plugin2889/dataset.py @@ -11,12 +11,500 @@ IPv4Network, IPv6Network, ) -from xoa_driver import ports +from abc import ABC, abstractmethod +from decimal import Decimal +from collections import defaultdict +from dataclasses import dataclass, field +from ipaddress import IPv4Network, IPv6Network +from typing import ( + TYPE_CHECKING, + Any, + Generator, + Iterable, + List, + Optional, + Union, + Dict, +) from pydantic import ( BaseModel, + NonNegativeInt, + Field, + validator, ) -from plugin2889.model.protocol_segment import BinaryString -from . import const +from xoa_driver import ports + +from plugin2889.model import exceptions +from plugin2889 import const +from plugin2889.model.protocol_segment import BinaryString, ProtocolSegmentProfileConfig +from plugin2889.const import ( + DEFAULT_IETF_PACKET_SIZE, + DEFAULT_MIXED_PACKET_SIZE, + INVALID_PORT_ROLE, + MIXED_DEFAULT_WEIGHTS, + BRRModeStr, + IPVersion, + LearningPortDMacMode, + LearningSequencePortDMacMode, + PortGroup, + PortRateCapProfile, + TestPortMacMode, + FECModeStr, + MdiMdixMode, + PacketSizeType, + PortRateCapUnit, + PortSpeedStr, + TestType, + StreamRateType, + DurationTimeUnit, + TestTopology, + LatencyMode, + TidAllocationScope, + TrafficDirection, +) + +if TYPE_CHECKING: + from xoa_driver.ports import GenericL23Port + + +class NewRateSweepOptions(BaseModel): + start_value: Decimal + end_value: Decimal + step_value: Decimal + + @validator("start_value", "end_value", "step_value", allow_reuse=True) + def to_decimal(cls, v): + return Decimal(v) + + +class RateIterationOptions(BaseModel): + initial_value: float + minimum_value: float + maximum_value: float + value_resolution: float + use_pass_threshold: bool + pass_threshold: float + + +class PortRoleConfig(BaseModel): + is_used: bool + role: PortGroup + peer_port_id: str + + +class PortRoleCounter(BaseModel): + enabled: int = 0 + by_roles: Dict[PortGroup, int] = {} + + def read(self, role: PortGroup) -> int: + return self.by_roles.get(role, 0) + + +class PortRoleHandler(BaseModel): + role_map: Dict[str, PortRoleConfig] # key is guid_{uuid} "guid_fed2f488-a81e-4bbd-9eaa-16b10748ba33" + + @property + def used_port_count(self) -> int: + return sum(int(port.is_used) for port in self.role_map.values()) + + @property + def role_counter(self) -> "PortRoleCounter": + counter = PortRoleCounter() + for port in self.role_map.values(): + if port.is_used: + counter.enabled += 1 + current = counter.by_roles.get(port.role, 0) + counter.by_roles[port.role] = current + 1 + return counter + + +class TestCaseBaseConfiguration(ABC, BaseModel): + enabled: bool + topology: Optional[TestTopology] = None + direction: Optional[TrafficDirection] = None + rate_iteration_options: Optional[RateIterationOptions] = None + rate_sweep_options: Optional[NewRateSweepOptions] = None + port_role_handler: Optional[PortRoleHandler] + duration: int + duration_time_unit: DurationTimeUnit + iterations: int + item_id: str + label: str + + def check_src_dest_port_roles(self, require_src_ports: int, require_dest_ports: int) -> None: + assert self.port_role_handler, INVALID_PORT_ROLE + if self.port_role_handler.role_counter.enabled != require_src_ports + require_dest_ports: + raise exceptions.PortRoleEnabledNotEnough(require_src_ports + require_dest_ports) + + if self.port_role_handler.role_counter.read(PortGroup.SOURCE) != require_src_ports: + raise exceptions.PortRoleNotEnough('source', require_src_ports) + + if self.port_role_handler.role_counter.read(PortGroup.DESTINATION) != require_dest_ports: + raise exceptions.PortRoleNotEnough('destination', require_src_ports) + + def check_address_test_port_roles(self) -> None: + assert self.port_role_handler, INVALID_PORT_ROLE + if self.port_role_handler.role_counter.enabled != 3: + raise exceptions.PortRoleEnabledNotEnough(3) + + for role in (PortGroup.LEARNING_PORT, PortGroup.MONITORING_PORT, PortGroup.TEST_PORT): + if self.port_role_handler.role_counter.read(role) != 1: + raise exceptions.PortRoleNotEnough(role.value, 1) + + @abstractmethod + def check_configuration(self) -> None: + raise NotImplementedError(type(self).__name__) + + +class RateSubTestConfiguration(TestCaseBaseConfiguration): + topology: TestTopology + direction: TrafficDirection + test_type: TestType = TestType.RATE_TEST + throughput_test_enabled: bool + forwarding_test_enabled: bool + + def check_configuration(self) -> None: + pass + + +class RateTestConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.RATE_TEST + sub_test: List[RateSubTestConfiguration] + + def check_port_roles(self, sub_test: RateSubTestConfiguration) -> None: + assert sub_test.port_role_handler, INVALID_PORT_ROLE + seen_east = seen_west = False + for port in sub_test.port_role_handler.role_map.values(): + if not port.is_used: + continue + if port.role.is_east: + seen_east = True + elif port.role.is_west: + seen_west = True + elif port.role.is_undefined: + raise exceptions.RateTestPortRoleUndefined() + + if sub_test.topology.is_pair_topology and not port.peer_port_id: + raise exceptions.RateTestPortRoleEmptyPair() + + if not (seen_east and seen_west): + raise exceptions.RateTestPortRoleEmptyGroupRole() + + def check_configuration(self) -> None: + for test in self.sub_test: + if not test.enabled: + continue + if not (test.forwarding_test_enabled or test.throughput_test_enabled): + raise exceptions.RateTestEmptySubTest() + if not test.port_role_handler or (test.port_role_handler and test.port_role_handler.used_port_count < 2): + raise exceptions.RateTestPortConfigNotEnough() + if not test.topology.is_mesh_topology: + self.check_port_roles(test) + + +class CongestionControlConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.CONGESTION_CONTROL + + def check_configuration(self) -> None: + self.check_src_dest_port_roles(2, 2) + + +class ForwardPressureConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.FORWARD_PRESSURE + interframe_gap_delta: float + acceptable_rx_max_util_delta: float + + def check_configuration(self) -> None: + self.check_src_dest_port_roles(1, 1) + + +class MaxForwardingRateConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.MAX_FORWARDING_RATE + use_throughput_as_start_value: bool + + def check_configuration(self) -> None: + self.check_src_dest_port_roles(1, 1) + + +class AddressCachingCapacityConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.ADDRESS_CACHING_CAPACITY + address_iteration_options: RateIterationOptions + learn_mac_base_address: str + test_port_mac_mode: TestPortMacMode + learning_port_dmac_mode: LearningPortDMacMode + learning_sequence_port_dmac_mode: LearningSequencePortDMacMode + learning_rate_fps: float + toggle_sync_state: bool + sync_off_duration: int + sync_on_duration: int + switch_test_port_roles: bool + dut_aging_time: int + fast_run_resolution_enabled: bool + + def check_configuration(self) -> None: + self.check_address_test_port_roles() + + +class AddressLearningRateConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.ADDRESS_LEARNING_RATE + address_sweep_options: NewRateSweepOptions + rate_iteration_options: RateIterationOptions + learn_mac_base_address: str + test_port_mac_mode: TestPortMacMode + learning_port_dmac_mode: LearningPortDMacMode + learning_sequence_port_dmac_mode: LearningSequencePortDMacMode + learning_rate_fps: float + toggle_sync_state: bool + sync_off_duration: int + sync_on_duration: int + switch_test_port_roles: bool + dut_aging_time: int + only_use_capacity: bool + set_end_address_to_capacity: bool + + def check_configuration(self) -> None: + self.check_address_test_port_roles() + + +class ErroredFramesFilteringConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.ERRORED_FRAMES_FILTERING + rate_sweep_options: NewRateSweepOptions + oversize_test_enabled: bool + max_frame_size: int + oversize_span: int + min_frame_size: int + undersize_span: int + + def check_configuration(self) -> None: + self.check_src_dest_port_roles(1, 1) + + +class BroadcastForwardingConfiguration(TestCaseBaseConfiguration): + test_type: TestType = TestType.BROADCAST_FORWARDING + rate_iteration_options: RateIterationOptions + + def check_configuration(self) -> None: + assert self.port_role_handler, INVALID_PORT_ROLE + if self.port_role_handler.role_counter.enabled < 2: + raise exceptions.PortConfigNotEnough(2) + + if self.port_role_handler.role_counter.read(PortGroup.SOURCE) != 1: + raise exceptions.PortConfigNotMatchExactly(PortGroup.SOURCE.value, 1) + + if self.port_role_handler.role_counter.read(PortGroup.DESTINATION) < 1: + raise exceptions.PortRoleNotEnoughAtLeast(PortGroup.DESTINATION.value, 1) + + +UnionTestSuitConfiguration = Union[ + RateTestConfiguration, + RateSubTestConfiguration, + CongestionControlConfiguration, + ForwardPressureConfiguration, + MaxForwardingRateConfiguration, + AddressCachingCapacityConfiguration, + AddressLearningRateConfiguration, + ErroredFramesFilteringConfiguration, + BroadcastForwardingConfiguration, + + +] + + +class TestSuitesConfiguration(BaseModel): + class Config: + arbitrary_types_allowed = True + validate_assignment = True + + rate_test: RateTestConfiguration + congestion_control: CongestionControlConfiguration + forward_pressure: ForwardPressureConfiguration + max_forwarding_rate: MaxForwardingRateConfiguration + address_caching_capacity: AddressCachingCapacityConfiguration + address_learning_rate: AddressLearningRateConfiguration + errored_frames_filtering: ErroredFramesFilteringConfiguration + broadcast_forwarding: BroadcastForwardingConfiguration + + def __init__(self, **data: Any): + super().__init__(**data) + for test_name in self.__fields__: + test_config = getattr(self, test_name) + if not test_config.enabled: + continue + + test_config.check_configuration() + + +class RateDefinition(BaseModel): + rate_type: StreamRateType + rate_fraction: float + rate_pps: float + rate_bps_l1: float + rate_bps_l1_unit: PortRateCapUnit + rate_bps_l2: float + rate_bps_l2_unit: PortRateCapUnit + + @ property + def is_fraction(self): + return self.rate_type == StreamRateType.FRACTION + + @ property + def is_pps(self): + return self.rate_type == StreamRateType.PPS + + @ property + def is_l1bps(self): + return self.rate_type == StreamRateType.L1BPS + + @ property + def is_l2bps(self): + return self.rate_type == StreamRateType.L2BPS + + +@ dataclass +class TPLDIDController: + tid_allocation_scope: TidAllocationScope + current_rx_port_tid: Dict["GenericL23Port", int] = field(default_factory=lambda: defaultdict(lambda: 0)) + current_tid: int = 0 + next_tid: int = 0 + + def alloc_new_tpld_id(self, source_port: "GenericL23Port", destination_port: "GenericL23Port") -> int: + if self.tid_allocation_scope == TidAllocationScope.CONFIGURATION_SCOPE: + self.next_tid = self.current_tid + self.current_tid += 1 + elif self.tid_allocation_scope == TidAllocationScope.RX_PORT_SCOPE: + self.next_tid = self.current_rx_port_tid[destination_port] + self.current_rx_port_tid[destination_port] += 1 + elif self.tid_allocation_scope == TidAllocationScope.SOURCE_PORT_ID: + self.next_tid = source_port.kind.port_id + + if self.next_tid > source_port.info.capabilities.max_tpld_stats: + exceptions.TPLDIDExceed(self.next_tid, source_port.info.capabilities.max_tpld_stats) + return self.next_tid + + +class FrameSizesOptions(BaseModel): + class Config: + allow_population_by_field_name = True + + field_0: NonNegativeInt = Field(56, alias="0") + field_1: NonNegativeInt = Field(60, alias="1") + field_14: NonNegativeInt = Field(9216, alias="14") + field_15: NonNegativeInt = Field(16360, alias="15") + + @ property + def dictionary(self) -> Dict[int, NonNegativeInt]: + return { + 0: self.field_0, + 1: self.field_1, + 14: self.field_14, + 15: self.field_15, + } + + +class FrameSizeConfiguration(BaseModel): + # FrameSizes + packet_size_type: PacketSizeType + # FixedSizesPerTrial + custom_packet_sizes: List[NonNegativeInt] + fixed_packet_start_size: NonNegativeInt + fixed_packet_end_size: NonNegativeInt + fixed_packet_step_size: NonNegativeInt + # VaryingSizesPerTrial + varying_packet_min_size: NonNegativeInt + varying_packet_max_size: NonNegativeInt + mixed_sizes_weights: List[NonNegativeInt] + mixed_length_config: FrameSizesOptions + + def check_mixed_weights_valid(self) -> None: + if self.packet_size_type == PacketSizeType.MIX: + if len(self.mixed_sizes_weights) != len(MIXED_DEFAULT_WEIGHTS): + raise exceptions.MixWeightsNotEnough(len(MIXED_DEFAULT_WEIGHTS)) + if sum(self.mixed_sizes_weights) != 100: + raise exceptions.MixWeightsSumError(sum(self.mixed_sizes_weights)) + + @property + def mixed_packet_length(self) -> List[int]: + mix_size_lengths = self.mixed_length_config.dict() + return [ + DEFAULT_MIXED_PACKET_SIZE[index] + if not (mix_size_lengths.get(f"field_{index}", 0)) + else mix_size_lengths.get(f"field_{index}", 0) + for index in range(len(DEFAULT_MIXED_PACKET_SIZE)) + ] + + @property + def mixed_average_packet_size(self) -> int: + weighted_size = 0.0 + for index, size in enumerate(self.mixed_packet_length): + weight = self.mixed_sizes_weights[index] + weighted_size += size * weight + return int(round(weighted_size / 100.0)) + + @property + def packet_size_list(self) -> Iterable[int]: + packet_size_type = self.packet_size_type + if packet_size_type == PacketSizeType.IETF_DEFAULT: + return DEFAULT_IETF_PACKET_SIZE + elif packet_size_type == PacketSizeType.CUSTOM_SIZES: + return list(sorted(self.custom_packet_sizes)) + elif packet_size_type == PacketSizeType.MIX: + return [self.mixed_average_packet_size] + + elif packet_size_type == PacketSizeType.RANGE: + return list(range( + self.fixed_packet_start_size, + self.fixed_packet_end_size + self.fixed_packet_step_size, + self.fixed_packet_step_size, + )) + + elif packet_size_type in (PacketSizeType.INCREMENTING, PacketSizeType.BUTTERFLY, PacketSizeType.RANDOM): + return [(self.varying_packet_min_size + self.varying_packet_max_size) // 2] + else: + raise ValueError(packet_size_type.value) + + def __init__(self, **data: Any): + super().__init__(**data) + self.check_mixed_weights_valid() + + +class GeneralTestConfiguration(BaseModel): + frame_sizes: FrameSizeConfiguration + rate_definition: RateDefinition + latency_mode: LatencyMode + toggle_sync_state: bool + sync_off_duration: int + sync_on_duration: int + should_stop_on_los: bool + port_reset_delay: int + use_port_sync_start: bool + port_stagger_steps: int + use_micro_tpld_on_demand: bool + tid_allocation_scope: TidAllocationScope + tpld_id_controller: Any = None + + def __init__(self, **data: Any): + super().__init__(**data) + self.tpld_id_controller = TPLDIDController(self.tid_allocation_scope) + + def alloc_new_tpld_id(self, source_port, destination_port) -> int: + assert self.tpld_id_controller + return self.tpld_id_controller.alloc_new_tpld_id(source_port, destination_port) + + + +class PortIdentity(BaseModel): + tester_id: str + chassis_id: str + module_index: NonNegativeInt + port_index: NonNegativeInt + + @property + def name(self) -> str: + return f"P-{self.tester_id}-{self.module_index}-{self.port_index}" + + @property + def identity(self) -> str: + return f"{self.tester_id}-{self.module_index}-{self.port_index}" def hex_string_to_binary_string(hex: str) -> "BinaryString": @@ -39,7 +527,7 @@ def partial_replace(self, new_mac_address: "MacAddress"): @classmethod def from_base_address(cls, base_address: str): prefix = [hex(int(i)) for i in base_address.split(",")] - return cls(":".join([p.replace("0x", "").zfill(2).upper() for p in prefix])) + return cls("".join([p.replace("0x", "").zfill(2).upper() for p in prefix])) @property def is_empty(self) -> bool: @@ -214,6 +702,11 @@ def network(self, prefix: int) -> IPv6Network: def to_binary_string(self) -> "BinaryString": return hex_string_to_binary_string(self.to_hexstring()) +class Prefix(int): + def to_ipv4(self) -> IPv4Address: + return IPv4Address(int(self * "1" + (32 - self) * "0", 2)) + + @dataclass class AddressCollection: @@ -223,3 +716,140 @@ class AddressCollection: dst_ipv4_addr: IPv4Address src_ipv6_addr: IPv6Address dst_ipv6_addr: IPv6Address + +class IPV6AddressProperties(BaseModel): + address: IPv6Address + routing_prefix: Prefix = Prefix(24) + public_address: IPv6Address + public_routing_prefix: Prefix = Prefix(24) + gateway: IPv6Address + remote_loop_address: IPv6Address + ip_version: IPVersion = IPVersion.IPV6 + + @property + def network(self) -> IPv6Network: + return IPv6Network(f"{self.address}/{self.routing_prefix}", strict=False) + + @validator("address", "public_address", "gateway", "remote_loop_address", pre=True, allow_reuse=True) + def set_address(cls, v) -> IPv6Address: + return IPv6Address(v) + + @validator("routing_prefix", "public_routing_prefix", pre=True, allow_reuse=True) + def set_prefix(cls, v) -> Prefix: + return Prefix(v) + + @property + def dst_addr(self): + return self.public_address if not self.public_address.is_empty else self.address + + +class IPV4AddressProperties(BaseModel): + address: IPv4Address + routing_prefix: Prefix = Prefix(24) + public_address: IPv4Address + public_routing_prefix: Prefix = Prefix(24) + gateway: IPv4Address + remote_loop_address: IPv4Address + ip_version: IPVersion = IPVersion.IPV4 + + @property + def network(self) -> "IPv4Network": + return IPv4Network(f"{self.address}/{self.routing_prefix}", strict=False) + + @staticmethod + def is_ip_zero(ip_address: IPv4Address) -> bool: + return ip_address == IPv4Address("0.0.0.0") or (not ip_address) + + @validator("address", "public_address", "gateway", "remote_loop_address", pre=True, allow_reuse=True) + def set_address(cls, v): + return IPv4Address(v) + + @validator("routing_prefix", "public_routing_prefix", pre=True, allow_reuse=True) + def set_prefix(cls, v): + return Prefix(v) + + @property + def dst_addr(self) -> "IPv4Address": + return self.public_address if not self.public_address.is_empty else self.address + + + + +class PortConfiguration(BaseModel): + port_slot: str + port_config_slot: str = "" + peer_config_slot: str + port_group: PortGroup + port_speed_mode: PortSpeedStr + + # PeerNegotiation + auto_neg_enabled: bool + anlt_enabled: bool + mdi_mdix_mode: MdiMdixMode + broadr_reach_mode: BRRModeStr + + # PortRateCap + port_rate_cap_enabled: bool + port_rate_cap_value: float + port_rate_cap_profile: PortRateCapProfile + port_rate_cap_unit: PortRateCapUnit + + # PhysicalPortProperties + interframe_gap: NonNegativeInt + speed_reduction_ppm: NonNegativeInt + pause_mode_enabled: bool + latency_offset_ms: int # QUESTION: can be negative? + fec_mode: FECModeStr + + profile_id: str + + ip_gateway_mac_address: MacAddress + reply_arp_requests: bool + reply_ping_requests: bool + remote_loop_mac_address: MacAddress + ipv4_properties: IPV4AddressProperties + ipv6_properties: IPV6AddressProperties + item_id: str + + # Computed Properties + is_tx_port: bool = True + is_rx_port: bool = True + port_rate: Decimal = Decimal("0.0") + profile: ProtocolSegmentProfileConfig = ProtocolSegmentProfileConfig() + + @property + def ip_properties(self) -> Union[IPV4AddressProperties, IPV6AddressProperties]: + if self.profile.protocol_version.is_ipv6: + return self.ipv6_properties + return self.ipv4_properties + + class Config: + underscore_attrs_are_private = True + +class TestSuiteConfiguration2889(BaseModel): + ports_configuration: Dict[str, PortConfiguration] + protocol_segments: Dict[str, ProtocolSegmentProfileConfig] + general_test_configuration: GeneralTestConfiguration + test_suites_configuration: TestSuitesConfiguration + + @property + def enabled_test_suit_config_list(self) -> Generator[UnionTestSuitConfiguration, None, None]: + for _, test_type_config in self.test_suites_configuration: + if test_type_config and test_type_config.enabled: + yield test_type_config + + def check_port_config(self) -> None: + if len(self.ports_configuration) < 2: + raise exceptions.PortConfigNotEnough(2) + + def check_tests_enabled(self) -> None: + if len(list(self.enabled_test_suit_config_list)) == 0: + raise exceptions.TestTypeNotEnough() + + def __init__(self, **data: Any): + super().__init__(**data) + self.check_port_config() + self.check_tests_enabled() + + for port_conf in self.ports_configuration.values(): + port_conf.profile = self.protocol_segments[port_conf.profile_id].copy(deep=True) \ No newline at end of file diff --git a/plugin2889/meta.yml b/plugin2889/meta.yml new file mode 100644 index 0000000..e03c74e --- /dev/null +++ b/plugin2889/meta.yml @@ -0,0 +1,7 @@ +name: "RFC-2889" +version: "1.0.3" +core_version: ">=1.0.0" +author: + - "Frank Chen" +entry_object: "TestSuite2889" +data_model: "TestSuiteConfiguration2889" \ No newline at end of file diff --git a/plugin2889/model/protocol_segment.py b/plugin2889/model/protocol_segment.py index ac96a61..664bbf3 100644 --- a/plugin2889/model/protocol_segment.py +++ b/plugin2889/model/protocol_segment.py @@ -1,9 +1,9 @@ import re -from enum import Enum from typing import List, Optional from pydantic import BaseModel from pydantic.class_validators import validator from xoa_driver.enums import ProtocolOption, ModifierAction +from plugin2889.const import Enum class BinaryString(str): diff --git a/plugin2889/model/test_suite.py b/plugin2889/model/test_suite.py index 066924b..47c7d83 100644 --- a/plugin2889/model/test_suite.py +++ b/plugin2889/model/test_suite.py @@ -40,7 +40,7 @@ StreamRateType, DurationTimeUnit, TestTopology, - LatencyModeStr, + LatencyMode, TidAllocationScope, TrafficDirection, ) @@ -448,7 +448,7 @@ def packet_size_list(self) -> Iterable[int]: packet_size_type = self.packet_size_type if packet_size_type == PacketSizeType.IETF_DEFAULT: return DEFAULT_MIXED_PACKET_SIZE - elif packet_size_type == PacketSizeType.CUSTOM: + elif packet_size_type == PacketSizeType.CUSTOM_SIZES: return list(sorted(self.custom_packet_sizes)) elif packet_size_type == PacketSizeType.MIX: return [self.mixed_average_packet_size] @@ -473,7 +473,7 @@ def __init__(self, **data: Any): class GeneralTestConfiguration(BaseModel): frame_sizes: FrameSizeConfiguration rate_definition: RateDefinition - latency_mode: LatencyModeStr + latency_mode: LatencyMode toggle_sync_state: bool sync_off_duration: int sync_on_duration: int @@ -621,39 +621,4 @@ def name(self) -> str: @property def identity(self) -> str: - return f"{self.chassis_index}-{self.module_index}-{self.port_index}" - - -class TestSuiteConfiguration2889(BaseModel): - ports_configuration: Dict[str, PortConfiguration] - protocol_segments: Dict[str, ProtocolSegmentProfileConfig] - general_test_configuration: GeneralTestConfiguration - test_suites_configuration: TestSuitesConfiguration - - @property - def enabled_test_suit_config_list(self) -> Generator[UnionTestSuitConfiguration, None, None]: - for _, test_type_config in self.test_suites_configuration: - if test_type_config and test_type_config.enabled: - yield test_type_config - - def check_port_config(self) -> None: - if len(self.ports_configuration) < 2: - raise exceptions.PortConfigNotEnough(2) - - def check_tests_enabled(self) -> None: - if len(list(self.enabled_test_suit_config_list)) == 0: - raise exceptions.TestTypeNotEnough() - - def __init__(self, **data: Any): - super().__init__(**data) - self.check_port_config() - self.check_tests_enabled() - - for port_conf in self.ports_configuration.values(): - port_conf.profile = self.protocol_segments[port_conf.profile_id].copy(deep=True) - - -class CoreConfiguration(BaseModel): - username: str - port_identities: Dict[str, PortIdentity] - config: TestSuiteConfiguration2889 + return f"{self.chassis_index}-{self.module_index}-{self.port_index}" \ No newline at end of file diff --git a/plugin2889/plugin/__init__.py b/plugin2889/plugin/__init__.py index 1369344..e69de29 100644 --- a/plugin2889/plugin/__init__.py +++ b/plugin2889/plugin/__init__.py @@ -1,56 +0,0 @@ -from typing import TYPE_CHECKING -from xoa_core.types import PluginAbstract - -if TYPE_CHECKING: - from plugin2889.model.test_suite import TestSuiteConfiguration2889 - -from plugin2889.plugin.dataset import TestSuiteDataSharing -from plugin2889.const import TestType -from plugin2889.util.logger import logger -from plugin2889.plugin.test_abstract import PluginParameter -from plugin2889.plugin.test_rate import RateTest -from plugin2889.plugin.test_congestion_control import CongestionControlTest -from plugin2889.plugin.test_forward_pressure import ForwardPressureTest -from plugin2889.plugin.test_max_forwarding_rate import MaxForwardingRateTest -from plugin2889.plugin.test_address_caching_capacity import AddressCachingCapacityTest -from plugin2889.plugin.test_address_learning_rate import AddressLearningRateTest -from plugin2889.plugin.test_errored_frames_filtering import ErroredFramesFilteringTest -from plugin2889.plugin.test_broadcast_forwarding import BroadcastForwardingTest - - -TEST_TYPE_CLASS = { - TestType.RATE_TEST: RateTest, - TestType.CONGESTION_CONTROL: CongestionControlTest, - TestType.FORWARD_PRESSURE: ForwardPressureTest, - TestType.MAX_FORWARDING_RATE: MaxForwardingRateTest, - TestType.ADDRESS_CACHING_CAPACITY: AddressCachingCapacityTest, - TestType.ADDRESS_LEARNING_RATE: AddressLearningRateTest, - TestType.ERRORED_FRAMES_FILTERING: ErroredFramesFilteringTest, - TestType.BROADCAST_FORWARDING: BroadcastForwardingTest, -} - - -class TestSuite2889(PluginAbstract["TestSuiteConfiguration2889"]): - def prepare(self) -> None: - pass - - async def __do_test(self) -> None: - plugin_params = PluginParameter( - testers=self.testers, - port_identities=self.port_identities, - xoa_out=self.xoa_out, - full_test_config=self.cfg, - data_sharing=TestSuiteDataSharing(), - state_conditions=self.state_conditions, - ) - for test_suit_config in self.cfg.enabled_test_suit_config_list: - test_suit_class = TEST_TYPE_CLASS[test_suit_config.test_type] - logger.debug(f"init {test_suit_class}") - await test_suit_class(plugin_params, test_suit_config).start() - - async def __post_test(self) -> None: - logger.info("test finish") - - async def start(self) -> None: - await self.__do_test() - await self.__post_test() diff --git a/plugin2889/plugin/base_class.py b/plugin2889/plugin/base_class.py index b7eeae6..debee14 100644 --- a/plugin2889/plugin/base_class.py +++ b/plugin2889/plugin/base_class.py @@ -5,7 +5,21 @@ from abc import ABC, abstractmethod from decimal import ROUND_DOWN, Decimal from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Dict, Generic, Iterable, Optional, Protocol, TypeVar, Union +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + Callable, + Dict, + Generic, + Iterable, + List, + Optional, + Protocol, + TypeVar, + Union, +) + from loguru import logger from xoa_driver.utils import apply from xoa_driver.enums import OnOff @@ -16,7 +30,7 @@ from plugin2889.test_manager import L23TestManager from plugin2889.statistics import ResultData, StatisticsProcessor from plugin2889.dataset import MacAddress, PortPair -from plugin2889.model.test_suite import ( +from plugin2889.dataset import ( AddressCachingCapacityConfiguration, AddressLearningRateConfiguration, RateIterationOptions, @@ -132,7 +146,7 @@ def determine_should_end(self, result: Optional[ResultData] = None) -> bool: class DecimalBinarySearch(BinarySearchBase[Decimal]): def _type_cast(self, value: Any) -> Decimal: - return Decimal(value) + return Decimal(value).quantize(Decimal('.001'), rounding=ROUND_DOWN) def _calculate_move_right(self) -> Decimal: return ((self.current + self.right) / Decimal(2.0)).quantize(Decimal('.001'), rounding=ROUND_DOWN) @@ -192,7 +206,7 @@ async def __call__(self, is_live: bool) -> "ResultData": class TestBase(TestSuitAbstract[TCONFIG]): - port_identities: Dict[str, PortIdentity] + port_identities: List[PortIdentity] resources: ResourcesManager test_manager: L23TestManager full_test_config: TestSuiteConfiguration2889 @@ -262,6 +276,7 @@ def create_statistics(self) -> None: async def setup_resources(self) -> None: await self.resources.reset_ports() + await self.resources.check_port_link() await self.resources.configure_ports() await self.resources.map_pairs() @@ -283,13 +298,23 @@ async def generate_traffic(self, sample_rate: float = 1) -> AsyncGenerator[Traff raise exceptions.StopTestByLossSignal() result = await self.staticstics_collect(is_live=True) self.xoa_out.send_progress(duration_progress) - self.xoa_out.send_statistics(result) + self.xoa_out.send_statistics(self.reprocess_result(result, is_live=True)) yield TrafficInfo(progress=duration_progress, result=result) @property def iterations_offset_by_1(self) -> Iterable[int]: return range(1, self.test_suit_config.iterations + 1) + def reprocess_result(self, result: "ResultData", is_live: bool = False) -> "ResultData": + return result + + async def send_final_staticstics(self) -> "ResultData": + await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) + result = self.reprocess_result(await self.staticstics_collect(is_live=False)) + self.xoa_out.send_statistics(result) + logger.debug(result) + return result + TCFG = TypeVar("TCFG", AddressCachingCapacityConfiguration, AddressLearningRateConfiguration) @@ -316,7 +341,7 @@ def get_mac_address(self, resource: "TestResource", resource_current_address: "M def create_port_pairs(self) -> "PortPairs": assert self.test_suit_config.port_role_handler, const.INVALID_PORT_ROLE group_by_result = group_by_port_property(self.full_test_config.ports_configuration, self.test_suit_config.port_role_handler, self.port_identities) - logger.debug(group_by_result) + # logger.debug(group_by_result) test_port_uuid = group_by_result.port_role_uuids[const.PortGroup.TEST_PORT][0] learning_port_uuid = group_by_result.port_role_uuids[const.PortGroup.LEARNING_PORT][0] @@ -362,7 +387,7 @@ async def set_learning_modifiers(self, port_name: str) -> None: modifier = modifiers.obtain(0) tokens.extend( [ - modifier.specification.set(position=modifier_position + 1, mask="0xffff0000", action=ModifierActionOption.INC.to_xmp(), repetition=1), + modifier.specification.set(position=modifier_position + 1, mask="ffff0000", action=ModifierActionOption.INC.to_xmp(), repetition=1), modifier.range.set(min_val=1, step=1, max_val=0xffff) ] ) @@ -371,9 +396,9 @@ async def set_learning_modifiers(self, port_name: str) -> None: modifier1 = modifiers.obtain(1) tokens.extend( [ - modifier0.specification.set(position=modifier_position, mask="0xfff00000", action=ModifierActionOption.INC.to_xmp(), repetition=0x1000), + modifier0.specification.set(position=modifier_position, mask="fff00000", action=ModifierActionOption.INC.to_xmp(), repetition=0x1000), modifier0.range.set(min_val=1, step=1, max_val=0xfff), - modifier1.specification.set(position=modifier_position + 1, mask="0x0fff0000", action=ModifierActionOption.INC.to_xmp(), repetition=1), + modifier1.specification.set(position=modifier_position + 1, mask="0fff0000", action=ModifierActionOption.INC.to_xmp(), repetition=1), modifier1.range.set(min_val=1, step=1, max_val=0xfff), ] ) @@ -383,13 +408,12 @@ async def set_learning_modifiers(self, port_name: str) -> None: modifier1 = modifiers.obtain(1) tokens.extend( [ - modifier0.specification.set(position=modifier_position - 1, mask="0xffff0000", action=ModifierActionOption.RANDOM.to_xmp(), repetition=1), + modifier0.specification.set(position=modifier_position - 1, mask="ffff0000", action=ModifierActionOption.RANDOM.to_xmp(), repetition=1), modifier0.range.set(min_val=0, step=1, max_val=0xfff), - modifier1.specification.set(position=modifier_position + 1, mask="0x0fff0000", action=ModifierActionOption.RANDOM.to_xmp(), repetition=1), + modifier1.specification.set(position=modifier_position + 1, mask="0fff0000", action=ModifierActionOption.RANDOM.to_xmp(), repetition=1), modifier1.range.set(min_val=1, step=1, max_val=0xfff), ] ) - await apply(*tokens) async def set_learning_limit(self, port_name: str) -> None: @@ -400,6 +424,11 @@ async def set_learning_limit(self, port_name: str) -> None: ] ) + def reprocess_result(self, result: "ResultData", is_live: bool = True) -> "ResultData": + result.extra['port_name'] = self.port_name + result.extra['binary_search'] = self.binary_search + return result + async def setup_learning_traffic(self, port_name: str) -> None: await self.set_learning_modifiers(port_name) await self.set_learning_limit(port_name) @@ -459,8 +488,7 @@ async def address_learning_test(self, packet_size: int) -> Optional[ResultData]: await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) await sleep_log(const.DELAY_LEARNING_ADDRESS) result = await self.staticstics_collect(is_live=False) - await self.switch_port_roles() + await self.switch_port_roles() assert result - logger.debug(result.status) return result diff --git a/plugin2889/plugin/test_abstract.py b/plugin2889/plugin/test_abstract.py index 005a4c5..0b19948 100644 --- a/plugin2889/plugin/test_abstract.py +++ b/plugin2889/plugin/test_abstract.py @@ -1,12 +1,12 @@ from abc import ABC, abstractmethod -from typing import Any, Dict, Generator, Generic, Protocol, Type, TypeVar, runtime_checkable +from typing import Any, Dict, Generator, Generic, List, Protocol, Type, TypeVar, runtime_checkable from pydantic import BaseModel from loguru import logger from xoa_core.types import PortIdentity from plugin2889.const import TestStatus from plugin2889.plugin.dataset import BaseRunProps, TestSuiteDataSharing -from plugin2889.model.test_suite import ( +from plugin2889.dataset import ( TestSuiteConfiguration2889, UnionTestSuitConfiguration, ) @@ -38,7 +38,7 @@ async def stop_if_stopped(self) -> None: class PluginParameter(BaseModel): testers: Dict[str, Any] - port_identities: Dict[str, PortIdentity] + port_identities: List[PortIdentity] xoa_out: PXOAOut full_test_config: TestSuiteConfiguration2889 data_sharing: TestSuiteDataSharing diff --git a/plugin2889/plugin/test_address_caching_capacity.py b/plugin2889/plugin/test_address_caching_capacity.py index d26d659..d62f445 100644 --- a/plugin2889/plugin/test_address_caching_capacity.py +++ b/plugin2889/plugin/test_address_caching_capacity.py @@ -8,7 +8,7 @@ from plugin2889.resource.manager import ResourcesManager from plugin2889.util.logger import logger from plugin2889.statistics import ResultData -from plugin2889.model.test_suite import AddressCachingCapacityConfiguration +from plugin2889.dataset import AddressCachingCapacityConfiguration class AddressCachingCapacityTest(AddressLearningBase[AddressCachingCapacityConfiguration, int]): @@ -27,6 +27,7 @@ def test_suit_prepare(self) -> None: port_pairs=self.create_port_pairs(), get_mac_address_function=self.get_mac_address, ) + self.create_statistics() def do_testing_cycle(self) -> Generator[BaseRunProps, None, None]: packet_sizes = self.full_test_config.general_test_configuration.frame_sizes.packet_size_list @@ -57,6 +58,7 @@ async def run_test(self, run_props: BaseRunProps) -> None: packet_size=run_props.packet_size, rate=DECIMAL_100, ) - result = await self.address_learning_test(run_props.packet_size) + await self.address_learning_test(run_props.packet_size) logger.debug(self.binary_search.is_ended) logger.debug(self.binary_search.current) + result = await self.send_final_staticstics() \ No newline at end of file diff --git a/plugin2889/plugin/test_address_learning_rate.py b/plugin2889/plugin/test_address_learning_rate.py index e3dfad6..80eb798 100644 --- a/plugin2889/plugin/test_address_learning_rate.py +++ b/plugin2889/plugin/test_address_learning_rate.py @@ -9,7 +9,7 @@ from plugin2889.resource.manager import ResourcesManager from plugin2889.util.logger import logger from plugin2889.statistics import ResultData -from plugin2889.model.test_suite import AddressLearningRateConfiguration +from plugin2889.dataset import AddressLearningRateConfiguration class AddressLearningRateTest(AddressLearningBase[AddressLearningRateConfiguration, Decimal]): @@ -21,13 +21,14 @@ def test_suit_prepare(self) -> None: port_pairs=self.create_port_pairs(), get_mac_address_function=self.get_mac_address, ) + self.create_statistics() def do_testing_cycle(self) -> Generator[AddressLearningRateRunProps, None, None]: packet_sizes = self.full_test_config.general_test_configuration.frame_sizes.packet_size_list max_capacity = self.plugin_params.data_sharing.get_max_caching_capacity() logger.debug(max_capacity) sweep_options: Iterable[int] - if self.test_suit_config.only_use_capacity and max_capacity: + if self.test_suit_config.only_use_capacity and max_capacity > 0: sweep_options = (max_capacity,) else: end_value = max_capacity if self.test_suit_config.set_end_address_to_capacity and max_capacity else int(self.test_suit_config.address_sweep_options.end_value) @@ -67,4 +68,5 @@ async def run_test(self, run_props: AddressLearningRateRunProps) -> None: while not self.binary_search.determine_should_end(result): logger.debug(self.binary_search) logger.debug(self.learning_adress_count) - result = await self.address_learning_test(run_props.packet_size) + await self.address_learning_test(run_props.packet_size) + result = await self.send_final_staticstics() \ No newline at end of file diff --git a/plugin2889/plugin/test_broadcast_forwarding.py b/plugin2889/plugin/test_broadcast_forwarding.py index 6f93f26..7f5a23e 100644 --- a/plugin2889/plugin/test_broadcast_forwarding.py +++ b/plugin2889/plugin/test_broadcast_forwarding.py @@ -11,7 +11,7 @@ from plugin2889.resource.manager import ResourcesManager from plugin2889.util.logger import logger from plugin2889.statistics import ResultData -from plugin2889.model.test_suite import BroadcastForwardingConfiguration +from plugin2889.dataset import BroadcastForwardingConfiguration @dataclass @@ -91,7 +91,4 @@ async def run_test(self, run_props: BaseRunProps) -> None: async for traffic_info in self.generate_traffic(): result = traffic_info.result - await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) - result = await self.staticstics_collect(is_live=False) - self.xoa_out.send_statistics(result) - logger.debug(result) + await self.send_final_staticstics() \ No newline at end of file diff --git a/plugin2889/plugin/test_congestion_control.py b/plugin2889/plugin/test_congestion_control.py index 188de9a..f756a3b 100644 --- a/plugin2889/plugin/test_congestion_control.py +++ b/plugin2889/plugin/test_congestion_control.py @@ -1,11 +1,11 @@ from dataclasses import dataclass, field from functools import partial -from typing import Dict, Generator, Optional +from typing import Generator from loguru import logger from plugin2889 import const -from plugin2889.model.test_suite import CongestionControlConfiguration -from plugin2889.plugin.base_class import TestBase, TrafficInfo +from plugin2889.dataset import CongestionControlConfiguration +from plugin2889.plugin.base_class import TestBase from plugin2889.plugin.utils import PortPairs, sleep_log, group_by_port_property from plugin2889.dataset import CurrentIterProps, PortPair, StatisticsData from plugin2889.resource.manager import ResourcesManager @@ -22,7 +22,6 @@ class TestPortName: class CongestionControlTest(TestBase[CongestionControlConfiguration]): def test_suit_prepare(self): - self.create_statistics() self.port_name = TestPortName() self.uncongested_stream_index = -1 self.resources = ResourcesManager( @@ -31,6 +30,7 @@ def test_suit_prepare(self): port_identities=self.port_identities, port_pairs=self.__create_port_pairs(), ) + self.create_statistics() def __create_port_pairs(self) -> "PortPairs": assert self.test_suit_config.port_role_handler @@ -58,7 +58,7 @@ def check_statistic_status(self, result: ResultData, is_live: bool = False) -> c status = const.StatisticsStatus.FAIL return status - async def process_result_again(self, result: "ResultData") -> Dict[str, "StatisticsData"]: + async def reprocess_result(self, result: "ResultData", is_live: bool = True) -> "ResultData": resource_source_split = self.resources[self.port_name.source_split] # find the exact stream that from split port to uncongested port @@ -84,10 +84,11 @@ async def process_result_again(self, result: "ResultData") -> Dict[str, "Statist result.total.loss = uncongested_result.loss result.total.loss_percent = uncongested_result.loss_percent - return { + result.extra.update({ 'congested': congested_result, 'uncongested': uncongested_result, - } + }) + return result def do_testing_cycle(self) -> Generator[CurrentIterProps, None, None]: packet_sizes = self.full_test_config.general_test_configuration.frame_sizes.packet_size_list @@ -114,13 +115,7 @@ async def run_test(self, run_props: CurrentIterProps) -> None: await self.resources.set_time_limit(self.test_suit_config.duration) self.statistics.reset_max() - result: Optional[ResultData] = None - traffic_info: Optional[TrafficInfo] = None async for traffic_info in self.generate_traffic(): - processed_result = await self.process_result_again(traffic_info.result) - logger.debug(processed_result) + logger.debug(traffic_info) - await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) - result = await self.staticstics_collect(is_live=False) - # processed_result = await self.process_result_again(result) - self.xoa_out.send_statistics(result) + result = await self.send_final_staticstics() \ No newline at end of file diff --git a/plugin2889/plugin/test_errored_frames_filtering.py b/plugin2889/plugin/test_errored_frames_filtering.py index 8c91f91..20a3cbf 100644 --- a/plugin2889/plugin/test_errored_frames_filtering.py +++ b/plugin2889/plugin/test_errored_frames_filtering.py @@ -15,7 +15,7 @@ from plugin2889.resource.manager import ResourcesManager from plugin2889.util.logger import logger from plugin2889.statistics import ResultData -from plugin2889.model.test_suite import ErroredFramesFilteringConfiguration +from plugin2889.dataset import ErroredFramesFilteringConfiguration @dataclass @@ -30,6 +30,13 @@ class TestStreamIndex(IntEnum): OVER_SIZE = 2 +@dataclass +class StreamStats: + name: str = '' + tx: int = 0 + rx: int = 0 + + class ErroredFramesFilteringTest(TestBase[ErroredFramesFilteringConfiguration]): def test_suit_prepare(self) -> None: self.port_name: PortRolePortNameMapping @@ -45,7 +52,6 @@ def test_suit_prepare(self) -> None: def create_port_pairs(self) -> "PortPairs": assert self.test_suit_config.port_role_handler group_by_result = group_by_port_property(self.full_test_config.ports_configuration, self.test_suit_config.port_role_handler, self.port_identities) - logger.debug(group_by_result) source_port_uuid = group_by_result.port_role_uuids[const.PortGroup.SOURCE][0] destination_port_uuid = group_by_result.port_role_uuids[const.PortGroup.DESTINATION][0] self.port_name = PortRolePortNameMapping( @@ -77,7 +83,7 @@ def check_statistic_status(self, result: ResultData, is_live: bool = False) -> c return const.StatisticsStatus.FAIL return const.StatisticsStatus.SUCCESS - def calc_stream_packet_size(self) -> None: + def set_stream_packet_size(self) -> None: source_stream = self.resources[self.port_name.source].streams source_stream[TestStreamIndex.UNDER_SIZE].packet_size = \ @@ -114,12 +120,28 @@ async def set_stream_incrementing(self) -> None: ) asyncio.gather(*coroutines) - async def inject_fcs_error(self, stop_time: int) -> None: - while int(time.time()) < stop_time: - valid_stream = self.resources[self.port_name.source].port.streams.obtain(TestStreamIndex.VALID) - self.fcs_error_injected_count += 1 + async def inject_fcs_error(self) -> None: + valid_stream = self.resources[self.port_name.source].port.streams.obtain(TestStreamIndex.VALID) + try: await valid_stream.inject_err.frame_checksum.set() - await sleep_log(const.INTERVAL_INJECT_FCS_ERROR) + self.fcs_error_injected_count += 1 + except Exception as e: # error when stream traffic off + logger.debug(str(e)) + + def reprocess_result(self, result: "ResultData", is_live: bool = True) -> "ResultData": + stream_stats = [] + for stream_idx, tx in result.ports[self.port_name.source].per_tx_stream.items(): + stream_stats.append(StreamStats( + name=TestStreamIndex(stream_idx).name, + tx=tx.packet, + rx=result.ports[self.port_name.destination].per_rx_tpld_id[tx.tpld_id].packet, + )) + result.extra.update({ + "fcs_tx": self.fcs_error_injected_count, + "fcs_rx": result.total.fcs, + "streams": stream_stats, + }) + return result async def run_test(self, run_props: ErroredFramesFilteringRunProps) -> None: self.fcs_error_injected_count = 0 @@ -135,18 +157,11 @@ async def run_test(self, run_props: ErroredFramesFilteringRunProps) -> None: await self.resources.mac_learning() await sleep_log(const.DELAY_LEARNING_MAC) - self.calc_stream_packet_size() + self.set_stream_packet_size() await self.set_stream_incrementing() await self.resources.set_stream_rate_and_packet_limit(0, run_props.rate_percent, self.test_suit_config.duration) - stop_time = int(time.time()) + self.test_suit_config.duration - async for traffic_info in self.generate_traffic(): - await self.inject_fcs_error(stop_time) - result = traffic_info.result + async for _ in self.generate_traffic(sample_rate=5): + await self.inject_fcs_error() - await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) - result = await self.staticstics_collect(is_live=False) - logger.debug(result) - logger.debug(f"fcs error tx: {self.fcs_error_injected_count} rx: {result.total.fcs}") - for stream_idx, tx in result.ports[self.port_name.source].per_tx_stream.items(): - logger.debug(f"{TestStreamIndex(stream_idx).name} tx: {tx.packet}, rx: {result.ports[self.port_name.destination].per_rx_tpld_id[tx.tpld_id].packet}") + await self.send_final_staticstics() \ No newline at end of file diff --git a/plugin2889/plugin/test_forward_pressure.py b/plugin2889/plugin/test_forward_pressure.py index d89d19e..71ca5b8 100644 --- a/plugin2889/plugin/test_forward_pressure.py +++ b/plugin2889/plugin/test_forward_pressure.py @@ -2,11 +2,11 @@ from decimal import Decimal from enum import Enum from functools import partial -from typing import Dict, Generator, List, Optional +from typing import Generator, List from loguru import logger from plugin2889 import const -from plugin2889.model.test_suite import ForwardPressureConfiguration +from plugin2889.dataset import ForwardPressureConfiguration from plugin2889.plugin import rate_helper from plugin2889.plugin.base_class import TestBase from plugin2889.plugin.utils import PortPairs, sleep_log, group_by_port_property @@ -75,12 +75,19 @@ def __create_port_pair(self) -> "PortPairs": destination=group_by_result.uuid_port_name[destination_port_uuid], ) pairs = (PortPair(west=self.port_name.source, east=self.port_name.destination),) - logger.debug(pairs) + # logger.debug(pairs) return pairs async def __set_port_interframe_gap(self) -> None: await self.resources[self.port_name.source].set_port_interframe_gap(self.interframe_gap.reduced) + async def setup_resources(self) -> None: + await self.resources.reset_ports() + await self.resources.check_port_link() + await self.resources.configure_ports() + await self.__set_port_interframe_gap() + await self.resources.map_pairs() + def do_testing_cycle(self) -> Generator[CurrentIterProps, None, None]: packet_sizes = self.full_test_config.general_test_configuration.frame_sizes.packet_size_list for i in self.iterations_offset_by_1: @@ -106,24 +113,28 @@ def check_statistic_status(self, result: ResultData, is_live: bool = False) -> c status = const.StatisticsStatus.FAIL return status - def process_result_again(self, result: "ResultData", is_live: bool = True) -> Dict[str, "StatisticsData"]: + def reprocess_result(self, result: "ResultData", is_live: bool = True) -> "ResultData": tx_result = result.ports[self.port_name.source] rx_result = result.ports[self.port_name.destination] tx_util = self.__calc_max_port_util_from_result(result.ports[self.port_name.source].tx_pps, result.packet_size) logger.debug(tx_util) - if is_live: - self.port_rate_average.add(Direction.TX, tx_result.per_tx_stream[0].pps) - self.port_rate_average.add(Direction.RX, rx_result.per_rx_tpld_id[0].pps) + if is_live and tx_result.per_tx_stream and rx_result.per_rx_tpld_id: + tx_pps = list(tx_result.per_tx_stream.values())[0].pps + rx_pps = list(rx_result.per_rx_tpld_id.values())[0].pps + self.port_rate_average.add(Direction.TX, tx_pps) + self.port_rate_average.add(Direction.RX, rx_pps) tx_result.tx_pps = self.port_rate_average.read(Direction.TX) rx_result.rx_pps = self.port_rate_average.read(Direction.RX) - return { + result.extra.update({ Direction.TX.value: tx_result, Direction.RX.value: rx_result, - } + 'tx_util': tx_util, + }) + return result async def run_test(self, run_props: CurrentIterProps) -> None: logger.debug(f'iter props: {run_props}') @@ -142,19 +153,7 @@ async def run_test(self, run_props: CurrentIterProps) -> None: await self.resources.set_stream_rate_and_packet_limit(run_props.packet_size, const.DECIMAL_100, self.test_suit_config.duration) self.statistics.reset_max() - result: Optional[ResultData] = None async for traffic_info in self.generate_traffic(sample_rate=0.5): - result = traffic_info.result - processed_result = self.process_result_again(result) - logger.debug(processed_result) + logger.debug(traffic_info) - await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) - result = await self.staticstics_collect(is_live=True) - self.xoa_out.send_statistics(result) - logger.debug(self.process_result_again(result)) - - async def setup_resources(self) -> None: - await self.resources.reset_ports() - await self.resources.configure_ports() - await self.__set_port_interframe_gap() - await self.resources.map_pairs() + await self.send_final_staticstics() diff --git a/plugin2889/plugin/test_forwarding.py b/plugin2889/plugin/test_forwarding.py index 0abf6ee..154f37e 100644 --- a/plugin2889/plugin/test_forwarding.py +++ b/plugin2889/plugin/test_forwarding.py @@ -4,7 +4,7 @@ from loguru import logger from plugin2889 import const -from plugin2889.model.test_suite import MaxForwardingRateConfiguration, RateSubTestConfiguration +from plugin2889.dataset import MaxForwardingRateConfiguration, RateSubTestConfiguration from plugin2889.plugin.base_class import TestBase from plugin2889.plugin.dataset import ForwadingTestRunProps from plugin2889.plugin.utils import sleep_log @@ -55,7 +55,4 @@ async def run_test(self, run_props: ForwadingTestRunProps) -> None: async for _ in self.generate_traffic(): continue - await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) - result = await self.staticstics_collect(is_live=False) - self.xoa_out.send_statistics(result) - logger.debug(result) + await self.send_final_staticstics() \ No newline at end of file diff --git a/plugin2889/plugin/test_max_forwarding_rate.py b/plugin2889/plugin/test_max_forwarding_rate.py index 65d5aa0..399ac67 100644 --- a/plugin2889/plugin/test_max_forwarding_rate.py +++ b/plugin2889/plugin/test_max_forwarding_rate.py @@ -3,7 +3,7 @@ from plugin2889.const import PortGroup from plugin2889.dataset import PortPair -from plugin2889.model.test_suite import MaxForwardingRateConfiguration +from plugin2889.dataset import MaxForwardingRateConfiguration from plugin2889.plugin.test_forwarding import ForwardingBase from plugin2889.plugin.utils import group_by_port_property if TYPE_CHECKING: diff --git a/plugin2889/plugin/test_rate.py b/plugin2889/plugin/test_rate.py index 4e4b7a2..022a92a 100644 --- a/plugin2889/plugin/test_rate.py +++ b/plugin2889/plugin/test_rate.py @@ -1,5 +1,5 @@ from typing import Generator, Union -from plugin2889.model.test_suite import ( +from plugin2889.dataset import ( RateTestConfiguration, ) from plugin2889.plugin.base_class import TestBase diff --git a/plugin2889/plugin/test_throughput.py b/plugin2889/plugin/test_throughput.py index ac11cd7..484ed0d 100644 --- a/plugin2889/plugin/test_throughput.py +++ b/plugin2889/plugin/test_throughput.py @@ -3,7 +3,7 @@ from typing import Generator, Optional from plugin2889 import const -from plugin2889.model.test_suite import RateSubTestConfiguration +from plugin2889.dataset import RateSubTestConfiguration from plugin2889.plugin.base_class import BinarySearchMixin, DecimalBinarySearch, TestBase, TrafficInfo from plugin2889.plugin.utils import sleep_log from plugin2889.util.logger import logger @@ -43,9 +43,7 @@ async def run_test(self, run_props: CurrentIterProps) -> None: traffic_info: Optional[TrafficInfo] = None async for traffic_info in self.generate_traffic(): result = traffic_info.result + result = await self.send_final_staticstics() - await sleep_log(const.DELAY_WAIT_TRAFFIC_STOP) - result = await self.staticstics_collect(is_live=False) - self.plugin_params.xoa_out.send_statistics(result) if result and result.status.is_success and self.test_suit_config.topology.is_mesh_topology: self.plugin_params.data_sharing.set_throughput_of_frame_size(run_props.packet_size, self.binary_search.passed) diff --git a/plugin2889/plugin/utils.py b/plugin2889/plugin/utils.py index 117f996..7d8b6e3 100644 --- a/plugin2889/plugin/utils.py +++ b/plugin2889/plugin/utils.py @@ -21,7 +21,7 @@ from plugin2889.model import exceptions from plugin2889.dataset import IPv4Address, IPv6Address, MacAddress, PortPair -from plugin2889.model.test_suite import PortConfiguration, PortRoleHandler +from plugin2889.dataset import PortConfiguration, PortRoleHandler from plugin2889.model.protocol_segment import SegmentType, PortProtocolVersion from plugin2889.const import ( PortGroup, @@ -227,12 +227,12 @@ class GroupByPortProperty(BaseModel): uuid_slot: Dict[str, str] = {} port_name_role: Dict[str, str] = {} uuid_port_name: Dict[str, str] = {} - + port_peer: Dict[str, str] = {} def group_by_port_property( port_configuration: Dict[str, PortConfiguration], port_role: PortRoleHandler, - port_identities: Dict[str, PortIdentity], + port_identities: List[PortIdentity], ) -> "GroupByPortProperty": result = GroupByPortProperty() @@ -243,16 +243,16 @@ def group_by_port_property( result.port_role_uuids[port_role_config.role].append(uuid) if not port_role_config.is_used: not_use_port_uuid.append(uuid) + result.port_peer[uuid] = port_role_config.peer_port_id - logger.debug(result.uuid_role) - for _, port_config in port_configuration.items(): + for port_name, port_config in port_configuration.items(): uuid = port_config.item_id if uuid in not_use_port_uuid: continue result.uuid_slot[port_config.item_id] = port_config.port_slot - result.uuid_port_name[port_config.item_id] = port_identities[port_config.port_slot].name + result.uuid_port_name[port_config.item_id] = port_name - logger.debug(result) + # logger.debug(result) return result @@ -267,7 +267,7 @@ def create_pairs_mesh(group_by_property: GroupByPortProperty) -> PortPairs: def create_pairs_pair(group_by_property: GroupByPortProperty, traffic_direction: TrafficDirection, role_source: PortGroup) -> PortPairs: pairs = [] for port_uuid in group_by_property.port_role_uuids[role_source]: - peer_uuid = group_by_property.uuid_role[port_uuid][1] + peer_uuid = group_by_property.port_peer[port_uuid] pairs.append(PortPair(west=group_by_property.uuid_port_name[port_uuid], east=group_by_property.uuid_port_name[peer_uuid])) if traffic_direction == TrafficDirection.BIDIR: pairs.append(PortPair(east=group_by_property.uuid_port_name[port_uuid], west=group_by_property.uuid_port_name[peer_uuid])) @@ -290,7 +290,7 @@ def create_port_pair( topology: TestTopology, port_configuration: Dict[str, PortConfiguration], port_role: Optional[PortRoleHandler], - port_identities: Dict[str, PortIdentity], + port_identities: List[PortIdentity], ) -> PortPairs: role_source = PortGroup.WEST @@ -307,7 +307,7 @@ def create_port_pair( else: pairs = create_pairs_blocks(group_by_property, traffic_direction, role_source, role_destination) - logger.debug(pairs) + # logger.debug(pairs) assert pairs, 'empty port pairs' return pairs @@ -317,8 +317,7 @@ async def sleep_log(duration: float) -> None: caller_frame = inspect.stack()[1] message = f"\x1b[33;20m{caller_frame.filename.rsplit(os.path.sep, 1)[1]}:{caller_frame.lineno} {caller_frame.function} {duration}\x1B[0m" logger.debug(message) - if duration > 0: - await asyncio.sleep(duration) + await asyncio.sleep(duration) def is_ip_segment_exists(header_segments: List["ProtocolSegment"]) -> bool: diff --git a/plugin2889/resource/_port_statistics.py b/plugin2889/resource/_port_statistics.py index c78e02c..a60130e 100644 --- a/plugin2889/resource/_port_statistics.py +++ b/plugin2889/resource/_port_statistics.py @@ -2,6 +2,7 @@ import functools from typing import ( Callable, + Dict, Generator, List, Set, @@ -95,7 +96,7 @@ async def collect_rx_by_streams(self, statistics: StatisticsData, packet_size: i port_latency = PortLatency() port_jitter = PortJitter() - per_rx_tpld_id = {} + per_rx_tpld_id: Dict[int, RxTPLDId] = {} coroutines = [] for rx_stream in self.__streams_sending_in: rx_tpld_statistics = self.__port.statistics.rx.access_tpld(rx_stream.tpld_id) @@ -122,7 +123,7 @@ async def collect_rx_by_streams(self, statistics: StatisticsData, packet_size: i self.max.update_rx_bps_l2(rx_bit_count_last_sec) self.max.update_rx_pps(rx_packet_count_last_sec) - statistics.rx_packet = rx_packet + statistics.rx_packet = sum(rx_tpld.packet for rx_tpld in per_rx_tpld_id.values()) statistics.rx_bps_l1 = self.max.rx_bps_l1 statistics.loss = non_incre_seq_event_count statistics.rx_bps_l2 = self.max.rx_bps_l2 diff --git a/plugin2889/resource/_port_stream.py b/plugin2889/resource/_port_stream.py index 49066a9..402ce5a 100644 --- a/plugin2889/resource/_port_stream.py +++ b/plugin2889/resource/_port_stream.py @@ -9,7 +9,7 @@ from xoa_driver.misc import GenuineStream from .test_resource import TestResource -from plugin2889.model.test_suite import RateDefinition +from plugin2889.dataset import RateDefinition from plugin2889.plugin import rate_helper from plugin2889.plugin import utils from plugin2889.dataset import AddressCollection, IPv4Address, IPv6Address, MacAddress @@ -53,7 +53,7 @@ def header(self) -> str: if segment.segment_type.is_ipv6: utils.setup_segment_ipv6(segment, addresses.src_ipv6_addr, addresses.dst_ipv6_addr) - return f"0x{profile.prepare().hex()}" + return f"{profile.prepare().hex()}" @property def total_stream_count(self) -> int: @@ -68,7 +68,7 @@ async def setup_stream(self) -> None: stream.tpld_id.set(self.tpld_id), stream.packet.header.protocol.set(self.__resource.port_config.profile.segment_id_list), stream.packet.header.data.set(self.header), - stream.payload.content.set_incrementing(f"0x{'0'*36}"), + stream.payload.content.set_inc_word(f"{'0'*36}"), stream.insert_packets_checksum.set_on(), stream.enable.set_on(), stream.comment.set(f"Stream {self.stream_id} / {self.tpld_id}") diff --git a/plugin2889/resource/manager.py b/plugin2889/resource/manager.py index 99c4263..1fff297 100644 --- a/plugin2889/resource/manager.py +++ b/plugin2889/resource/manager.py @@ -13,6 +13,7 @@ TypeVar, List, ) +from loguru import logger from xoa_core.types import PortIdentity from xoa_driver import testers, modules, enums @@ -20,12 +21,11 @@ from plugin2889.resource._port_stream import StreamManager from plugin2889.model import exceptions -from plugin2889.const import DELAY_CREATE_PORT_PAIR, INTERVAL_CLEAR_STATISTICS, PacketSizeType +from plugin2889.const import DELAY_CREATE_PORT_PAIR, DELAY_WAIT_RESET_PORT, INTERVAL_CHECK_PORT_SYNC, CHECK_SYNC_MAX_RETRY, PacketSizeType from plugin2889.dataset import MacAddress, PortPair -from plugin2889.model.test_suite import TestSuiteConfiguration2889 +from plugin2889.dataset import TestSuiteConfiguration2889 from plugin2889.plugin.utils import sleep_log from plugin2889.resource.test_resource import TestResource -from plugin2889.util.logger import logger T = TypeVar("T", bound="ResourcesManager") @@ -38,7 +38,7 @@ def __init__( self, testers: Dict[str, testers.L23Tester], test_config: TestSuiteConfiguration2889, - port_identities: Dict[str, "PortIdentity"], + port_identities: List["PortIdentity"], port_pairs: Iterable["PortPair"], get_mac_address_function: Optional[Callable[["TestResource", "MacAddress"], "MacAddress"]] = None, ) -> None: @@ -55,7 +55,7 @@ async def setup(self) -> None: # need_ports = [pair.names for pair in self.__port_pairs] # need_ports = list(itertools.chain.from_iterable(need_ports)) coroutines = [] - for port_identity in self.__port_identities.values(): + for port_identity in self.__port_identities: # if port_identity.name not in need_ports: # continue @@ -148,8 +148,6 @@ async def stop_traffic(self) -> None: async def clear_statistic_counters(self) -> None: await asyncio.gather(*[r.statistics.clear() for r in self]) - while not self.all_ports_is_sync: - await sleep_log(INTERVAL_CLEAR_STATISTICS) async def prepare_streams(self) -> None: crooutines = [] @@ -293,8 +291,8 @@ async def configure_ports(self) -> None: async def reset_ports(self) -> None: coroutines = ( - self.set_port_reset(), self.stop_traffic(), + self.set_port_reset(), self.set_port_speed_selection(), self.set_port_autoneg(), self.set_port_anlt(), @@ -304,6 +302,18 @@ async def reset_ports(self) -> None: self.set_tpld_mode(), ) await asyncio.gather(*coroutines) + await sleep_log(DELAY_WAIT_RESET_PORT) async def set_stream_packet_limit(self, limit: int) -> None: await asyncio.gather(*[r.set_packet_limit(limit) for r in self]) + + async def check_port_link(self) -> None: + check_count = 0 + while not self.all_ports_is_sync: + logger.debug('Detected loss of link - retrying') + check_count += 1 + if check_count > CHECK_SYNC_MAX_RETRY: + if self.__test_config.general_test_configuration.should_stop_on_los: + raise exceptions.StopTestByLossSignal() + break + await sleep_log(INTERVAL_CHECK_PORT_SYNC) \ No newline at end of file diff --git a/plugin2889/resource/test_resource.py b/plugin2889/resource/test_resource.py index 70cb1a8..2fa66c7 100644 --- a/plugin2889/resource/test_resource.py +++ b/plugin2889/resource/test_resource.py @@ -18,7 +18,7 @@ from plugin2889.resource._traffic import Traffic from plugin2889.util.logger import logger from plugin2889.plugin.utils import sleep_log -from plugin2889.model.test_suite import PortConfiguration +from plugin2889.dataset import PortConfiguration class TestResource: @@ -150,10 +150,9 @@ async def mac_learning(self) -> None: paddings = "00" * 118 own_mac = self.mac_address.to_hexstring() hex_data = f"{dest_mac}{own_mac}{four_f}{paddings}" - packet = f"0x{hex_data}" if len(hex_data) // 2 > self.port.info.capabilities.max_xmit_one_packet_length: raise exceptions.PacketLengthExceed(len(hex_data) // 2, self.port.info.capabilities.max_xmit_one_packet_length) - await apply(self.port.tx_single_pkt.send.set(packet)) # P_XMITONE + await apply(self.port.tx_single_pkt.send.set(hex_data)) # P_XMITONE await sleep_log(DELAY_LEARNING_MAC) async def set_tx_config_enable(self, on_off: enums.OnOff) -> None: @@ -188,9 +187,8 @@ async def set_packet_limit(self, limit: int) -> None: asyncio.gather(*[stream.packet.limit.set(limit) for stream in self.port.streams]) async def start_traffic_sync(self, module_ports: List[int]) -> None: - logger.debug(module_ports) local_time = (await self.tester.time.get()).local_time - await self.tester.traffic_sync.set(enums.OnOff.ON, local_time + 2, module_ports) # add 2 second dealy + await self.tester.traffic_sync.set(enums.OnOff.ON, local_time + 2, module_ports) # add 2 second delay async def set_stream_peer_mac_address(self, new_peer_mac_address: "MacAddress") -> None: await asyncio.gather(*[stream.set_peer_mac_address(new_peer_mac_address) for stream in self.streams]) @@ -275,11 +273,11 @@ async def set_port_anlt(self) -> None: if bool(self.port.info.capabilities.can_set_link_train): coroutines.append( self.port.pcs_pma.link_training.settings.set( - enums.LinkTrainingMode.FORCE_ENABLE, - enums.PAM4FrameSize.N16K_FRAME, + enums.LinkTrainingMode.STANDALONE, + enums.PAM4FrameSize.P16K_FRAME, enums.LinkTrainingInitCondition.NO_INIT, enums.NRZPreset.NRZ_NO_PRESET, - enums.TimeoutMode.DEFAULT_TIMEOUT, + enums.TimeoutMode.DEFAULT, ) ) else: diff --git a/plugin2889/statistics.py b/plugin2889/statistics.py index 9d47a42..aa4be30 100644 --- a/plugin2889/statistics.py +++ b/plugin2889/statistics.py @@ -3,6 +3,7 @@ from pydantic import BaseModel from typing import ( TYPE_CHECKING, + Any, Callable, Dict, Optional, @@ -24,6 +25,7 @@ class ResultData(BaseModel): status: const.StatisticsStatus ports: Dict[str, StatisticsData] = {} is_live: bool + extra: Dict[str, Any] = {} class StatisticsProcessor: diff --git a/plugin2889/test_manager.py b/plugin2889/test_manager.py index 414b767..35bae03 100644 --- a/plugin2889/test_manager.py +++ b/plugin2889/test_manager.py @@ -3,7 +3,7 @@ import time from typing import Awaitable, TypeVar, AsyncGenerator -from plugin2889.const import INTERVAL_CHECK_SHOULD_STOP_TRAFFIC +from plugin2889.const import DELAY_WAIT_RESET_STATS, INTERVAL_CHECK_SHOULD_STOP_TRAFFIC from plugin2889.resource.manager import ResourcesManager from plugin2889.plugin.utils import sleep_log from plugin2889.util.logger import logger @@ -36,8 +36,9 @@ def __await__(self: T): # type: ignore @contextlib.asynccontextmanager async def __traffic_runner(self) -> AsyncGenerator[None, None]: - logger.debug("\033[31mStart traffic on both ports...\x1B[0m") + logger.debug("\033[31mStart traffic...\x1B[0m") await self.__resources.clear_statistic_counters() + await sleep_log(DELAY_WAIT_RESET_STATS) await self.__resources.start_traffic() try: yield @@ -54,7 +55,6 @@ async def generate_traffic(self, duration: int, *, sampling_rate: float = 1.0) - time_step = 1.0 / sampling_rate duration_accived = False async with self.__traffic_runner(): - logger.debug(f"all_traffic_is_stop {self.__resources.all_traffic_is_stop}") while time.time() - start_ts <= duration + 2: # traffic stop delay begin = time.time() time_clock = await self.__resources.get_time_elipsed() @@ -64,5 +64,3 @@ async def generate_traffic(self, duration: int, *, sampling_rate: float = 1.0) - duration_accived = time_clock == duration yield int(time_clock / (duration) * 100) await sleep_log(round(time_step - (time.time() - begin), 3)) - else: - logger.debug('all_traffic_is_stop') diff --git a/plugin3918/meta.yml b/plugin3918/meta.yml index af1dd21..2388b1b 100644 --- a/plugin3918/meta.yml +++ b/plugin3918/meta.yml @@ -1,5 +1,5 @@ name: "RFC-3918" -version: "1.0.0b1" +version: "1.0.1" core_version: ">=1.0.0" author: - "RDI"