From 8f819cc4d1af98279eb0e00c40f8f48081fff482 Mon Sep 17 00:00:00 2001 From: r12f Date: Sun, 2 Jun 2024 01:45:38 +0000 Subject: [PATCH] Add P4 meta in SAI spec for lib generation, support merge SAI spec. --- dash-pipeline/Makefile | 5 +- dash-pipeline/SAI/sai_api_gen.py | 3 +- .../SAI/utils/dash_p4/dash_p4_counter.py | 4 +- .../SAI/utils/dash_p4/dash_p4_table.py | 29 +++++++++-- .../utils/dash_p4/dash_p4_table_attribute.py | 12 ++--- .../SAI/utils/dash_p4/dash_p4_table_group.py | 25 ++++++---- .../SAI/utils/dash_p4/dash_p4_table_key.py | 4 +- .../SAI/utils/dash_p4/dash_sai_extensions.py | 4 +- dash-pipeline/SAI/utils/sai_spec/__init__.py | 1 + dash-pipeline/SAI/utils/sai_spec/sai_api.py | 16 ++++++ .../SAI/utils/sai_spec/sai_api_extension.py | 5 ++ .../SAI/utils/sai_spec/sai_api_group.py | 18 +++++++ .../SAI/utils/sai_spec/sai_api_p4_meta.py | 19 +++++++ .../SAI/utils/sai_spec/sai_attribute.py | 8 +++ .../SAI/utils/sai_spec/sai_common.py | 18 +++++++ dash-pipeline/SAI/utils/sai_spec/sai_enum.py | 5 ++ .../SAI/utils/sai_spec/sai_enum_member.py | 4 ++ dash-pipeline/SAI/utils/sai_spec/sai_spec.py | 29 +++++++++-- .../SAI/utils/sai_spec/sai_spec_utils.py | 50 +++++++++++++++++++ .../SAI/utils/sai_spec/sai_struct.py | 6 ++- .../SAI/utils/sai_spec/sai_struct_entry.py | 4 ++ 21 files changed, 235 insertions(+), 34 deletions(-) create mode 100644 dash-pipeline/SAI/utils/sai_spec/sai_api_p4_meta.py create mode 100644 dash-pipeline/SAI/utils/sai_spec/sai_spec_utils.py diff --git a/dash-pipeline/Makefile b/dash-pipeline/Makefile index 47fedf7d7..841ad33f3 100644 --- a/dash-pipeline/Makefile +++ b/dash-pipeline/Makefile @@ -177,8 +177,9 @@ sai: sai-headers sai-meta libsai sai-headers: p4 docker-saithrift-bldr-image-exists | SAI/SAI @echo "Generate SAI library headers and implementation..." - # Once the specs are checked in, we can use this to revert any local changes before generating the new specs. - # git checkout SAI/specs/* + # Revert any local changes before generating the new specs. + git clean -xf SAI/specs + git checkout SAI/specs/* mkdir -p SAI/lib diff --git a/dash-pipeline/SAI/sai_api_gen.py b/dash-pipeline/SAI/sai_api_gen.py index af8098b48..ceb574d42 100755 --- a/dash-pipeline/SAI/sai_api_gen.py +++ b/dash-pipeline/SAI/sai_api_gen.py @@ -68,7 +68,8 @@ print("Outputting new SAI spec to " + sai_spec_dir) yaml_inc_ctor.autoload = False new_sai_spec = dash_sai_exts.to_sai() - new_sai_spec.serialize(sai_spec_dir) + sai_spec.merge(new_sai_spec) + sai_spec.serialize(sai_spec_dir) # Generate and update all SAI files SAIGenerator(dash_sai_exts).generate() diff --git a/dash-pipeline/SAI/utils/dash_p4/dash_p4_counter.py b/dash-pipeline/SAI/utils/dash_p4/dash_p4_counter.py index 88ef46b62..bb20c8bfa 100644 --- a/dash-pipeline/SAI/utils/dash_p4/dash_p4_counter.py +++ b/dash-pipeline/SAI/utils/dash_p4/dash_p4_counter.py @@ -147,13 +147,13 @@ def generate_counter_sai_attributes(self) -> "Iterator[DashP4Counter]": # # Functions for generating SAI specs. # - def _get_sai_name(self, table_name: str) -> str: + def get_sai_name(self, table_name: str) -> str: if self.attr_type == "stats": return f"SAI_{table_name.upper()}_STAT_{self.name.upper()}" return f"SAI_{table_name.upper()}_{self.name.upper()}" - def _get_sai_description(self, table_name: str): + def get_sai_description(self, table_name: str): if self.attr_type == "stats": return f"DASH {table_name.upper()} {self.name.upper()} stat count" diff --git a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table.py b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table.py index 1fff37500..6b4a2ff71 100644 --- a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table.py +++ b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table.py @@ -5,7 +5,7 @@ from .dash_p4_table_action_param import * from .dash_p4_table_key import * from .dash_p4_table_action import * -from ..sai_spec import SaiApi, SaiStruct, SaiEnum, SaiEnumMember, SaiAttribute +from ..sai_spec import SaiApi, SaiStruct, SaiEnum, SaiEnumMember, SaiAttribute, SaiApiP4MetaAction, SaiApiP4MetaTable @dash_p4rt_parser @@ -264,6 +264,7 @@ def __build_sai_attributes_after_parsing(self): # def to_sai(self) -> SaiApi: sai_api = SaiApi(self.name, "", self.is_object != "false") + sai_api.p4_meta.tables.append(SaiApiP4MetaTable(self.id)) self.create_sai_action_enum(sai_api) self.create_sai_structs(sai_api) @@ -276,19 +277,36 @@ def create_sai_action_enum(self, sai_api: SaiApi) -> None: # If the table represents an SAI object, it should not have an action enum. # If the table has only 1 action, we don't need to create the action enum. if len(self.actions) <= 1 and self.is_object != "false": + # We still need to create the p4 meta action here for generating default action code in libsai. + if len(self.actions) == 1: + sai_api.p4_meta.tables[0].actions["default"] = SaiApiP4MetaAction("default", self.actions[0].id) return action_enum_member_value = 0 action_enum_members: List[SaiEnumMember] = [] for action in self.actions: + action_enum_member_name = f"SAI_{self.name.upper()}_ACTION_{action.name.upper()}" + action_enum_members.append( SaiEnumMember( - name=f"SAI_{self.name.upper()}_ACTION_{action.name.upper()}", + name=action_enum_member_name, description="", value=str(action_enum_member_value), ) ) + p4_meta_action = SaiApiP4MetaAction( + name=action_enum_member_name, + id=action.id, + ) + + for action_param in action.params: + p4_meta_action.attr_param_id[action_param.get_sai_name(self.name)] = action_param.id + + sai_api.p4_meta.tables[0].actions[action_enum_member_name] = p4_meta_action + + action_enum_member_value += 1 + action_enum_type_name = f"sai_{self.name.lower()}_action_t" action_enum = SaiEnum( @@ -307,6 +325,7 @@ def create_sai_action_enum(self, sai_api: SaiApi) -> None: ) sai_api.attributes.append(sai_attr_action) + def create_sai_structs(self, sai_api: SaiApi) -> None: # The entry struct is only needed for non-object tables. if self.is_object != "false": @@ -329,9 +348,9 @@ def create_sai_stats(self, sai_api: SaiApi) -> None: ] def create_sai_attributes(self, sai_api: SaiApi) -> None: - sai_api.attributes.extend( - [attr.to_sai_attribute(self.name) for attr in self.sai_attributes if attr.skipattr != "true"] - ) + for attr in self.sai_attributes: + if attr.skipattr != "true": + sai_api.attributes.append(attr.to_sai_attribute(self.name)) # If the table has an counter attached, we need to create a counter attribute for it. # The counter attribute only counts that packets that hits any entry, but not the packet that misses all entries. diff --git a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_attribute.py b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_attribute.py index 9563f3d51..74487d5c1 100644 --- a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_attribute.py +++ b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_attribute.py @@ -95,8 +95,8 @@ def set_sai_type(self, sai_type_info: SAITypeInfo) -> None: # Functions for generating SAI specs. # def to_sai_struct_entry(self, table_name: str) -> SaiStructEntry: - name = self._get_sai_name(table_name) - description = self._get_sai_description(table_name) + name = self.get_sai_name(table_name) + description = self.get_sai_description(table_name) object_name = f"SAI_OBJECT_TYPE_{self.object_name.upper()}" if self.object_name else None return SaiStructEntry( @@ -108,8 +108,8 @@ def to_sai_struct_entry(self, table_name: str) -> SaiStructEntry: ) def to_sai_attribute(self, table_name: str) -> SaiAttribute: - name = self._get_sai_name(table_name) - description = self._get_sai_description(table_name) + name = self.get_sai_name(table_name) + description = self.get_sai_description(table_name) default_value = None if self.isreadonly == "true" else self.default object_name = f"SAI_OBJECT_TYPE_{self.object_name.upper()}" if self.object_name else None @@ -129,8 +129,8 @@ def to_sai_attribute(self, table_name: str) -> SaiAttribute: valid_only = self.validonly, ) - def _get_sai_name(self, table_name: str) -> str: + def get_sai_name(self, table_name: str) -> str: return f"SAI_{table_name.upper()}_{self.name.upper()}" - def _get_sai_description(self, table_name: str): + def get_sai_description(self, table_name: str): return f"Action parameter {self.name.upper()}" \ No newline at end of file diff --git a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_group.py b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_group.py index c6db8c07d..e2c232a8c 100644 --- a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_group.py +++ b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_group.py @@ -1,7 +1,7 @@ from typing import List, Optional from .common import * from .dash_p4_table import DashP4Table -from ..sai_spec import SaiApiGroup +from ..sai_spec import SaiApiGroup, SaiApi class DashP4TableGroup(DashP4Object): @@ -25,20 +25,23 @@ def add_table(self, table: DashP4Table) -> None: self.tables.append(table) def post_parsing_process(self, all_table_names: List[str]) -> None: - self.__ignore_duplicated_tables_in_headers() - for table in self.tables: table.post_parsing_process(all_table_names) - - def __ignore_duplicated_tables_in_headers(self) -> None: - table_names = set() + + def to_sai(self) -> SaiApiGroup: + sai_api_list: List[SaiApi] = [] + sai_api_map: Dict[str, SaiApi] = {} for table in self.tables: - if table.name in table_names: + sai_api = table.to_sai() + + if table.name in sai_api_map: table.ignored_in_header = True - table_names.add(table.name) + sai_api_map[table.name].p4_meta.tables.extend(sai_api.p4_meta.tables) + else: + sai_api_map[table.name] = sai_api + sai_api_list.append(sai_api) - def to_sai(self) -> SaiApiGroup: sai_api_group = SaiApiGroup(self.app_name, "") - sai_api_group.sai_apis = [table.to_sai() for table in self.tables if not table.ignored_in_header] - return sai_api_group \ No newline at end of file + sai_api_group.sai_apis = sai_api_list + return sai_api_group diff --git a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_key.py b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_key.py index ec2c26f20..6dc1c58d4 100644 --- a/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_key.py +++ b/dash-pipeline/SAI/utils/dash_p4/dash_p4_table_key.py @@ -61,11 +61,11 @@ def parse_p4rt(self, p4rt_table_key: Dict[str, Any]) -> None: # # Functions for generating SAI specs. # - def _get_sai_name(self, table_name: str) -> str: + def get_sai_name(self, table_name: str) -> str: if self.is_entry_key: return self.name return f"SAI_{table_name.upper()}_{self.name.upper()}" - def _get_sai_description(self, table_name: str): + def get_sai_description(self, table_name: str): return f"{self.match_type.capitalize()} matched key {self.name}" \ No newline at end of file diff --git a/dash-pipeline/SAI/utils/dash_p4/dash_sai_extensions.py b/dash-pipeline/SAI/utils/dash_p4/dash_sai_extensions.py index 19ae5bf26..897d99fbd 100644 --- a/dash-pipeline/SAI/utils/dash_p4/dash_sai_extensions.py +++ b/dash-pipeline/SAI/utils/dash_p4/dash_sai_extensions.py @@ -119,12 +119,14 @@ def post_parsing_process(self) -> None: # def to_sai(self) -> SaiSpec: sai_spec = SaiSpec() + sai_spec.api_groups = [api_group.to_sai() for api_group in self.table_groups] + self.create_sai_api_types(sai_spec) self.create_sai_object_types(sai_spec) self.create_sai_object_entries(sai_spec) self.create_sai_enums(sai_spec) self.create_sai_port_counters(sai_spec.port_extenstion) - sai_spec.api_groups = [api_group.to_sai() for api_group in self.table_groups] + return sai_spec def create_sai_api_types(self, sai_spec: SaiSpec): diff --git a/dash-pipeline/SAI/utils/sai_spec/__init__.py b/dash-pipeline/SAI/utils/sai_spec/__init__.py index e79348a72..3542b4756 100644 --- a/dash-pipeline/SAI/utils/sai_spec/__init__.py +++ b/dash-pipeline/SAI/utils/sai_spec/__init__.py @@ -7,3 +7,4 @@ from .sai_struct import SaiStruct from .sai_struct_entry import SaiStructEntry from .sai_attribute import SaiAttribute +from .sai_api_p4_meta import SaiApiP4Meta, SaiApiP4MetaTable, SaiApiP4MetaAction \ No newline at end of file diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_api.py b/dash-pipeline/SAI/utils/sai_spec/sai_api.py index 07334c4f9..19f10f405 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_api.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_api.py @@ -3,6 +3,8 @@ from .sai_attribute import SaiAttribute from .sai_enum import SaiEnum from .sai_struct import SaiStruct +from .sai_api_p4_meta import SaiApiP4Meta +from . import sai_spec_utils class SaiApi(SaiCommon): @@ -17,3 +19,17 @@ def __init__(self, name: str, description: str, is_object: bool = False): self.structs: List[SaiStruct] = [] self.attributes: List[SaiAttribute] = [] self.stats: List[SaiAttribute] = [] + self.p4_meta: SaiApiP4Meta = SaiApiP4Meta() + + def merge(self, other: "SaiCommon"): + super().merge(other) + + self.is_object = other.is_object + sai_spec_utils.merge_sai_common_lists(self.enums, other.enums) + sai_spec_utils.merge_sai_common_lists(self.structs, other.structs) + sai_spec_utils.merge_sai_common_lists(self.attributes, other.attributes) + sai_spec_utils.merge_sai_common_lists(self.stats, other.stats) + + # The P4 meta can be merged by replacing the old one, since it doesn't affect the ABI, + # and the new one is always more up-to-date. + self.p4_meta = other.p4_meta diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_api_extension.py b/dash-pipeline/SAI/utils/sai_spec/sai_api_extension.py index 3ebd9e38a..d3324d88c 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_api_extension.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_api_extension.py @@ -1,5 +1,6 @@ from typing import List from .sai_attribute import SaiAttribute +from . import sai_spec_utils class SaiApiExtension: @@ -12,3 +13,7 @@ class SaiApiExtension: def __init__(self): self.attributes: List[SaiAttribute] = [] self.stats: List[SaiAttribute] = [] + + def merge(self, other: "SaiApiExtension"): + sai_spec_utils.merge_sai_common_lists(self.attributes, other.attributes) + sai_spec_utils.merge_sai_common_lists(self.stats, other.stats) diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_api_group.py b/dash-pipeline/SAI/utils/sai_spec/sai_api_group.py index 25f0b7b38..b960a9f99 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_api_group.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_api_group.py @@ -1,6 +1,7 @@ from typing import List from .sai_common import SaiCommon from .sai_api import SaiApi +from . import sai_spec_utils class SaiApiGroup(SaiCommon): @@ -11,3 +12,20 @@ class SaiApiGroup(SaiCommon): def __init__(self, name: str, description: str): super().__init__(name, description) self.sai_apis: List[SaiApi] = [] + + def merge(self, other: "SaiCommon"): + super().merge(other) + sai_spec_utils.merge_sai_common_lists(self.sai_apis, other.sai_apis) + + def deprecate(self) -> bool: + """ + Deprecate API group. + + When deprecating the API group, we can safely remove it from the list as the + net effect is the same as keeping it: + - The old API type, object type and object entries will not be changed. + - The SAI headers will not be changed, because their API groups are present. + - The DASH libsai will not be generated anymore, but it is ok, since we will not + use them in the BMv2 anyway. + """ + return True diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_api_p4_meta.py b/dash-pipeline/SAI/utils/sai_spec/sai_api_p4_meta.py new file mode 100644 index 000000000..e1279faeb --- /dev/null +++ b/dash-pipeline/SAI/utils/sai_spec/sai_api_p4_meta.py @@ -0,0 +1,19 @@ +from typing import Dict, List + + +class SaiApiP4MetaAction: + def __init__(self, name: str, id: int): + self.name: str = name + self.id: int = id + self.attr_param_id: Dict[str, int] = {} + + +class SaiApiP4MetaTable: + def __init__(self, id: int): + self.id: int = id + self.actions: Dict[str, SaiApiP4MetaAction] = {} + + +class SaiApiP4Meta: + def __init__(self): + self.tables: List[SaiApiP4MetaTable] = [] diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_attribute.py b/dash-pipeline/SAI/utils/sai_spec/sai_attribute.py index bdb680f0a..88b0033ff 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_attribute.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_attribute.py @@ -31,3 +31,11 @@ def __init__( self.allow_null = allow_null self.valid_only = valid_only self.deprecated = deprecated + + def merge(self, other: "SaiCommon"): + super().merge(other) + self.__dict__.update(other.__dict__) + + def deprecate(self) -> bool: + self.deprecated = True + return False \ No newline at end of file diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_common.py b/dash-pipeline/SAI/utils/sai_spec/sai_common.py index 7f1a21332..c6ee8e8cd 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_common.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_common.py @@ -6,3 +6,21 @@ class SaiCommon: def __init__(self, name: str, description: str): self.name: str = name self.description: str = description + + def merge(self, other: "SaiCommon"): + """ + Merge the other SaiCommon object into this object. + """ + if not isinstance(other, type(self)): + raise TypeError(f"Cannot merge {type(self)} with {type(other)}") + + self.description = other.description + + def deprecate(self) -> bool: + """ + Deprecate this object. + + If the value doesn't support deprecation marking, we don't do anything + but return False to keep it in the list. + """ + return False diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_enum.py b/dash-pipeline/SAI/utils/sai_spec/sai_enum.py index bae1cff4c..621b80427 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_enum.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_enum.py @@ -1,6 +1,7 @@ from typing import List from .sai_common import SaiCommon from .sai_enum_member import SaiEnumMember +from . import sai_spec_utils class SaiEnum(SaiCommon): @@ -11,3 +12,7 @@ class SaiEnum(SaiCommon): def __init__(self, name: str, description: str, members: List[SaiEnumMember] = []): super().__init__(name, description) self.members: List[SaiEnumMember] = members + + def merge(self, other: "SaiCommon"): + super().merge(other) + sai_spec_utils.merge_sai_common_lists(self.members, other.members) diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_enum_member.py b/dash-pipeline/SAI/utils/sai_spec/sai_enum_member.py index f8cfb6bd2..fe4a458c8 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_enum_member.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_enum_member.py @@ -10,3 +10,7 @@ class SaiEnumMember(SaiCommon): def __init__(self, name: str, description: str, value: str): super().__init__(name, description) self.value: str = value + + def merge(self, other: "SaiCommon"): + super().merge(other) + self.value = other.value \ No newline at end of file diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_spec.py b/dash-pipeline/SAI/utils/sai_spec/sai_spec.py index 74cd6ee33..e82faee03 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_spec.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_spec.py @@ -6,6 +6,7 @@ from .sai_api_group import SaiApiGroup from .sai_api_extension import SaiApiExtension from .sai_struct_entry import SaiStructEntry +from . import sai_spec_utils class SaiSpec: @@ -24,12 +25,16 @@ def __init__(self): def serialize(self, spec_dir: str): yaml_inc_files = [] for api_group in self.api_groups: - sai_api_group_spec_file_path = os.path.join(spec_dir, api_group.name + ".yaml") + sai_api_group_spec_file_path = os.path.join( + spec_dir, api_group.name + ".yaml" + ) with open(sai_api_group_spec_file_path, "w") as f: f.write(yaml.dump(api_group, indent=2, sort_keys=False)) - - yaml_inc_files.append(yaml_include.Data(urlpath=sai_api_group_spec_file_path)) + + yaml_inc_files.append( + yaml_include.Data(urlpath=sai_api_group_spec_file_path) + ) api_groups = self.api_groups self.api_groups = yaml_inc_files @@ -43,3 +48,21 @@ def serialize(self, spec_dir: str): def deserialize(spec_dir: str): with open(os.path.join(spec_dir, "sai_spec.yaml")) as f: return yaml.unsafe_load(f) + + def merge(self, other: "SaiSpec"): + sai_spec_utils.merge_sai_value_lists( + self.api_types, other.api_types, lambda x: x + ) + sai_spec_utils.merge_sai_value_lists( + self.object_types, other.object_types, lambda x: x + ) + sai_spec_utils.merge_sai_common_lists(self.object_entries, other.object_entries) + + # The global enums are generated from the P4 enum types, so we can respect whatever in the + # new spec and simply replace them, because: + # - It doesn't matter if the order of enum itself changes. + # - We cannot move the enum members as we want, as their order changes their values. + self.enums = other.enums + + self.port_extenstion.merge(other.port_extenstion) + sai_spec_utils.merge_sai_common_lists(self.api_groups, other.api_groups) diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_spec_utils.py b/dash-pipeline/SAI/utils/sai_spec/sai_spec_utils.py new file mode 100644 index 000000000..747d19933 --- /dev/null +++ b/dash-pipeline/SAI/utils/sai_spec/sai_spec_utils.py @@ -0,0 +1,50 @@ +from typing import Any, List, Callable +from .sai_common import SaiCommon + + +def merge_sai_value_lists( + target: List[Any], + source: List[Any], + get_key: Callable[[Any], str], + on_conflict: Callable[[Any, Any], None] = lambda x, y: x, + on_deprecate: Callable[[Any], bool] = lambda x: False +) -> None: + """ + Merge 2 SAI value lists from source list into target. + + Since we could not remove the old value or change the order of old values, the merge + is done as below: + - Any new values will be added in the end of the list. + - Any values that collapse with existing values will invoke on_conflict callback to resolve. + - Any values that needs to be removed will invoke on_deprecate function to deprecate. By default, + it will not be removed from the old list. + """ + target_dict = {get_key(item): item for item in target} + + source_keys = set() + for source_item in source: + source_key = get_key(source_item) + source_keys.add(source_key) + + if source_key in target_dict: + target_item = target_dict[source_key] + on_conflict(target_item, source_item) + else: + target.append(source_item) + target_dict[source_key] = source_item + + # Remove all items in target, if its key doesn't exist in source_keys and on_deprecate returns True. + target[:] = [item for item in target if get_key(item) in source_keys or not on_deprecate(item)] + + +def merge_sai_common_lists( + target: List[SaiCommon], + source: List[SaiCommon], +) -> None: + merge_sai_value_lists( + target, + source, + get_key=lambda x: x.name, + on_conflict=lambda x, y: x.merge(y), + on_deprecate=lambda x: x.deprecate(), + ) diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_struct.py b/dash-pipeline/SAI/utils/sai_spec/sai_struct.py index b98cf3a27..025d1ecb4 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_struct.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_struct.py @@ -1,7 +1,7 @@ from typing import List from .sai_common import SaiCommon from .sai_struct_entry import SaiStructEntry - +from . import sai_spec_utils class SaiStruct(SaiCommon): """ @@ -11,3 +11,7 @@ class SaiStruct(SaiCommon): def __init__(self, name: str, description: str, members: List[SaiStructEntry] = []): super().__init__(name, description) self.members: List[SaiStructEntry] = members + + def merge(self, other: "SaiCommon"): + super().merge(other) + sai_spec_utils.merge_sai_common_lists(self.members, other.members) \ No newline at end of file diff --git a/dash-pipeline/SAI/utils/sai_spec/sai_struct_entry.py b/dash-pipeline/SAI/utils/sai_spec/sai_struct_entry.py index 1f4e6ea06..ba3e42f99 100644 --- a/dash-pipeline/SAI/utils/sai_spec/sai_struct_entry.py +++ b/dash-pipeline/SAI/utils/sai_spec/sai_struct_entry.py @@ -19,3 +19,7 @@ def __init__( self.type = type self.objects = objects self.valid_only = valid_only + + def merge(self, other: "SaiCommon"): + super().merge(other) + self.__dict__.update(other.__dict__) \ No newline at end of file