[RFC,v1,18/23] dts: merge DTS framework/flow/flow.py to DPDK

Message ID 20220406151903.2916254-19-juraj.linkes@pantheon.tech (mailing list archive)
State RFC, archived
Delegated to: Thomas Monjalon
Headers
Series merge DTS test resource files to DPDK |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

Juraj Linkeš April 6, 2022, 3:18 p.m. UTC
  ---
 dts/framework/flow/flow.py | 223 +++++++++++++++++++++++++++++++++++++
 1 file changed, 223 insertions(+)
 create mode 100644 dts/framework/flow/flow.py
  

Patch

diff --git a/dts/framework/flow/flow.py b/dts/framework/flow/flow.py
new file mode 100644
index 0000000000..e80e69a991
--- /dev/null
+++ b/dts/framework/flow/flow.py
@@ -0,0 +1,223 @@ 
+# BSD LICENSE
+#
+# Copyright(c) 2020 Intel Corporation. All rights reserved.
+# Copyright © 2018[, 2019] The University of New Hampshire. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   * Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in
+#     the documentation and/or other materials provided with the
+#     distribution.
+#   * Neither the name of Intel Corporation nor the names of its
+#     contributors may be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import annotations
+
+import copy
+import itertools
+import operator
+from functools import reduce
+from typing import FrozenSet, Iterable, List, Tuple, Union
+
+from scapy.layers.l2 import Ether
+from scapy.packet import Raw
+
+from .enums import FlowActionType, FlowItemType
+from .exceptions import InvalidFlowItemException
+from .flow_action_items import ActionFlowItem
+from .flow_items import FlowItem
+from .flow_pattern_items import TUNNELING_PROTOCOLS, PatternFlowItem
+
+# Get reserved mac addresses
+NEVER_MATCH_PACKET = Ether(src="", dst="") / Raw("\x00" * 64)
+
+
+def _iterable_deep_compare(i1, i2):
+    return reduce(lambda x, y: x and y, map(lambda x, y: x == y, i1, i2), True)
+
+
+def expand_pattern_list_with_iterable_replacing_item(
+    patterns: List[Iterable[FlowItem]],
+    it: Iterable[Tuple[FlowItem, FrozenSet[str], FrozenSet[str], str]],
+    item,
+):
+    """
+    This function takes a list of patterns and splits each of them into 2
+    parts, excluding the item at index. It then uses the provided
+    iterator to fill in that value for all patterns.
+
+    Ex:
+    if patterns is [['a', 'b', 'c'], ['c','b','a']], it is [1,2], and item is 'b',
+    then this function will produce
+
+    [['a', 1], ['a', 2], ['a', 1], ['a', 2], ['a', 1], ['a', 2]]
+
+    if everything is converted into a list. It is not converted
+    because that requires using the memory to store all of this at
+    the same time, which could be fairly large.
+    """
+
+    split_patterns = list(
+        map(
+            lambda pattern: (
+                pattern[: pattern.index(item)],
+                pattern[pattern.index(item) + 1 :],
+            ),
+            filter(lambda pattern: item in pattern, patterns),
+        )
+    )
+    # Tee the iterators so I can consume all of them
+
+    iterators = itertools.tee(it, len(patterns))
+    for pattern_before, pattern_after in split_patterns:
+        for iterator in iterators:
+            for dataset in iterator:
+                backup_dataset = copy.deepcopy(dataset)
+                yield (
+                    [*pattern_before, backup_dataset[0], *pattern_after],
+                    *backup_dataset[1:],
+                )
+            # yield from map(
+            #     lambda flow_item_test_properties: (
+            #         [*pattern_before, flow_item_test_properties[0], *pattern_after],
+            #         *flow_item_test_properties[1:],
+            #     ), iterator
+            # )
+
+    yield from filter(lambda pattern: item not in pattern, patterns)
+
+
+class Flow(object):
+    action_items: List[ActionFlowItem]
+    pattern_items: List[PatternFlowItem]
+    entry_points: FrozenSet[FlowItemType]
+
+    def __init__(
+        self,
+        action_items=None,
+        pattern_items=None,
+    ):
+        if action_items is None:
+            action_items = []
+
+        if pattern_items is None:
+            pattern_items = []
+
+        self.action_items = action_items
+        self.pattern_items = pattern_items
+
+    def __truediv__(self, item: Union[FlowItem, Flow]):
+        """
+        Used in a similar way to scapy's packet composition. Returns a new flow with the mutated state.
+        @param item: The other flow item.
+        @return: A Flow containing both items
+        """
+        if isinstance(item, Flow):
+            return Flow(
+                pattern_items=[*self.pattern_items, *item.pattern_items],
+                action_items=[*self.action_items, *item.action_items],
+            )
+        elif isinstance(item, PatternFlowItem):
+            if len(self.pattern_items) == 0:
+                return Flow(
+                    pattern_items=[*self.pattern_items, item],
+                    action_items=[*self.action_items],
+                )
+            elif item.type in self.pattern_items[-1].valid_next_items:
+                return Flow(
+                    pattern_items=[*self.pattern_items, item],
+                    action_items=[*self.action_items],
+                )
+            else:
+                raise InvalidFlowItemException(self.pattern_items[-1], item, flow=self)
+        elif isinstance(item, ActionFlowItem):
+            if len(self.action_items) == 0:
+                return Flow(
+                    pattern_items=[*self.pattern_items],
+                    action_items=[*self.action_items, item],
+                )
+
+            for action in self.action_items:
+                if item.type not in action.allowed_with:
+                    raise InvalidFlowItemException(action, item, flow=self)
+            return Flow(
+                pattern_items=[*self.pattern_items],
+                action_items=[*self.action_items, item],
+            )
+
+    def __str__(self):
+        return f"ingress pattern %s actions queue index 1 / end" % (
+            " / ".join(str(item) for item in self.pattern_items) + " / end"
+        )
+
+    def __repr__(self):
+        return str(self)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, Flow)
+            and len(self.action_items) == len(other.action_items)
+            and len(self.pattern_items) == len(other.pattern_items)
+            and _iterable_deep_compare(self.pattern_items, other.pattern_items)
+            and _iterable_deep_compare(self.action_items, other.action_items)
+        )
+
+    def to_scapy_packet(self):
+        return reduce(
+            operator.truediv, map(lambda x: x.to_scapy_packet(), self.pattern_items)
+        )
+
+    def get_test_property_flows(
+        self, pattern_item_types_to_update=None, action_item_types_to_update=None
+    ) -> Iterable[Flow]:
+        if pattern_item_types_to_update is None and action_item_types_to_update is None:
+            pattern_item_types_to_update = [self.pattern_items[-1]]
+        elif pattern_item_types_to_update is None:
+            pattern_item_types_to_update = []
+        elif action_item_types_to_update is None:
+            action_item_types_to_update = []
+
+        # So that if this object is mutated before the generator is finished, it won't change anything
+        base_pattern_items = copy.deepcopy(self.pattern_items)
+        base_action_items = copy.deepcopy(self.action_items)
+
+        test_flows: Iterable[Iterable[FlowItem]] = [base_pattern_items]
+
+        tunnelling_protocols = list(
+            filter(lambda i: type(i) in TUNNELING_PROTOCOLS, base_pattern_items)
+        )
+        if len(tunnelling_protocols) > 0:
+            test_flows = expand_pattern_list_with_iterable_replacing_item(
+                [*test_flows],
+                tunnelling_protocols[0].get_property_stream(),
+                tunnelling_protocols[0],
+            )
+        else:
+            test_flows = expand_pattern_list_with_iterable_replacing_item(
+                [*test_flows],
+                self.pattern_items[-1].get_property_stream(),
+                self.pattern_items[-1],
+            )
+        for pattern in test_flows:
+            yield Flow(
+                pattern_items=pattern[0], action_items=base_action_items
+            ), *pattern[1:]