Skip to content

Commit

Permalink
Formalize annotated name parsing in DASH. (#473)
Browse files Browse the repository at this point in the history
Use a normalized way to parse the annotated name in p4 files.

This change will not change any generated files.
  • Loading branch information
r12f authored Dec 10, 2023
1 parent f3be928 commit 2721864
Showing 1 changed file with 110 additions and 47 deletions.
157 changes: 110 additions & 47 deletions dash-pipeline/SAI/sai_api_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
SAI_TAG = 'Sai'

#
# SAI parser decorators
# SAI parser decorators:
#
def sai_parser_from_p4rt(cls):
@staticmethod
Expand All @@ -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.
Expand All @@ -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',
Expand All @@ -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):
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]

Expand All @@ -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")

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -465,36 +501,56 @@ 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)
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:
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'):
Expand Down Expand Up @@ -531,7 +587,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]:
Expand All @@ -551,6 +607,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.
Expand Down Expand Up @@ -665,7 +728,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):
Expand Down

0 comments on commit 2721864

Please sign in to comment.