From 0caaeed399790634c90a37fe736e1d4f3ba4b422 Mon Sep 17 00:00:00 2001 From: r12f Date: Sat, 9 Dec 2023 19:13:20 +0000 Subject: [PATCH 1/2] Formalize annotated name parsing. --- dash-pipeline/SAI/sai_api_gen.py | 154 +++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 47 deletions(-) diff --git a/dash-pipeline/SAI/sai_api_gen.py b/dash-pipeline/SAI/sai_api_gen.py index 690043178..c90d654ef 100755 --- a/dash-pipeline/SAI/sai_api_gen.py +++ b/dash-pipeline/SAI/sai_api_gen.py @@ -36,7 +36,7 @@ SAI_TAG = 'Sai' # -# SAI parser decorators +# SAI parser decorators: # def sai_parser_from_p4rt(cls): @staticmethod @@ -61,7 +61,7 @@ def parse(self, p4rt_value, *args, **kwargs): return cls # -# Parsed SAI objects and parsers +# Parsed SAI objects and parsers: # # The SAI objects are parsed from the P4Runtime JSON file, generated by p4 compiler, which contains the information # of all tables and entry information. @@ -71,16 +71,16 @@ def parse(self, p4rt_value, *args, **kwargs): # # At high level, the hiredarchy of the SAI objects is as follows: # -# DASHSAIExtensions: All DASH SAI extensions. -# - SAIEnum: A single enum type. -# - SAIEnumMember: A single enum member within the enum. -# - SAIAPISet: All information for a single SAI API set, such as routing or CA-PA mapping. -# - SAIAPITableData: All information for a single SAI API table used in the API set. -# - SAIAPITableKey: Information of a single P4 table key defined in the table. -# - SAIAPITableAction: Information of a single P4 table action defined used by the table. -# - SAIAPITableActionParam: Information of a single P4 table action parameter used by the action. +# DASHSAIExtensions : All DASH SAI extensions. +# |- SAIEnum : A single enum type. +# | |- SAIEnumMember : A single enum member within the enum. +# |- SAIAPISet : All information for a single SAI API set, such as routing or CA-PA mapping. +# |- SAIAPITableData : All information for a single SAI API table used in the API set. +# |- SAIAPITableKey : Information of a single P4 table key defined in the table. +# |- SAIAPITableAction <-------| : Information of a single P4 table action defined used by the table. +# |- SAIAPITableActionParam -| : Information of a single P4 table action parameter used by the action. # -class SAIType: +class SAITypeSolver: sai_type_to_field = { 'bool': 'booldata', 'sai_uint8_t': 'u8', @@ -96,7 +96,7 @@ class SAIType: @staticmethod def get_sai_default_field_from_type(sai_type): - return SAIType.sai_type_to_field[sai_type] + return SAITypeSolver.sai_type_to_field[sai_type] @staticmethod def get_sai_key_type(key_size, key_header, key_field): @@ -197,6 +197,46 @@ def parse_preamble_if_exists(self, p4rt_object): self.name = preamble['name'] self.alias = preamble['alias'] + @staticmethod + def parse_sai_annotated_name(name, full_name_part_start = 0, full_name_part_end = None): + ''' + This method parses the annotated name and returns the parsed parts. + + Annotated name format in ABNF: + - annotated_name = full_name [":" preferred_name] ["|" api_name] + - full_name = control_block *child_control_block "." object_name + - child_control_block = "." control_block + - control_block = object_name = preferred_name = name + - name = 1*(ALPHA / DIGIT / "_") + + Also if preferred_name is not specified, it will be the same as object_name. + + Annotated name examples: + - dash_ingress.inbound_routing|dash_inbound_routing + - dash_ingress.outbound.acl.stage3:dash_acl_rule|dash_acl + - meta.dash_acl_group_id:dash_acl_group_id + ''' + full_name = "" + preferred_name = None + api_name = None + + if '|' in name: + full_name, api_name = name.split('|') + else: + full_name = name + + if ':' in full_name: + full_name, preferred_name = full_name.split(':') + + full_name_parts = full_name.split('.') + if preferred_name == None: + preferred_name = full_name_parts[-1] + + full_name_parts = full_name_parts[full_name_part_start:full_name_part_end] + full_name = '.'.join(full_name_parts) + + return full_name, preferred_name, api_name + def _parse_sai_object_annotation(self, p4rt_anno_list): ''' This method parses the SAI annotations and populates the SAI object. @@ -229,7 +269,7 @@ def _parse_sai_object_annotation(self, p4rt_anno_list): else: raise ValueError("Unknown attr annotation " + kv['key']) - self.field = SAIType.get_sai_default_field_from_type(self.type) + self.field = SAITypeSolver.get_sai_default_field_from_type(self.type) @sai_parser_from_p4rt @@ -317,12 +357,8 @@ def parse_p4rt(self, p4rt_table_key, ip_is_v6_key_ids): self.name = p4rt_table_key[NAME_TAG] #print("Parsing table key: " + self.name) - full_key_name, self.sai_key_name = self.name.split(':') - key_tuple = full_key_name.split('.') - if len(key_tuple) == 3: - key_struct, key_header, key_field = key_tuple - else: - key_header, key_field = key_tuple + full_key_name, self.sai_key_name, _ = self.parse_sai_annotated_name(self.name, full_name_part_start = -2) + key_header, key_field = full_key_name.split('.') self.bitwidth = p4rt_table_key[BITWIDTH_TAG] @@ -337,13 +373,13 @@ def parse_p4rt(self, p4rt_table_key, ip_is_v6_key_ids): self._parse_sai_object_annotation(p4rt_table_key) else: if self.match_type == 'exact' or self.match_type == 'optional' or self.match_type == 'ternary': - self.type, self.field = SAIType.get_sai_key_type(self.bitwidth, key_header, key_field) + self.type, self.field = SAITypeSolver.get_sai_key_type(self.bitwidth, key_header, key_field) elif self.match_type == 'lpm': - self.type, self.field = SAIType.get_sai_lpm_type(self.bitwidth, key_header, key_field) + self.type, self.field = SAITypeSolver.get_sai_lpm_type(self.bitwidth, key_header, key_field) elif self.match_type == 'list': - self.type, self.field = SAIType.get_sai_list_type(self.bitwidth, key_header, key_field) + self.type, self.field = SAITypeSolver.get_sai_list_type(self.bitwidth, key_header, key_field) elif self.match_type == 'range_list': - self.type, self.field = SAIType.get_sai_range_list_type(self.bitwidth, key_header, key_field) + self.type, self.field = SAITypeSolver.get_sai_range_list_type(self.bitwidth, key_header, key_field) else: raise ValueError(f"match_type={self.match_type} is not supported") @@ -381,7 +417,7 @@ def parse_p4rt(self, p4rt_table_action, sai_enums): } ''' #print("Parsing table action: " + self.name) - self.name = self.name.split('.')[-1] + _, self.name, _ = self.parse_sai_annotated_name(self.name) self.parse_action_params(p4rt_table_action, sai_enums) def parse_action_params(self, p4rt_table_action, sai_enums): @@ -428,7 +464,7 @@ def parse_p4rt(self, p4rt_table_action_param, sai_enums, ip_is_v6_param_ids): if STRUCTURED_ANNOTATIONS_TAG in p4rt_table_action_param: self._parse_sai_object_annotation(p4rt_table_action_param) else: - self.type, self.field = SAIType.get_sai_key_type(int(p4rt_table_action_param[BITWIDTH_TAG]), p4rt_table_action_param[NAME_TAG], p4rt_table_action_param[NAME_TAG]) + self.type, self.field = SAITypeSolver.get_sai_key_type(int(p4rt_table_action_param[BITWIDTH_TAG]), p4rt_table_action_param[NAME_TAG], p4rt_table_action_param[NAME_TAG]) for sai_enum in sai_enums: if self.name == sai_enum.name: self.type = 'sai_' + self.name + '_t' @@ -465,36 +501,53 @@ def __init__(self): self.is_object = None def parse_p4rt(self, p4rt_table, program, all_actions, ignore_tables): - _, table_name = p4rt_table[PREAMBLE_TAG][NAME_TAG].split('.', 1) + ''' + This method parses the P4Runtime table object and populates the SAI API table object. - if table_name in ignore_tables: + Example P4Runtime table object: + + { + "preamble": { + "id": 49812549, + "name": "dash_ingress.outbound.acl.stage2:dash_acl_rule|dash_acl", + "alias": "outbound.acl.stage2:dash_acl_rule|dash_acl" + }, + "matchFields": [ + { + "id": 1, + "name": "meta.dash_acl_group_id:dash_acl_group_id", + "bitwidth": 16, + "matchType": "EXACT", + "structuredAnnotations": [...] + }, + ... + ], + "actionRefs": [ + { "id": 18858683 }, + ... + ], + "directResourceIds": [ 334749261 ], + "size": "1024" + } + ''' + + # The first part of full name is the top level control block name, which is removed for showing better comments as stage. + self.stage, self.name, self.api_name = self.parse_sai_annotated_name(self.name, full_name_part_start = 1) + + # If tables are specified as ignored via CLI or annotations, skip them. + if self.name in ignore_tables: self.ignored = True return self.__parse_sai_table_annotations(p4rt_table[PREAMBLE_TAG]) if self.ignored: - ignore_tables.append(table_name) + ignore_tables.append(self.name) return - print("Parsing table: " + table_name) - - table_name, self.api_name = table_name.split('|') - if ':' in table_name: - stage, group_name = table_name.split(':') - self.name = group_name - self.stage = stage.replace('.' , '_') - else: - self.name = table_name.split('.')[-1] if '.' in table_name else table_name - + print("Parsing table: " + self.name) self.with_counters = self.__table_with_counters(program) - self.__parse_table_keys(p4rt_table) - - for p4rt_table_action in p4rt_table[ACTION_REFS_TAG]: - action_id = p4rt_table_action["id"] - if all_actions[action_id].name != NOACTION and not (SCOPE_TAG in p4rt_table_action and p4rt_table_action[SCOPE_TAG] == 'DEFAULT_ONLY'): - self.__merge_action_params_to_table_params(all_actions[action_id]) - self.actions.append(all_actions[action_id]) + self.__parse_table_actions(p4rt_table, all_actions) if self.is_object == None: if len(self.keys) == 1 and self.keys[0].sai_key_name.endswith(self.name.split('.')[-1] + '_id'): @@ -531,7 +584,7 @@ def __parse_table_keys(self, p4rt_table): ip_is_v6_key_ids = dict() for p4rt_table_key in p4rt_table[MATCH_FIELDS_TAG]: if '_is_v6' in p4rt_table_key[NAME_TAG]: - _, ip_is_v6_key_name = p4rt_table_key[NAME_TAG].split(':') + _, ip_is_v6_key_name, _ = self.parse_sai_annotated_name(p4rt_table_key[NAME_TAG]) ip_is_v6_key_ids[ip_is_v6_key_name] = p4rt_table_key['id'] for p4rt_table_key in p4rt_table[MATCH_FIELDS_TAG]: @@ -551,6 +604,13 @@ def __parse_table_keys(self, p4rt_table): return + def __parse_table_actions(self, p4rt_table, all_actions): + for p4rt_table_action in p4rt_table[ACTION_REFS_TAG]: + action_id = p4rt_table_action["id"] + if all_actions[action_id].name != NOACTION and not (SCOPE_TAG in p4rt_table_action and p4rt_table_action[SCOPE_TAG] == 'DEFAULT_ONLY'): + self.__merge_action_params_to_table_params(all_actions[action_id]) + self.actions.append(all_actions[action_id]) + def __merge_action_params_to_table_params(self, action): ''' Merge all parameters of an action into a single list of parameters for the table. @@ -665,7 +725,7 @@ def __parse_sai_table_action(self, p4rt_actions, sai_enums): return action_data # -# SAI Generators +# SAI Generators: # class SAIFileUpdater: def __init__(self, file_path): From 9f158c33789792d55f9d12be77b96fc237a0c4c1 Mon Sep 17 00:00:00 2001 From: r12f Date: Sat, 9 Dec 2023 19:39:48 +0000 Subject: [PATCH 2/2] Fix unwanted stage. --- dash-pipeline/SAI/sai_api_gen.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dash-pipeline/SAI/sai_api_gen.py b/dash-pipeline/SAI/sai_api_gen.py index c90d654ef..9e056ed63 100755 --- a/dash-pipeline/SAI/sai_api_gen.py +++ b/dash-pipeline/SAI/sai_api_gen.py @@ -533,6 +533,9 @@ def parse_p4rt(self, p4rt_table, program, all_actions, ignore_tables): # The first part of full name is the top level control block name, which is removed for showing better comments as stage. self.stage, self.name, self.api_name = self.parse_sai_annotated_name(self.name, full_name_part_start = 1) + self.stage = self.stage.replace('.', '_') + if "stage" not in self.stage: + self.stage = None # If tables are specified as ignored via CLI or annotations, skip them. if self.name in ignore_tables: