Loading src/device/service/driver_api/_Driver.py +1 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ RESOURCE_ROUTING_POLICIES = '__routing_policies__' RESOURCE_SERVICES = '__services__' RESOURCE_ACL = '__acl__' RESOURCE_INVENTORY = '__inventory__' RESOURCE_RULES = "__rules__" class _Driver: Loading src/device/service/drivers/p4/p4_common.py +101 −1 Original line number Diff line number Diff line Loading @@ -27,10 +27,12 @@ import math import re import socket import ipaddress from typing import Any, Dict, List, Optional, Tuple from ctypes import c_uint16, sizeof import macaddress from common.type_checkers.Checkers import chk_type from common.type_checkers.Checkers import \ chk_attribute, chk_string, chk_type, chk_issubclass try: from .p4_exception import UserBadValueError except ImportError: Loading @@ -38,6 +40,7 @@ except ImportError: P4_ATTR_DEV_ID = "id" P4_ATTR_DEV_NAME = "name" P4_ATTR_DEV_ENDPOINTS = "endpoints" P4_ATTR_DEV_VENDOR = "vendor" P4_ATTR_DEV_HW_VER = "hw_ver" P4_ATTR_DEV_SW_VER = "sw_ver" Loading @@ -50,6 +53,7 @@ P4_VAL_DEF_HW_VER = "BMv2 simple_switch" P4_VAL_DEF_SW_VER = "Stratum" P4_VAL_DEF_TIMEOUT = 60 RESOURCE_ENDPOINTS_ROOT_PATH = "/endpoints" # Logger instance LOGGER = logging.getLogger(__name__) Loading Loading @@ -422,6 +426,28 @@ def parse_action_parameters_from_json(resource): return action_params def parse_replicas_from_json(resource): """ Parse the session replicas within a JSON-based object. :param resource: JSON-based object :return: map of replicas """ if not resource or ("replicas" not in resource): LOGGER.warning( "JSON entry misses 'replicas' list of attributes") return None chk_type("replicas", resource["replicas"], list) replicas = {} for rep in resource["replicas"]: chk_type("egress-port", rep["egress-port"], int) chk_type("instance", rep["instance"], int) replicas[rep["egress-port"]] = rep["instance"] return replicas def parse_integer_list_from_json(resource, resource_list, resource_item): """ Parse the list of integers within a JSON-based object. Loading @@ -443,3 +469,77 @@ def parse_integer_list_from_json(resource, resource_list, resource_item): integers_list.append(item[resource_item]) return integers_list def process_optional_string_field( #TODO: Consider adding this in common methdos as it is taken by the Emulated driver endpoint_data : Dict[str, Any], field_name : str, endpoint_resource_value : Dict[str, Any] ) -> None: field_value = chk_attribute(field_name, endpoint_data, 'endpoint_data', default=None) if field_value is None: return chk_string('endpoint_data.{:s}'.format(field_name), field_value) if len(field_value) > 0: endpoint_resource_value[field_name] = field_value def compose_resource_endpoints(endpoints_list : List[Tuple[str, Any]]): #TODO: Consider adding this in common methods; currently taken by the Emulated driver endpoint_resources = [] for i, endpoint in enumerate(endpoints_list): LOGGER.debug("P4 endpoint {}: {}".format(i, endpoint)) endpoint_resource = compose_resource_endpoint(endpoint) if endpoint_resource is None: continue endpoint_resources.append(endpoint_resource) return endpoint_resources def compose_resource_endpoint(endpoint_data : Dict[str, Any]) -> Optional[Tuple[str, Dict]]: #TODO: Consider adding this in common methods; currently taken by the Emulated driver try: endpoint_uuid = chk_attribute('uuid', endpoint_data, 'endpoint_data') chk_string('endpoint_data.uuid', endpoint_uuid, min_length=1) endpoint_resource_path = RESOURCE_ENDPOINTS_ROOT_PATH endpoint_resource_key = '{:s}/endpoint[{:s}]'.format(endpoint_resource_path, endpoint_uuid) endpoint_resource_value = {'uuid': endpoint_uuid} # Check endpoint's optional string fields process_optional_string_field(endpoint_data, 'name', endpoint_resource_value) process_optional_string_field(endpoint_data, 'type', endpoint_resource_value) process_optional_string_field(endpoint_data, 'context_uuid', endpoint_resource_value) process_optional_string_field(endpoint_data, 'topology_uuid', endpoint_resource_value) return endpoint_resource_key, endpoint_resource_value except: # pylint: disable=bare-except LOGGER.error('Problem composing endpoint({:s})'.format(str(endpoint_data))) return None def compose_resource_rules(rules_list : List[Tuple[str, Any]]): rule_resources = [] for i, rule in enumerate(rules_list): rule_resource = compose_resource_rule(rule_data=rule, rule_cnt=i) if rule_resource is None: continue rule_resources.append(rule_resource) return rule_resources def compose_resource_rule(rule_data : Dict[str, Any], rule_cnt : int) -> Optional[Tuple[str, Dict]]: try: LOGGER.info("Rule: {}".format(rule_data)) rule_resource_key = chk_attribute('resource_key', rule_data, 'rule_data') chk_string('rule_data.resource_key', rule_resource_key, min_length=1) rule_resource_value = chk_attribute('resource_value', rule_data, 'rule_data') chk_issubclass('rule_data.resource_value', rule_resource_value, dict) rule_key_unique = "" if "table" == rule_resource_key: table_name = parse_resource_string_from_json(rule_resource_value, "table-name") assert table_name, "Invalid table name in rule" rule_key_unique = '/{0}s/{0}/{1}[{2}]'.format(rule_resource_key, table_name, rule_cnt) else: msg = f"Parsed an invalid key {rule_resource_key}" LOGGER.error(msg) raise Exception(msg) assert rule_key_unique, "Invalid unique resource key" return rule_key_unique, rule_resource_value except: # pylint: disable=bare-except LOGGER.error('Problem composing rule({:s})'.format(str(rule_data))) return None src/device/service/drivers/p4/p4_context.py +7 −4 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ class P4Type(enum.Enum): meter = 6 direct_meter = 7 controller_packet_metadata = 8 digest = 9 P4Type.table.p4info_name = "tables" Loading @@ -44,6 +45,7 @@ P4Type.direct_counter.p4info_name = "direct_counters" P4Type.meter.p4info_name = "meters" P4Type.direct_meter.p4info_name = "direct_meters" P4Type.controller_packet_metadata.p4info_name = "controller_packet_metadata" P4Type.digest.p4info_name = "digests" for object_type in P4Type: object_type.pretty_name = object_type.name.replace('_', ' ') Loading @@ -58,11 +60,12 @@ class P4RuntimeEntity(enum.Enum): table_entry = 1 action_profile_member = 2 action_profile_group = 3 meter_entry = 4 direct_meter_entry = 5 counter_entry = 6 direct_counter_entry = 7 counter_entry = 4 direct_counter_entry = 5 meter_entry = 6 direct_meter_entry = 7 packet_replication_engine_entry = 8 digest_entry = 9 class Context: Loading src/device/service/drivers/p4/p4_driver.py +177 −63 File changed.Preview size limit exceeded, changes collapsed. Show changes src/device/service/drivers/p4/p4_manager.py +165 −111 Original line number Diff line number Diff line Loading @@ -35,7 +35,8 @@ try: from .p4_common import encode,\ parse_resource_string_from_json, parse_resource_integer_from_json,\ parse_resource_bytes_from_json, parse_match_operations_from_json,\ parse_action_parameters_from_json, parse_integer_list_from_json parse_action_parameters_from_json, parse_integer_list_from_json,\ parse_replicas_from_json from .p4_exception import UserError, InvalidP4InfoError except ImportError: from p4_client import P4RuntimeClient, P4RuntimeException,\ Loading @@ -58,6 +59,7 @@ CONTEXT = Context() CLIENTS = {} # Constant P4 entities KEYS_P4 = [] KEY_TABLE = "table" KEY_ACTION = "action" KEY_ACTION_PROFILE = "action_profile" Loading @@ -66,6 +68,11 @@ KEY_DIR_COUNTER = "direct_counter" KEY_METER = "meter" KEY_DIR_METER = "direct_meter" KEY_CTL_PKT_METADATA = "controller_packet_metadata" KEY_DIGEST = "digest" # Extra resource keys KEY_CLONE_SESSION = "clone_session" KEY_ENDPOINT = "endpoint" def get_context(): Loading @@ -83,19 +90,20 @@ def get_table_type(table): :param table: P4 table :return: P4 table type """ is_ternary = False for m_f in table.match_fields: if m_f.match_type == p4info_pb2.MatchField.EXACT: return p4info_pb2.MatchField.EXACT if m_f.match_type == p4info_pb2.MatchField.LPM: return p4info_pb2.MatchField.LPM if m_f.match_type == p4info_pb2.MatchField.TERNARY: # LPM and range are special forms of ternary if m_f.match_type in [ p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.LPM, p4info_pb2.MatchField.RANGE ]: is_ternary = True if is_ternary: return p4info_pb2.MatchField.TERNARY if m_f.match_type == p4info_pb2.MatchField.RANGE: return p4info_pb2.MatchField.RANGE if m_f.match_type == p4info_pb2.MatchField.OPTIONAL: return p4info_pb2.MatchField.OPTIONAL return None return p4info_pb2.MatchField.EXACT def match_type_to_str(match_type): """ Loading Loading @@ -132,10 +140,10 @@ class P4Manager: self.__id = device_id self.__ip_address = ip_address self.__port = int(port) self.__endpoint = f"{self.__ip_address}:{self.__port}" self.__grpc_endpoint = f"{self.__ip_address}:{self.__port}" self.key_id = ip_address+str(port) CLIENTS[self.key_id] = P4RuntimeClient( self.__id, self.__endpoint, election_id, role_name, ssl_options) self.__id, self.__grpc_endpoint, election_id, role_name, ssl_options) self.__p4info = None self.local_client = CLIENTS[self.key_id] Loading @@ -146,14 +154,14 @@ class P4Manager: # | -> P4 entities self.table_entries = {} self.action_profile_members = {} self.action_profile_groups = {} self.counter_entries = {} self.direct_counter_entries = {} self.meter_entries = {} self.direct_meter_entries = {} self.multicast_groups = {} self.clone_session_entries = {} self.action_profile_members = {} self.action_profile_groups = {} self.multicast_groups = {} def start(self, p4bin_path, p4info_path): """ Loading Loading @@ -234,7 +242,7 @@ class P4Manager: self.__id = None self.__ip_address = None self.__port = None self.__endpoint = None self.__grpc_endpoint = None self.__clear_state() def __clear_state(self): Loading @@ -244,14 +252,14 @@ class P4Manager: :return: void """ self.table_entries.clear() self.action_profile_members.clear() self.action_profile_groups.clear() self.counter_entries.clear() self.direct_counter_entries.clear() self.meter_entries.clear() self.direct_meter_entries.clear() self.multicast_groups.clear() self.clone_session_entries.clear() self.action_profile_members.clear() self.action_profile_groups.clear() self.multicast_groups.clear() self.p4_objects.clear() def __init_objects(self): Loading @@ -264,7 +272,7 @@ class P4Manager: global KEY_TABLE, KEY_ACTION, KEY_ACTION_PROFILE, \ KEY_COUNTER, KEY_DIR_COUNTER, \ KEY_METER, KEY_DIR_METER, \ KEY_CTL_PKT_METADATA KEY_CTL_PKT_METADATA, KEY_DIGEST, KEYS_P4 KEY_TABLE = P4Type.table.name KEY_ACTION = P4Type.action.name Loading @@ -274,12 +282,15 @@ class P4Manager: KEY_METER = P4Type.meter.name KEY_DIR_METER = P4Type.direct_meter.name KEY_CTL_PKT_METADATA = P4Type.controller_packet_metadata.name assert (k for k in [ KEY_DIGEST = P4Type.digest.name KEYS_P4 = [ KEY_TABLE, KEY_ACTION, KEY_ACTION_PROFILE, KEY_COUNTER, KEY_DIR_COUNTER, KEY_METER, KEY_DIR_METER, KEY_CTL_PKT_METADATA ]) KEY_CTL_PKT_METADATA, KEY_DIGEST ] assert (k for k in KEYS_P4) if not self.p4_objects: LOGGER.warning( Loading @@ -292,6 +303,11 @@ class P4Manager: for table in self.p4_objects[KEY_TABLE]: self.table_entries[table.name] = [] if KEY_ACTION_PROFILE in self.p4_objects: for act_prof in self.p4_objects[KEY_ACTION_PROFILE]: self.action_profile_members[act_prof.name] = [] self.action_profile_groups[act_prof.name] = [] if KEY_COUNTER in self.p4_objects: for cnt in self.p4_objects[KEY_COUNTER]: self.counter_entries[cnt.name] = [] Loading @@ -308,11 +324,6 @@ class P4Manager: for d_meter in self.p4_objects[KEY_DIR_METER]: self.direct_meter_entries[d_meter.name] = [] if KEY_ACTION_PROFILE in self.p4_objects: for act_prof in self.p4_objects[KEY_ACTION_PROFILE]: self.action_profile_members[act_prof.name] = [] self.action_profile_groups[act_prof.name] = [] def __discover_objects(self): """ Discover and store all P4 objects. Loading Loading @@ -509,6 +520,20 @@ class P4Manager: return pkt_meta return None def get_digest(self, digest_name): """ Get a digest object by name. :param digest_name: name of a digest object :return: digest object or None """ if KEY_DIGEST not in self.p4_objects: return None for dg in self.p4_objects[KEY_DIGEST]: if dg == digest_name.name: return digest_name return None def get_resource_keys(self): """ Retrieve the available P4 resource keys. Loading Loading @@ -561,14 +586,14 @@ class P4Manager: self.table_entries[table_name] = [] try: for count, table_entry in enumerate( TableEntry(self.local_client, table_name)(action=action_name).read()): LOGGER.debug( "Table %s - Entry %d\n%s", table_name, count, table_entry) entries = TableEntry(self.local_client, table_name).read() assert self.local_client for table_entry in entries: self.table_entries[table_name].append(table_entry) return self.table_entries[table_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get table %s entries: %s", table_name, str(ex)) return [] def table_entries_to_json(self, table_name): Loading Loading @@ -634,10 +659,14 @@ class P4Manager: :return: number of P4 table entries or negative integer upon missing table """ entries = self.get_table_entries(table_name, action_name) if entries is None: return -1 return len(entries) count = 0 try: entries = TableEntry(self.local_client, table_name).read() count = sum(1 for _ in entries) except Exception as e: # pylint: disable=broad-except LOGGER.error("Failed to read entries of table: %s", table_name) return count def count_table_entries_all(self): """ Loading Loading @@ -675,7 +704,7 @@ class P4Manager: metadata = parse_resource_bytes_from_json(json_resource, "metadata") if operation in [WriteOperation.insert, WriteOperation.update]: LOGGER.debug("Table entry to insert/update: %s", json_resource) LOGGER.info("Table entry to insert/update: %s", json_resource) return self.insert_table_entry( table_name=table_name, match_map=match_map, Loading @@ -685,7 +714,7 @@ class P4Manager: metadata=metadata if metadata else None ) if operation == WriteOperation.delete: LOGGER.debug("Table entry to delete: %s", json_resource) LOGGER.info("Table entry to delete: %s", json_resource) return self.delete_table_entry( table_name=table_name, match_map=match_map, Loading Loading @@ -735,7 +764,8 @@ class P4Manager: table_entry.insert() LOGGER.info("Inserted exact table entry: %s", table_entry) except (P4RuntimeException, P4RuntimeWriteException) as ex: raise P4RuntimeException from ex ex_msg = str(ex) LOGGER.warning(ex) # Table entry exists, needs to be modified if "ALREADY_EXISTS" in ex_msg: Loading @@ -744,7 +774,6 @@ class P4Manager: return table_entry def insert_table_entry_ternary(self, table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt=-1, cnt_byte=-1): Loading Loading @@ -788,7 +817,8 @@ class P4Manager: table_entry.insert() LOGGER.info("Inserted ternary table entry: %s", table_entry) except (P4RuntimeException, P4RuntimeWriteException) as ex: raise P4RuntimeException from ex ex_msg = str(ex) LOGGER.error(ex) # Table entry exists, needs to be modified if "ALREADY_EXISTS" in ex_msg: Loading @@ -797,7 +827,6 @@ class P4Manager: return table_entry def insert_table_entry_range(self, table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt=-1, cnt_byte=-1): # pylint: disable=unused-argument Loading @@ -820,7 +849,6 @@ class P4Manager: raise NotImplementedError( "Range-based table insertion not implemented yet") def insert_table_entry_optional(self, table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt=-1, cnt_byte=-1): # pylint: disable=unused-argument Loading Loading @@ -869,32 +897,36 @@ class P4Manager: assert table, \ "P4 pipeline does not implement table " + table_name if not get_table_type(table): table_type = get_table_type(table) if not table_type: msg = f"Table {table_name} is undefined, cannot insert entry" LOGGER.error(msg) raise UserError(msg) LOGGER.debug("Table {}: {}".format(table_name, match_type_to_str(table_type))) # Exact match is supported if get_table_type(table) == p4info_pb2.MatchField.EXACT: if table_type == p4info_pb2.MatchField.EXACT: return self.insert_table_entry_exact( table_name, match_map, action_name, action_params, metadata, cnt_pkt, cnt_byte) # Ternary and LPM matches are supported if get_table_type(table) in \ if table_type in \ [p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.LPM]: return self.insert_table_entry_ternary( table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt, cnt_byte) # TODO: Cover RANGE match # pylint: disable=W0511 if get_table_type(table) == p4info_pb2.MatchField.RANGE: if table_type == p4info_pb2.MatchField.RANGE: return self.insert_table_entry_range( table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt, cnt_byte) # TODO: Cover OPTIONAL match # pylint: disable=W0511 if get_table_type(table) == p4info_pb2.MatchField.OPTIONAL: if table_type == p4info_pb2.MatchField.OPTIONAL: return self.insert_table_entry_optional( table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt, cnt_byte) Loading @@ -917,7 +949,9 @@ class P4Manager: assert table, \ "P4 pipeline does not implement table " + table_name if not get_table_type(table): table_type = get_table_type(table) if not table_type: msg = f"Table {table_name} is undefined, cannot delete entry" LOGGER.error(msg) raise UserError(msg) Loading @@ -930,7 +964,7 @@ class P4Manager: for action_k, action_v in action_params.items(): table_entry.action[action_k] = action_v if get_table_type(table) in \ if table_type in \ [p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.LPM]: if priority == 0: msg = f"Table {table_name} is ternary, priority must be != 0" Loading @@ -938,15 +972,25 @@ class P4Manager: raise UserError(msg) # TODO: Ensure correctness of RANGE & OPTIONAL # pylint: disable=W0511 if get_table_type(table) in \ if table_type in \ [p4info_pb2.MatchField.RANGE, p4info_pb2.MatchField.OPTIONAL]: raise NotImplementedError( "Range and optional-based table deletion not implemented yet") table_entry.priority = priority ex_msg = "" try: table_entry.delete() LOGGER.info("Deleted entry %s from table: %s", table_entry, table_name) except (P4RuntimeException, P4RuntimeWriteException) as ex: ex_msg = str(ex) LOGGER.warning(ex) # Table entry exists, needs to be modified if "NOT_FOUND" in ex_msg: # TODO: No way to discriminate between a modified entry and an actual table miss LOGGER.warning("Table entry was initially modified, thus cannot be removed: %s", table_entry) return table_entry Loading Loading @@ -1172,7 +1216,8 @@ class P4Manager: self.counter_entries[cnt_name].append(cnt_entry) return self.counter_entries[cnt_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get counter %s entries: %s", cnt_name, str(ex)) return [] def counter_entries_to_json(self, cnt_name): Loading Loading @@ -1620,7 +1665,8 @@ class P4Manager: self.meter_entries[meter_name].append(meter_entry) return self.meter_entries[meter_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get meter %s entries: %s", meter_name, str(ex)) return [] def meter_entries_to_json(self, meter_name): Loading Loading @@ -1852,7 +1898,8 @@ class P4Manager: self.direct_meter_entries[d_meter_name].append(d_meter_entry) return self.direct_meter_entries[d_meter_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get direct meter %s entries: %s", d_meter_name, str(ex)) return [] def direct_meter_entries_to_json(self, d_meter_name): Loading Loading @@ -2094,7 +2141,8 @@ class P4Manager: self.action_profile_members[ap_name].append(ap_entry) return self.action_profile_members[ap_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get action profile member %s entries: %s", ap_name, str(ex)) return [] def action_prof_member_entries_to_json(self, ap_name): Loading Loading @@ -2357,7 +2405,8 @@ class P4Manager: self.action_profile_groups[ap_name].append(ap_entry) return self.action_profile_groups[ap_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get action profile group %s entries: %s", ap_name, str(ex)) return [] def count_action_prof_group_entries(self, ap_name): Loading Loading @@ -2880,14 +2929,13 @@ class P4Manager: json_resource, "session-id") if operation in [WriteOperation.insert, WriteOperation.update]: ports = parse_integer_list_from_json( json_resource, "ports", "port") replicas = parse_replicas_from_json(json_resource) LOGGER.debug( "Clone session entry to insert/update: %s", json_resource) return self.insert_clone_session_entry( session_id=session_id, ports=ports replicas=replicas ) if operation == WriteOperation.delete: LOGGER.debug( Loading @@ -2897,22 +2945,24 @@ class P4Manager: ) return None def insert_clone_session_entry(self, session_id, ports): def insert_clone_session_entry(self, session_id, replicas): """ Insert a new clone session. :param session_id: id of a clone session :param ports: list of egress ports to clone session :param replicas: list of egress ports to clone session :return: inserted clone session """ assert session_id > 0, \ "Clone session " + session_id + " must be > 0" assert ports, \ "No clone session ports are provided" assert replicas, \ "No clone session replicas are provided" assert isinstance(replicas, dict), \ "Clone session replicas must be a dictionary" session = CloneSessionEntry(self.local_client, session_id) for p in ports: session.add(p, 1) for eg_port,instance in replicas.items(): session.add(eg_port, instance) ex_msg = "" try: Loading Loading @@ -2943,12 +2993,15 @@ class P4Manager: "Clone session " + session_id + " must be > 0" session = CloneSessionEntry(self.local_client, session_id) try: session.delete() LOGGER.info("Deleted clone session %d", session_id) except (P4RuntimeException, P4RuntimeWriteException) as ex: LOGGER.error(ex) if session_id in self.clone_session_entries: del self.clone_session_entries[session_id] LOGGER.info( "Deleted clone session %d", session_id) return session Loading Loading @@ -3786,6 +3839,7 @@ class _P4EntityBase(_EntityBase): def __init__(self, p4_client, p4_type, entity_type, p4runtime_cls, name=None, modify_only=False): super().__init__(p4_client, entity_type, p4runtime_cls, modify_only) assert self.local_client, "No local P4 client instance" self._p4_type = p4_type if name is None: raise UserError( Loading Loading @@ -5213,7 +5267,7 @@ class _MeterEntryBase(_P4EntityBase): """ def __init__(self, p4_client, *args, **kwargs): super().__init__(*args, **kwargs) super().__init__(p4_client, *args, **kwargs) self._meter_type = self._info.spec.unit self.index = -1 self.cir = -1 Loading Loading
src/device/service/driver_api/_Driver.py +1 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ RESOURCE_ROUTING_POLICIES = '__routing_policies__' RESOURCE_SERVICES = '__services__' RESOURCE_ACL = '__acl__' RESOURCE_INVENTORY = '__inventory__' RESOURCE_RULES = "__rules__" class _Driver: Loading
src/device/service/drivers/p4/p4_common.py +101 −1 Original line number Diff line number Diff line Loading @@ -27,10 +27,12 @@ import math import re import socket import ipaddress from typing import Any, Dict, List, Optional, Tuple from ctypes import c_uint16, sizeof import macaddress from common.type_checkers.Checkers import chk_type from common.type_checkers.Checkers import \ chk_attribute, chk_string, chk_type, chk_issubclass try: from .p4_exception import UserBadValueError except ImportError: Loading @@ -38,6 +40,7 @@ except ImportError: P4_ATTR_DEV_ID = "id" P4_ATTR_DEV_NAME = "name" P4_ATTR_DEV_ENDPOINTS = "endpoints" P4_ATTR_DEV_VENDOR = "vendor" P4_ATTR_DEV_HW_VER = "hw_ver" P4_ATTR_DEV_SW_VER = "sw_ver" Loading @@ -50,6 +53,7 @@ P4_VAL_DEF_HW_VER = "BMv2 simple_switch" P4_VAL_DEF_SW_VER = "Stratum" P4_VAL_DEF_TIMEOUT = 60 RESOURCE_ENDPOINTS_ROOT_PATH = "/endpoints" # Logger instance LOGGER = logging.getLogger(__name__) Loading Loading @@ -422,6 +426,28 @@ def parse_action_parameters_from_json(resource): return action_params def parse_replicas_from_json(resource): """ Parse the session replicas within a JSON-based object. :param resource: JSON-based object :return: map of replicas """ if not resource or ("replicas" not in resource): LOGGER.warning( "JSON entry misses 'replicas' list of attributes") return None chk_type("replicas", resource["replicas"], list) replicas = {} for rep in resource["replicas"]: chk_type("egress-port", rep["egress-port"], int) chk_type("instance", rep["instance"], int) replicas[rep["egress-port"]] = rep["instance"] return replicas def parse_integer_list_from_json(resource, resource_list, resource_item): """ Parse the list of integers within a JSON-based object. Loading @@ -443,3 +469,77 @@ def parse_integer_list_from_json(resource, resource_list, resource_item): integers_list.append(item[resource_item]) return integers_list def process_optional_string_field( #TODO: Consider adding this in common methdos as it is taken by the Emulated driver endpoint_data : Dict[str, Any], field_name : str, endpoint_resource_value : Dict[str, Any] ) -> None: field_value = chk_attribute(field_name, endpoint_data, 'endpoint_data', default=None) if field_value is None: return chk_string('endpoint_data.{:s}'.format(field_name), field_value) if len(field_value) > 0: endpoint_resource_value[field_name] = field_value def compose_resource_endpoints(endpoints_list : List[Tuple[str, Any]]): #TODO: Consider adding this in common methods; currently taken by the Emulated driver endpoint_resources = [] for i, endpoint in enumerate(endpoints_list): LOGGER.debug("P4 endpoint {}: {}".format(i, endpoint)) endpoint_resource = compose_resource_endpoint(endpoint) if endpoint_resource is None: continue endpoint_resources.append(endpoint_resource) return endpoint_resources def compose_resource_endpoint(endpoint_data : Dict[str, Any]) -> Optional[Tuple[str, Dict]]: #TODO: Consider adding this in common methods; currently taken by the Emulated driver try: endpoint_uuid = chk_attribute('uuid', endpoint_data, 'endpoint_data') chk_string('endpoint_data.uuid', endpoint_uuid, min_length=1) endpoint_resource_path = RESOURCE_ENDPOINTS_ROOT_PATH endpoint_resource_key = '{:s}/endpoint[{:s}]'.format(endpoint_resource_path, endpoint_uuid) endpoint_resource_value = {'uuid': endpoint_uuid} # Check endpoint's optional string fields process_optional_string_field(endpoint_data, 'name', endpoint_resource_value) process_optional_string_field(endpoint_data, 'type', endpoint_resource_value) process_optional_string_field(endpoint_data, 'context_uuid', endpoint_resource_value) process_optional_string_field(endpoint_data, 'topology_uuid', endpoint_resource_value) return endpoint_resource_key, endpoint_resource_value except: # pylint: disable=bare-except LOGGER.error('Problem composing endpoint({:s})'.format(str(endpoint_data))) return None def compose_resource_rules(rules_list : List[Tuple[str, Any]]): rule_resources = [] for i, rule in enumerate(rules_list): rule_resource = compose_resource_rule(rule_data=rule, rule_cnt=i) if rule_resource is None: continue rule_resources.append(rule_resource) return rule_resources def compose_resource_rule(rule_data : Dict[str, Any], rule_cnt : int) -> Optional[Tuple[str, Dict]]: try: LOGGER.info("Rule: {}".format(rule_data)) rule_resource_key = chk_attribute('resource_key', rule_data, 'rule_data') chk_string('rule_data.resource_key', rule_resource_key, min_length=1) rule_resource_value = chk_attribute('resource_value', rule_data, 'rule_data') chk_issubclass('rule_data.resource_value', rule_resource_value, dict) rule_key_unique = "" if "table" == rule_resource_key: table_name = parse_resource_string_from_json(rule_resource_value, "table-name") assert table_name, "Invalid table name in rule" rule_key_unique = '/{0}s/{0}/{1}[{2}]'.format(rule_resource_key, table_name, rule_cnt) else: msg = f"Parsed an invalid key {rule_resource_key}" LOGGER.error(msg) raise Exception(msg) assert rule_key_unique, "Invalid unique resource key" return rule_key_unique, rule_resource_value except: # pylint: disable=bare-except LOGGER.error('Problem composing rule({:s})'.format(str(rule_data))) return None
src/device/service/drivers/p4/p4_context.py +7 −4 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ class P4Type(enum.Enum): meter = 6 direct_meter = 7 controller_packet_metadata = 8 digest = 9 P4Type.table.p4info_name = "tables" Loading @@ -44,6 +45,7 @@ P4Type.direct_counter.p4info_name = "direct_counters" P4Type.meter.p4info_name = "meters" P4Type.direct_meter.p4info_name = "direct_meters" P4Type.controller_packet_metadata.p4info_name = "controller_packet_metadata" P4Type.digest.p4info_name = "digests" for object_type in P4Type: object_type.pretty_name = object_type.name.replace('_', ' ') Loading @@ -58,11 +60,12 @@ class P4RuntimeEntity(enum.Enum): table_entry = 1 action_profile_member = 2 action_profile_group = 3 meter_entry = 4 direct_meter_entry = 5 counter_entry = 6 direct_counter_entry = 7 counter_entry = 4 direct_counter_entry = 5 meter_entry = 6 direct_meter_entry = 7 packet_replication_engine_entry = 8 digest_entry = 9 class Context: Loading
src/device/service/drivers/p4/p4_driver.py +177 −63 File changed.Preview size limit exceeded, changes collapsed. Show changes
src/device/service/drivers/p4/p4_manager.py +165 −111 Original line number Diff line number Diff line Loading @@ -35,7 +35,8 @@ try: from .p4_common import encode,\ parse_resource_string_from_json, parse_resource_integer_from_json,\ parse_resource_bytes_from_json, parse_match_operations_from_json,\ parse_action_parameters_from_json, parse_integer_list_from_json parse_action_parameters_from_json, parse_integer_list_from_json,\ parse_replicas_from_json from .p4_exception import UserError, InvalidP4InfoError except ImportError: from p4_client import P4RuntimeClient, P4RuntimeException,\ Loading @@ -58,6 +59,7 @@ CONTEXT = Context() CLIENTS = {} # Constant P4 entities KEYS_P4 = [] KEY_TABLE = "table" KEY_ACTION = "action" KEY_ACTION_PROFILE = "action_profile" Loading @@ -66,6 +68,11 @@ KEY_DIR_COUNTER = "direct_counter" KEY_METER = "meter" KEY_DIR_METER = "direct_meter" KEY_CTL_PKT_METADATA = "controller_packet_metadata" KEY_DIGEST = "digest" # Extra resource keys KEY_CLONE_SESSION = "clone_session" KEY_ENDPOINT = "endpoint" def get_context(): Loading @@ -83,19 +90,20 @@ def get_table_type(table): :param table: P4 table :return: P4 table type """ is_ternary = False for m_f in table.match_fields: if m_f.match_type == p4info_pb2.MatchField.EXACT: return p4info_pb2.MatchField.EXACT if m_f.match_type == p4info_pb2.MatchField.LPM: return p4info_pb2.MatchField.LPM if m_f.match_type == p4info_pb2.MatchField.TERNARY: # LPM and range are special forms of ternary if m_f.match_type in [ p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.LPM, p4info_pb2.MatchField.RANGE ]: is_ternary = True if is_ternary: return p4info_pb2.MatchField.TERNARY if m_f.match_type == p4info_pb2.MatchField.RANGE: return p4info_pb2.MatchField.RANGE if m_f.match_type == p4info_pb2.MatchField.OPTIONAL: return p4info_pb2.MatchField.OPTIONAL return None return p4info_pb2.MatchField.EXACT def match_type_to_str(match_type): """ Loading Loading @@ -132,10 +140,10 @@ class P4Manager: self.__id = device_id self.__ip_address = ip_address self.__port = int(port) self.__endpoint = f"{self.__ip_address}:{self.__port}" self.__grpc_endpoint = f"{self.__ip_address}:{self.__port}" self.key_id = ip_address+str(port) CLIENTS[self.key_id] = P4RuntimeClient( self.__id, self.__endpoint, election_id, role_name, ssl_options) self.__id, self.__grpc_endpoint, election_id, role_name, ssl_options) self.__p4info = None self.local_client = CLIENTS[self.key_id] Loading @@ -146,14 +154,14 @@ class P4Manager: # | -> P4 entities self.table_entries = {} self.action_profile_members = {} self.action_profile_groups = {} self.counter_entries = {} self.direct_counter_entries = {} self.meter_entries = {} self.direct_meter_entries = {} self.multicast_groups = {} self.clone_session_entries = {} self.action_profile_members = {} self.action_profile_groups = {} self.multicast_groups = {} def start(self, p4bin_path, p4info_path): """ Loading Loading @@ -234,7 +242,7 @@ class P4Manager: self.__id = None self.__ip_address = None self.__port = None self.__endpoint = None self.__grpc_endpoint = None self.__clear_state() def __clear_state(self): Loading @@ -244,14 +252,14 @@ class P4Manager: :return: void """ self.table_entries.clear() self.action_profile_members.clear() self.action_profile_groups.clear() self.counter_entries.clear() self.direct_counter_entries.clear() self.meter_entries.clear() self.direct_meter_entries.clear() self.multicast_groups.clear() self.clone_session_entries.clear() self.action_profile_members.clear() self.action_profile_groups.clear() self.multicast_groups.clear() self.p4_objects.clear() def __init_objects(self): Loading @@ -264,7 +272,7 @@ class P4Manager: global KEY_TABLE, KEY_ACTION, KEY_ACTION_PROFILE, \ KEY_COUNTER, KEY_DIR_COUNTER, \ KEY_METER, KEY_DIR_METER, \ KEY_CTL_PKT_METADATA KEY_CTL_PKT_METADATA, KEY_DIGEST, KEYS_P4 KEY_TABLE = P4Type.table.name KEY_ACTION = P4Type.action.name Loading @@ -274,12 +282,15 @@ class P4Manager: KEY_METER = P4Type.meter.name KEY_DIR_METER = P4Type.direct_meter.name KEY_CTL_PKT_METADATA = P4Type.controller_packet_metadata.name assert (k for k in [ KEY_DIGEST = P4Type.digest.name KEYS_P4 = [ KEY_TABLE, KEY_ACTION, KEY_ACTION_PROFILE, KEY_COUNTER, KEY_DIR_COUNTER, KEY_METER, KEY_DIR_METER, KEY_CTL_PKT_METADATA ]) KEY_CTL_PKT_METADATA, KEY_DIGEST ] assert (k for k in KEYS_P4) if not self.p4_objects: LOGGER.warning( Loading @@ -292,6 +303,11 @@ class P4Manager: for table in self.p4_objects[KEY_TABLE]: self.table_entries[table.name] = [] if KEY_ACTION_PROFILE in self.p4_objects: for act_prof in self.p4_objects[KEY_ACTION_PROFILE]: self.action_profile_members[act_prof.name] = [] self.action_profile_groups[act_prof.name] = [] if KEY_COUNTER in self.p4_objects: for cnt in self.p4_objects[KEY_COUNTER]: self.counter_entries[cnt.name] = [] Loading @@ -308,11 +324,6 @@ class P4Manager: for d_meter in self.p4_objects[KEY_DIR_METER]: self.direct_meter_entries[d_meter.name] = [] if KEY_ACTION_PROFILE in self.p4_objects: for act_prof in self.p4_objects[KEY_ACTION_PROFILE]: self.action_profile_members[act_prof.name] = [] self.action_profile_groups[act_prof.name] = [] def __discover_objects(self): """ Discover and store all P4 objects. Loading Loading @@ -509,6 +520,20 @@ class P4Manager: return pkt_meta return None def get_digest(self, digest_name): """ Get a digest object by name. :param digest_name: name of a digest object :return: digest object or None """ if KEY_DIGEST not in self.p4_objects: return None for dg in self.p4_objects[KEY_DIGEST]: if dg == digest_name.name: return digest_name return None def get_resource_keys(self): """ Retrieve the available P4 resource keys. Loading Loading @@ -561,14 +586,14 @@ class P4Manager: self.table_entries[table_name] = [] try: for count, table_entry in enumerate( TableEntry(self.local_client, table_name)(action=action_name).read()): LOGGER.debug( "Table %s - Entry %d\n%s", table_name, count, table_entry) entries = TableEntry(self.local_client, table_name).read() assert self.local_client for table_entry in entries: self.table_entries[table_name].append(table_entry) return self.table_entries[table_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get table %s entries: %s", table_name, str(ex)) return [] def table_entries_to_json(self, table_name): Loading Loading @@ -634,10 +659,14 @@ class P4Manager: :return: number of P4 table entries or negative integer upon missing table """ entries = self.get_table_entries(table_name, action_name) if entries is None: return -1 return len(entries) count = 0 try: entries = TableEntry(self.local_client, table_name).read() count = sum(1 for _ in entries) except Exception as e: # pylint: disable=broad-except LOGGER.error("Failed to read entries of table: %s", table_name) return count def count_table_entries_all(self): """ Loading Loading @@ -675,7 +704,7 @@ class P4Manager: metadata = parse_resource_bytes_from_json(json_resource, "metadata") if operation in [WriteOperation.insert, WriteOperation.update]: LOGGER.debug("Table entry to insert/update: %s", json_resource) LOGGER.info("Table entry to insert/update: %s", json_resource) return self.insert_table_entry( table_name=table_name, match_map=match_map, Loading @@ -685,7 +714,7 @@ class P4Manager: metadata=metadata if metadata else None ) if operation == WriteOperation.delete: LOGGER.debug("Table entry to delete: %s", json_resource) LOGGER.info("Table entry to delete: %s", json_resource) return self.delete_table_entry( table_name=table_name, match_map=match_map, Loading Loading @@ -735,7 +764,8 @@ class P4Manager: table_entry.insert() LOGGER.info("Inserted exact table entry: %s", table_entry) except (P4RuntimeException, P4RuntimeWriteException) as ex: raise P4RuntimeException from ex ex_msg = str(ex) LOGGER.warning(ex) # Table entry exists, needs to be modified if "ALREADY_EXISTS" in ex_msg: Loading @@ -744,7 +774,6 @@ class P4Manager: return table_entry def insert_table_entry_ternary(self, table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt=-1, cnt_byte=-1): Loading Loading @@ -788,7 +817,8 @@ class P4Manager: table_entry.insert() LOGGER.info("Inserted ternary table entry: %s", table_entry) except (P4RuntimeException, P4RuntimeWriteException) as ex: raise P4RuntimeException from ex ex_msg = str(ex) LOGGER.error(ex) # Table entry exists, needs to be modified if "ALREADY_EXISTS" in ex_msg: Loading @@ -797,7 +827,6 @@ class P4Manager: return table_entry def insert_table_entry_range(self, table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt=-1, cnt_byte=-1): # pylint: disable=unused-argument Loading @@ -820,7 +849,6 @@ class P4Manager: raise NotImplementedError( "Range-based table insertion not implemented yet") def insert_table_entry_optional(self, table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt=-1, cnt_byte=-1): # pylint: disable=unused-argument Loading Loading @@ -869,32 +897,36 @@ class P4Manager: assert table, \ "P4 pipeline does not implement table " + table_name if not get_table_type(table): table_type = get_table_type(table) if not table_type: msg = f"Table {table_name} is undefined, cannot insert entry" LOGGER.error(msg) raise UserError(msg) LOGGER.debug("Table {}: {}".format(table_name, match_type_to_str(table_type))) # Exact match is supported if get_table_type(table) == p4info_pb2.MatchField.EXACT: if table_type == p4info_pb2.MatchField.EXACT: return self.insert_table_entry_exact( table_name, match_map, action_name, action_params, metadata, cnt_pkt, cnt_byte) # Ternary and LPM matches are supported if get_table_type(table) in \ if table_type in \ [p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.LPM]: return self.insert_table_entry_ternary( table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt, cnt_byte) # TODO: Cover RANGE match # pylint: disable=W0511 if get_table_type(table) == p4info_pb2.MatchField.RANGE: if table_type == p4info_pb2.MatchField.RANGE: return self.insert_table_entry_range( table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt, cnt_byte) # TODO: Cover OPTIONAL match # pylint: disable=W0511 if get_table_type(table) == p4info_pb2.MatchField.OPTIONAL: if table_type == p4info_pb2.MatchField.OPTIONAL: return self.insert_table_entry_optional( table_name, match_map, action_name, action_params, metadata, priority, cnt_pkt, cnt_byte) Loading @@ -917,7 +949,9 @@ class P4Manager: assert table, \ "P4 pipeline does not implement table " + table_name if not get_table_type(table): table_type = get_table_type(table) if not table_type: msg = f"Table {table_name} is undefined, cannot delete entry" LOGGER.error(msg) raise UserError(msg) Loading @@ -930,7 +964,7 @@ class P4Manager: for action_k, action_v in action_params.items(): table_entry.action[action_k] = action_v if get_table_type(table) in \ if table_type in \ [p4info_pb2.MatchField.TERNARY, p4info_pb2.MatchField.LPM]: if priority == 0: msg = f"Table {table_name} is ternary, priority must be != 0" Loading @@ -938,15 +972,25 @@ class P4Manager: raise UserError(msg) # TODO: Ensure correctness of RANGE & OPTIONAL # pylint: disable=W0511 if get_table_type(table) in \ if table_type in \ [p4info_pb2.MatchField.RANGE, p4info_pb2.MatchField.OPTIONAL]: raise NotImplementedError( "Range and optional-based table deletion not implemented yet") table_entry.priority = priority ex_msg = "" try: table_entry.delete() LOGGER.info("Deleted entry %s from table: %s", table_entry, table_name) except (P4RuntimeException, P4RuntimeWriteException) as ex: ex_msg = str(ex) LOGGER.warning(ex) # Table entry exists, needs to be modified if "NOT_FOUND" in ex_msg: # TODO: No way to discriminate between a modified entry and an actual table miss LOGGER.warning("Table entry was initially modified, thus cannot be removed: %s", table_entry) return table_entry Loading Loading @@ -1172,7 +1216,8 @@ class P4Manager: self.counter_entries[cnt_name].append(cnt_entry) return self.counter_entries[cnt_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get counter %s entries: %s", cnt_name, str(ex)) return [] def counter_entries_to_json(self, cnt_name): Loading Loading @@ -1620,7 +1665,8 @@ class P4Manager: self.meter_entries[meter_name].append(meter_entry) return self.meter_entries[meter_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get meter %s entries: %s", meter_name, str(ex)) return [] def meter_entries_to_json(self, meter_name): Loading Loading @@ -1852,7 +1898,8 @@ class P4Manager: self.direct_meter_entries[d_meter_name].append(d_meter_entry) return self.direct_meter_entries[d_meter_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get direct meter %s entries: %s", d_meter_name, str(ex)) return [] def direct_meter_entries_to_json(self, d_meter_name): Loading Loading @@ -2094,7 +2141,8 @@ class P4Manager: self.action_profile_members[ap_name].append(ap_entry) return self.action_profile_members[ap_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get action profile member %s entries: %s", ap_name, str(ex)) return [] def action_prof_member_entries_to_json(self, ap_name): Loading Loading @@ -2357,7 +2405,8 @@ class P4Manager: self.action_profile_groups[ap_name].append(ap_entry) return self.action_profile_groups[ap_name] except P4RuntimeException as ex: LOGGER.error(ex) LOGGER.error("Failed to get action profile group %s entries: %s", ap_name, str(ex)) return [] def count_action_prof_group_entries(self, ap_name): Loading Loading @@ -2880,14 +2929,13 @@ class P4Manager: json_resource, "session-id") if operation in [WriteOperation.insert, WriteOperation.update]: ports = parse_integer_list_from_json( json_resource, "ports", "port") replicas = parse_replicas_from_json(json_resource) LOGGER.debug( "Clone session entry to insert/update: %s", json_resource) return self.insert_clone_session_entry( session_id=session_id, ports=ports replicas=replicas ) if operation == WriteOperation.delete: LOGGER.debug( Loading @@ -2897,22 +2945,24 @@ class P4Manager: ) return None def insert_clone_session_entry(self, session_id, ports): def insert_clone_session_entry(self, session_id, replicas): """ Insert a new clone session. :param session_id: id of a clone session :param ports: list of egress ports to clone session :param replicas: list of egress ports to clone session :return: inserted clone session """ assert session_id > 0, \ "Clone session " + session_id + " must be > 0" assert ports, \ "No clone session ports are provided" assert replicas, \ "No clone session replicas are provided" assert isinstance(replicas, dict), \ "Clone session replicas must be a dictionary" session = CloneSessionEntry(self.local_client, session_id) for p in ports: session.add(p, 1) for eg_port,instance in replicas.items(): session.add(eg_port, instance) ex_msg = "" try: Loading Loading @@ -2943,12 +2993,15 @@ class P4Manager: "Clone session " + session_id + " must be > 0" session = CloneSessionEntry(self.local_client, session_id) try: session.delete() LOGGER.info("Deleted clone session %d", session_id) except (P4RuntimeException, P4RuntimeWriteException) as ex: LOGGER.error(ex) if session_id in self.clone_session_entries: del self.clone_session_entries[session_id] LOGGER.info( "Deleted clone session %d", session_id) return session Loading Loading @@ -3786,6 +3839,7 @@ class _P4EntityBase(_EntityBase): def __init__(self, p4_client, p4_type, entity_type, p4runtime_cls, name=None, modify_only=False): super().__init__(p4_client, entity_type, p4runtime_cls, modify_only) assert self.local_client, "No local P4 client instance" self._p4_type = p4_type if name is None: raise UserError( Loading Loading @@ -5213,7 +5267,7 @@ class _MeterEntryBase(_P4EntityBase): """ def __init__(self, p4_client, *args, **kwargs): super().__init__(*args, **kwargs) super().__init__(p4_client, *args, **kwargs) self._meter_type = self._info.spec.unit self.index = -1 self.cir = -1 Loading