Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formalize annotated name parsing in DASH. #473

Merged
merged 2 commits into from
Dec 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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