diff --git a/dump/plugins/copp.py b/dump/plugins/copp.py new file mode 100644 index 000000000000..9cc015945ab9 --- /dev/null +++ b/dump/plugins/copp.py @@ -0,0 +1,351 @@ +import re +from dump.match_infra import MatchRequest +from dump.helper import create_template_dict +from dump.helper import handle_error, handle_multiple_keys_matched_error +from .executor import Executor + +TRAP_ID_MAP = { + "stp": "SAI_HOSTIF_TRAP_TYPE_STP", + "lacp": "SAI_HOSTIF_TRAP_TYPE_LACP", + "eapol": "SAI_HOSTIF_TRAP_TYPE_EAPOL", + "lldp": "SAI_HOSTIF_TRAP_TYPE_LLDP", + "pvrst": "SAI_HOSTIF_TRAP_TYPE_PVRST", + "igmp_query": "SAI_HOSTIF_TRAP_TYPE_IGMP_TYPE_QUERY", + "igmp_leave": "SAI_HOSTIF_TRAP_TYPE_IGMP_TYPE_LEAVE", + "igmp_v1_report": "SAI_HOSTIF_TRAP_TYPE_IGMP_TYPE_V1_REPORT", + "igmp_v2_report": "SAI_HOSTIF_TRAP_TYPE_IGMP_TYPE_V2_REPORT", + "igmp_v3_report": "SAI_HOSTIF_TRAP_TYPE_IGMP_TYPE_V3_REPORT", + "sample_packet": "SAI_HOSTIF_TRAP_TYPE_SAMPLEPACKET", + "switch_cust_range": "SAI_HOSTIF_TRAP_TYPE_SWITCH_CUSTOM_RANGE_BASE", + "arp_req": "SAI_HOSTIF_TRAP_TYPE_ARP_REQUEST", + "arp_resp": "SAI_HOSTIF_TRAP_TYPE_ARP_RESPONSE", + "dhcp": "SAI_HOSTIF_TRAP_TYPE_DHCP", + "ospf": "SAI_HOSTIF_TRAP_TYPE_OSPF", + "pim": "SAI_HOSTIF_TRAP_TYPE_PIM", + "vrrp": "SAI_HOSTIF_TRAP_TYPE_VRRP", + "bgp": "SAI_HOSTIF_TRAP_TYPE_BGP", + "dhcpv6": "SAI_HOSTIF_TRAP_TYPE_DHCPV6", + "ospfv6": "SAI_HOSTIF_TRAP_TYPE_OSPFV6", + "vrrpv6": "SAI_HOSTIF_TRAP_TYPE_VRRPV6", + "bgpv6": "SAI_HOSTIF_TRAP_TYPE_BGPV6", + "neigh_discovery": "SAI_HOSTIF_TRAP_TYPE_IPV6_NEIGHBOR_DISCOVERY", + "mld_v1_v2": "SAI_HOSTIF_TRAP_TYPE_IPV6_MLD_V1_V2", + "mld_v1_report": "SAI_HOSTIF_TRAP_TYPE_IPV6_MLD_V1_REPORT", + "mld_v1_done": "SAI_HOSTIF_TRAP_TYPE_IPV6_MLD_V1_DONE", + "mld_v2_report": "SAI_HOSTIF_TRAP_TYPE_MLD_V2_REPORT", + "ip2me": "SAI_HOSTIF_TRAP_TYPE_IP2ME", + "ssh": "SAI_HOSTIF_TRAP_TYPE_SSH", + "snmp": "SAI_HOSTIF_TRAP_TYPE_SNMP", + "router_custom_range": "SAI_HOSTIF_TRAP_TYPE_ROUTER_CUSTOM_RANGE_BASE", + "l3_mtu_error": "SAI_HOSTIF_TRAP_TYPE_L3_MTU_ERROR", + "ttl_error": "SAI_HOSTIF_TRAP_TYPE_TTL_ERROR", + "udld": "SAI_HOSTIF_TRAP_TYPE_UDLD", + "bfd": "SAI_HOSTIF_TRAP_TYPE_BFD", + "bfdv6": "SAI_HOSTIF_TRAP_TYPE_BFDV6", + "src_nat_miss": "SAI_HOSTIF_TRAP_TYPE_SNAT_MISS", + "dest_nat_miss": "SAI_HOSTIF_TRAP_TYPE_DNAT_MISS" +} + +CFG_COPP_TRAP_TABLE_NAME = "COPP_TRAP" +CFG_COPP_GROUP_TABLE_NAME = "COPP_GROUP" +APP_COPP_TABLE_NAME = "COPP_TABLE" + +ASIC_DB_PREFIX = "ASIC_STATE" + +ASIC_TRAP_OBJ = ASIC_DB_PREFIX + ":" + "SAI_OBJECT_TYPE_HOSTIF_TRAP" +ASIC_TRAP_GROUP_OBJ = ASIC_DB_PREFIX + ":" + "SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP" +ASIC_HOSTIF_TABLE_ENTRY = ASIC_DB_PREFIX + ":" + "SAI_OBJECT_TYPE_HOSTIF_TABLE_ENTRY" +ASIC_HOSTIF = ASIC_DB_PREFIX + ":" + "SAI_OBJECT_TYPE_HOSTIF" +ASIC_POLICER_OBJ = ASIC_DB_PREFIX + ":" + "SAI_OBJECT_TYPE_POLICER" +ASIC_QUEUE_OBJ = ASIC_DB_PREFIX + ":" + "SAI_OBJECT_TYPE_QUEUE" + + +class Copp(Executor): + + ARG_NAME = "trap_id" + CONFIG_FILE = "/etc/sonic/copp_cfg.json" + + def __init__(self, match_engine=None): + super().__init__(match_engine) + self.copp_trap_cfg_key = "" + self.trap_group = "" + self.trap_id = "" + self.ns = "" + self.ret_temp = {} + + def fetch_all_trap_ids(self, ret): + traps = [] + if not ret["error"]: + for key in ret["keys"]: + temp_ids = ret["return_values"][key]["trap_ids"].split(",") + for id in temp_ids: + traps.append(id) + return traps + + def get_all_args(self, ns): + all_trap_ids = set() + req = MatchRequest(file=Copp.CONFIG_FILE, table=CFG_COPP_TRAP_TABLE_NAME, return_fields=["trap_ids"], ns=ns) + ret = self.match_engine.fetch(req) + all_trap_ids.update(self.fetch_all_trap_ids(ret)) + + req = MatchRequest(db="CONFIG_DB", table=CFG_COPP_TRAP_TABLE_NAME, return_fields=["trap_ids"], ns=ns) + ret = self.match_engine.fetch(req) + all_trap_ids.update(self.fetch_all_trap_ids(ret)) + return list(all_trap_ids) + + def execute(self, params): + self.ret_temp = create_template_dict(dbs=["CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB", "CONFIG_FILE"]) + self.ns = params["namespace"] + self.trap_id = params[Copp.ARG_NAME] + self.copp_trap_cfg_key = "" + self.trap_group = "" + self.handle_user_and_default_config() + self.handle_appl_db() + self.handle_asic_db() + self.handle_state_db() + return self.ret_temp + + def handle_state_db(self): + req = MatchRequest(db="STATE_DB", table="COPP_TRAP_TABLE", key_pattern=self.copp_trap_cfg_key) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + self.ret_temp["STATE_DB"]["keys"].append(ret["keys"][0]) + else: + self.ret_temp["STATE_DB"]["tables_not_found"].append("COPP_TRAP_TABLE") + + req = MatchRequest(db="STATE_DB", table="COPP_GROUP_TABLE", key_pattern=self.trap_group) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + self.ret_temp["STATE_DB"]["keys"].append(ret["keys"][0]) + else: + self.ret_temp["STATE_DB"]["tables_not_found"].append("COPP_GROUP_TABLE") + + def handle_appl_db(self): + req = MatchRequest(db="APPL_DB", table=APP_COPP_TABLE_NAME, key_pattern="*", field="trap_ids", + value=self.trap_id, match_entire_list=False, return_fields=["trap_group"]) + ret = self.match_engine.fetch(req) + tg = "" + if not ret["error"] and len(ret["keys"]) > 0: + if len(ret["keys"]) > 1: + err_str_tup = ("ERROR: Multiple COPP_TABLE Keys found for the trap_id {} in", + "the APPL_DB, keys found: {}") + err_str = " ".join(err_str_tup) + err_str = err_str.format(self.trap_id, str(ret["keys"])) + handle_multiple_keys_matched_error(err_str, ret["keys"][0]) + self.ret_temp["APPL_DB"]["keys"].append(ret["keys"][0]) + tg = ret["return_values"][ret["keys"][0]]["trap_group"] + else: + self.ret_temp["APPL_DB"]["tables_not_found"].append(APP_COPP_TABLE_NAME) + + if tg != self.trap_group and not self.trap_group and not tg: + err_str_tup = ("The Associated Trap_group for the trap_id found in APPL", + "and CONFIG_DB/CONFIG_FILE did not match.", + "In APPL_DB: {}, CONFIG_DB: {}", + "\n Proceding with the trap group found in APPL DB") + err_str = " ".join(err_str_tup) + err_str = err_str.format(tg, self.trap_group) + handle_error(err_str, False) + + if tg: + self.trap_group = tg + + def handle_asic_db(self): + if self.trap_id not in TRAP_ID_MAP: + err_str = "Invalid Trap Id {} is provided, no corresponding SAI_TRAP_OBJ is found" + handle_error(err_str.format(self.trap_id), False) + sai_trap_id = "" + else: + sai_trap_id = TRAP_ID_MAP[self.trap_id] + sai_trap, sai_trap_grp = self.__get_asic_hostif_trap_obj(sai_trap_id) + sai_queue, sai_policer = self.__get_asic_hostif_trap_group_obj(sai_trap_grp) + self.__get_asic_policer_obj(sai_policer) + self.__get_asic_queue_obj(sai_queue) + sai_hostif_vid = self.__get_asic_hostif_entry_obj(sai_trap) + self.__get_asic_hostif_obj(sai_hostif_vid) + + def __get_asic_hostif_trap_obj(self, sai_trap_id): + if not sai_trap_id: + self.ret_temp["ASIC_DB"]["tables_not_found"].append(ASIC_TRAP_OBJ) + return "", "" + + req = MatchRequest(db="ASIC_DB", table=ASIC_TRAP_OBJ, field="SAI_HOSTIF_TRAP_ATTR_TRAP_TYPE", value=sai_trap_id, + ns=self.ns, return_fields=["SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP"]) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + if len(ret["keys"]) > 1: + err_str = "ERROR: Multiple {} Keys found for the trap_sai_type {} in the ASIC_DB, keys found: {}".format(ASIC_TRAP_OBJ, trap_sai_obj, str(ret["keys"])) + handle_multiple_keys_matched_error(err_str, ret["keys"][0]) + trap_asic_key = ret["keys"][0] + self.ret_temp["ASIC_DB"]["keys"].append(trap_asic_key) + return trap_asic_key, ret["return_values"][trap_asic_key]["SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP"] + else: + self.ret_temp["ASIC_DB"]["tables_not_found"].append(ASIC_TRAP_OBJ) + return "", "" + + def __get_asic_hostif_trap_group_obj(self, trap_group_obj): + if not trap_group_obj: + self.ret_temp["ASIC_DB"]["tables_not_found"].append(ASIC_TRAP_GROUP_OBJ) + return "", "" + + req = MatchRequest(db="ASIC_DB", table=ASIC_TRAP_GROUP_OBJ, key_pattern=trap_group_obj, ns=self.ns, + return_fields=["SAI_HOSTIF_TRAP_GROUP_ATTR_QUEUE", "SAI_HOSTIF_TRAP_GROUP_ATTR_POLICER"]) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + trap_group_asic_key = ret["keys"][0] + self.ret_temp["ASIC_DB"]["keys"].append(trap_group_asic_key) + SAI_QUEUE_INDEX = ret["return_values"][trap_group_asic_key]["SAI_HOSTIF_TRAP_GROUP_ATTR_QUEUE"] + SAI_POLICER_OBJ = ret["return_values"][trap_group_asic_key]["SAI_HOSTIF_TRAP_GROUP_ATTR_POLICER"] + return SAI_QUEUE_INDEX, SAI_POLICER_OBJ + else: + self.ret_temp["ASIC_DB"]["tables_not_found"].append(ASIC_TRAP_GROUP_OBJ) + return "", "" + + def __get_asic_policer_obj(self, policer_sai_obj): + # Not adding to tables_not_found because, some of the trap_ids might not have a policer associated with them + # and that is expected + if not policer_sai_obj: + return + req = MatchRequest(db="ASIC_DB", table=ASIC_POLICER_OBJ, key_pattern=policer_sai_obj, ns=self.ns) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + self.ret_temp["ASIC_DB"]["keys"].append(ret["keys"][0]) + + def __get_asic_queue_obj(self, queue_sai_obj): + # Not adding tp tables_not_found because of the type of reason specified for policer obj + if not queue_sai_obj: + return + req = MatchRequest(db="ASIC_DB", table=ASIC_QUEUE_OBJ, field="SAI_QUEUE_ATTR_INDEX", value=queue_sai_obj, ns=self.ns) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + self.ret_temp["ASIC_DB"]["keys"].append(ret["keys"][0]) + + def __get_asic_hostif_entry_obj(self, sai_trap_key): + # Not adding tp tables_not_found because of the type of reason specified for policer obj + if not sai_trap_key: + return + matches = re.findall(r"oid:0x\w{1,14}", sai_trap_key) + if matches: + sai_trap_vid = matches[0] + else: + return + req = MatchRequest(db="ASIC_DB", table=ASIC_HOSTIF_TABLE_ENTRY, field="SAI_HOSTIF_TABLE_ENTRY_ATTR_TRAP_ID", + value=sai_trap_vid, return_fields=["SAI_HOSTIF_TABLE_ENTRY_ATTR_HOST_IF"], ns=self.ns) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + sai_hostif_table_entry_key = ret["keys"][0] + self.ret_temp["ASIC_DB"]["keys"].append(sai_hostif_table_entry_key) + sai_hostif_vid = ret["return_values"][sai_hostif_table_entry_key]["SAI_HOSTIF_TABLE_ENTRY_ATTR_HOST_IF"] + return sai_hostif_vid + + def __get_asic_hostif_obj(self, sai_hostif_vid): + # Not adding tp tables_not_found because of the type of reason specified for policer obj + if not sai_hostif_vid: + return + req = MatchRequest(db="ASIC_DB", table=ASIC_HOSTIF, key_pattern=sai_hostif_vid, ns=self.ns) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + self.ret_temp["ASIC_DB"]["keys"].append(ret["keys"][0]) + + # When the user writes config to CONFIG_DB, that takes precedence over default config + def handle_user_and_default_config(self): + # ------------------ Find trap_id_key and trap_group from both the sources + # Search for any user-provided config for the trap-id provided + trap_id_key_db, trap_group_db = self.__find_trap_id_in_db() + trap_id_key_cf, trap_group_cf = "", "" + if not trap_id_key_db: # If nothing is found, search in CONFIG_FILE + trap_id_key_cf, trap_group_cf = self.__find_trap_id_in_conf_file() + elif trap_id_key_db and not trap_group_db: + _, trap_group_cf = self.__find_trap_id_in_conf_file(trap_id_key_db.split("|")[-1], False) + + # ------------------ Find any diff and fill the return dictionary with COPP_TRAP keys + if trap_id_key_db: + self.ret_temp["CONFIG_DB"]["keys"].append(trap_id_key_db) + self.copp_trap_cfg_key = trap_id_key_db.split("|")[-1] + id_in_file, _ = self.__find_trap_id_in_conf_file(self.copp_trap_cfg_key, False) + if id_in_file == trap_id_key_db: # If any diff + self.ret_temp["CONFIG_FILE"]["keys"].append(trap_id_key_db) + elif trap_id_key_cf: + self.ret_temp["CONFIG_FILE"]["keys"].append(trap_id_key_cf) + self.copp_trap_cfg_key = trap_id_key_cf.split("|")[-1] + id_in_file, _ = self.__find_trap_id_in_db(self.copp_trap_cfg_key, False) + if id_in_file == trap_id_key_cf: # Find the diff, if any, inside the CONFIG DB + self.ret_temp["CONFIG_DB"]["keys"].append(trap_id_key_cf) + else: + self.ret_temp["CONFIG_FILE"]["tables_not_found"].append(CFG_COPP_TRAP_TABLE_NAME) + self.ret_temp["CONFIG_DB"]["tables_not_found"].append(CFG_COPP_TRAP_TABLE_NAME) + + # ------------------ Find any diff and fill the return dictionary with COPP_GROUP keys + if trap_group_db: # Preference to User-provided Config + self.trap_group = trap_group_db + trap_in_cfg_file = False + elif trap_group_cf: # Then, the preference to the group found in CFG_File + self.trap_group = trap_group_cf + trap_in_cfg_file = True + else: + self.ret_temp["CONFIG_FILE"]["tables_not_found"].append(CFG_COPP_GROUP_TABLE_NAME) + self.ret_temp["CONFIG_DB"]["tables_not_found"].append(CFG_COPP_GROUP_TABLE_NAME) + return + tg_in_default = self.__fill_trap_group_in_conf_file(trap_in_cfg_file) # Check if the trap_group in cfg_file + # Trap_group is expected to be in cfg_db when + # 1) Trap_group is not found in cfg_file + # 2) Trap_ID was provided by the user i.e trap_in_cfg_file = False + # Otherwise, we're just looking for diff + self.__fill_trap_group_in_conf_db(not(tg_in_default) and not(trap_in_cfg_file)) + + def __fill_trap_group_in_conf_file(self, not_found_report=True): + req = MatchRequest(table=CFG_COPP_GROUP_TABLE_NAME, key_pattern=self.trap_group, ns=self.ns, file=Copp.CONFIG_FILE) + ret = self.match_engine.fetch(req) + key_tg = "" + if not ret["error"] and len(ret["keys"]) > 0: + key_tg = ret["keys"][0] + self.ret_temp["CONFIG_FILE"]["keys"].append(key_tg) + return True + elif not_found_report: + self.ret_temp["CONFIG_FILE"]["tables_not_found"].append(CFG_COPP_GROUP_TABLE_NAME) + return False + + def __fill_trap_group_in_conf_db(self, not_found_report=True): + req = MatchRequest(table=CFG_COPP_GROUP_TABLE_NAME, key_pattern=self.trap_group, ns=self.ns, db="CONFIG_DB") + ret = self.match_engine.fetch(req) + key_tg = "" + if not ret["error"] and len(ret["keys"]) > 0: + key_tg = ret["keys"][0] + self.ret_temp["CONFIG_DB"]["keys"].append(key_tg) + return True + elif not_found_report: + self.ret_temp["CONFIG_DB"]["tables_not_found"].append(CFG_COPP_GROUP_TABLE_NAME) + return False + + def __find_trap_id_in_conf_file(self, key_ptrn="*", do_fv_check=True): + field_, value_ = None, None + if do_fv_check: + field_ = "trap_ids" + value_ = self.trap_id + req = MatchRequest(file=Copp.CONFIG_FILE, table=CFG_COPP_TRAP_TABLE_NAME, key_pattern=key_ptrn, match_entire_list=False, + ns=self.ns, return_fields=["trap_group"], field=field_, value=value_) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + if len(ret["keys"]) > 1: + err_str = "ERROR (AMBIGUITY): Multiple COPP_TRAP Keys found for the trap_id {} in the copp_init file, i.e. {}".format(self.trap_id, str(ret["keys"])) + handle_multiple_keys_matched_error(err_str, ret["keys"][0]) + key_copp_trap = ret["keys"][0] + return key_copp_trap, ret["return_values"][key_copp_trap]["trap_group"] + else: + return "", "" + + def __find_trap_id_in_db(self, key_ptrn="*", do_fv_check=True): + field_, value_ = None, None + if do_fv_check: + field_ = "trap_ids" + value_ = self.trap_id + req = MatchRequest(db="CONFIG_DB", table=CFG_COPP_TRAP_TABLE_NAME, key_pattern=key_ptrn, match_entire_list=False, + ns=self.ns, return_fields=["trap_group"], field=field_, value=value_) + ret = self.match_engine.fetch(req) + if not ret["error"] and len(ret["keys"]) > 0: + if len(ret["keys"]) > 1: + err_str = "Multiple COPP_TRAP Keys found for the trap_id {} in the CONFIG_DB, i.e. {}".format(self.trap_id, str(ret["keys"])) + handle_multiple_keys_matched_error(err_str, ret["keys"][0]) + key_copp_trap = ret["keys"][0] + return key_copp_trap, ret["return_values"][key_copp_trap]["trap_group"] + else: + return "", "" diff --git a/tests/dump_input/copp/appl_db.json b/tests/dump_input/copp/appl_db.json new file mode 100644 index 000000000000..71a9a811d43f --- /dev/null +++ b/tests/dump_input/copp/appl_db.json @@ -0,0 +1,36 @@ +{ + "COPP_TABLE:queue4_group2": { + "cbs": "6000", + "cir": "6000", + "meter_type": "packets", + "mode": "sr_tcm", + "queue": "1", + "red_action": "drop", + "trap_action": "trap", + "trap_priority": "1", + "trap_ids": "arp_req,arp_resp,neigh_discovery,snmp" + }, + "COPP_TABLE:queue2_group1": { + "cbs": "2000", + "cir": "2000", + "meter_type": "packets", + "mode": "sr_tcm", + "queue": "1", + "red_action": "drop", + "trap_action": "trap", + "trap_priority": "1", + "trap_ids": "sample_packet" + }, + "COPP_TABLE:queue4_group1": { + "trap_action":"trap", + "trap_priority":"4", + "queue": "4", + "trap_ids": "bgp,bgpv6,ospfv6,ospf" + }, + "COPP_TABLE:queue1_group2":{ + "trap_ids": "src_nat_miss", + "trap_action":"trap", + "trap_priority":"4", + "queue": "4" + } +} diff --git a/tests/dump_input/copp/asic_db.json b/tests/dump_input/copp/asic_db.json new file mode 100644 index 000000000000..b41f2d2a1cc6 --- /dev/null +++ b/tests/dump_input/copp/asic_db.json @@ -0,0 +1,72 @@ +{ + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004dc": { + "SAI_HOSTIF_TRAP_ATTR_PACKET_ACTION": "SAI_PACKET_ACTION_COPY", + "SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP": "oid:0x110000000004da", + "SAI_HOSTIF_TRAP_ATTR_TRAP_TYPE": "SAI_HOSTIF_TRAP_TYPE_SNMP" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004da": { + "SAI_HOSTIF_TRAP_GROUP_ATTR_POLICER": "oid:0x120000000004db", + "SAI_HOSTIF_TRAP_GROUP_ATTR_QUEUE": "4" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_POLICER:oid:0x120000000004db": { + "SAI_POLICER_ATTR_CBS": "600", + "SAI_POLICER_ATTR_CIR": "600", + "SAI_POLICER_ATTR_METER_TYPE": "SAI_METER_TYPE_PACKETS", + "SAI_POLICER_ATTR_MODE": "SAI_POLICER_MODE_SR_TCM", + "SAI_POLICER_ATTR_RED_PACKET_ACTION": "SAI_PACKET_ACTION_DROP" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a0": { + "SAI_QUEUE_ATTR_INDEX": "4", + "SAI_QUEUE_ATTR_TYPE": "SAI_QUEUE_TYPE_UNICAST" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004dd": { + "SAI_HOSTIF_TRAP_ATTR_PACKET_ACTION": "SAI_PACKET_ACTION_COPY", + "SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP": "oid:0x110000000004da", + "SAI_HOSTIF_TRAP_ATTR_TRAP_TYPE": "SAI_HOSTIF_TRAP_TYPE_ARP_RESPONSE" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004de": { + "SAI_HOSTIF_TRAP_ATTR_PACKET_ACTION": "SAI_PACKET_ACTION_COPY", + "SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP": "oid:0x110000000004db", + "SAI_HOSTIF_TRAP_ATTR_TRAP_TYPE": "SAI_HOSTIF_TRAP_TYPE_SAMPLEPACKET" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004db": { + "SAI_HOSTIF_TRAP_GROUP_ATTR_POLICER": "oid:0x120000000004dc", + "SAI_HOSTIF_TRAP_GROUP_ATTR_QUEUE": "1" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_POLICER:oid:0x120000000004dc": { + "SAI_POLICER_ATTR_CBS": "2000", + "SAI_POLICER_ATTR_CIR": "2000", + "SAI_POLICER_ATTR_METER_TYPE": "SAI_METER_TYPE_PACKETS", + "SAI_POLICER_ATTR_MODE": "SAI_POLICER_MODE_SR_TCM", + "SAI_POLICER_ATTR_RED_PACKET_ACTION": "SAI_PACKET_ACTION_DROP" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a1": { + "SAI_QUEUE_ATTR_INDEX": "1", + "SAI_QUEUE_ATTR_TYPE": "SAI_QUEUE_TYPE_UNICAST" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TABLE_ENTRY:oid:0x230000000004d8": { + "SAI_HOSTIF_TABLE_ENTRY_ATTR_TYPE" : "SAI_HOSTIF_TABLE_ENTRY_TYPE_TRAP_ID", + "SAI_HOSTIF_TABLE_ENTRY_ATTR_TRAP_ID" : "oid:0x220000000004de", + "SAI_HOSTIF_TABLE_ENTRY_ATTR_CHANNEL_TYPE" : "SAI_HOSTIF_TABLE_ENTRY_CHANNEL_TYPE_GENETLINK", + "SAI_HOSTIF_TABLE_ENTRY_ATTR_HOST_IF" : "oid:0xd0000000004d6" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd0000000004d6": { + "SAI_HOSTIF_ATTR_GENETLINK_MCGRP_NAME" : "packets", + "SAI_HOSTIF_ATTR_TYPE" : "SAI_HOSTIF_TYPE_GENETLINK", + "SAI_HOSTIF_ATTR_NAME" : "psample" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004df": { + "SAI_HOSTIF_TRAP_ATTR_PACKET_ACTION": "SAI_PACKET_ACTION_COPY", + "SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP": "oid:0x110000000004db", + "SAI_HOSTIF_TRAP_ATTR_TRAP_TYPE": "SAI_HOSTIF_TRAP_TYPE_OSPFV6" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004e0": { + "SAI_HOSTIF_TRAP_ATTR_PACKET_ACTION": "SAI_PACKET_ACTION_COPY", + "SAI_HOSTIF_TRAP_ATTR_TRAP_GROUP": "oid:0x110000000004e0", + "SAI_HOSTIF_TRAP_ATTR_TRAP_TYPE": "SAI_HOSTIF_TRAP_TYPE_SNAT_MISS" + }, + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004e0": { + "NONE": "NONE", + "SAI_HOSTIF_TRAP_GROUP_ATTR_QUEUE": "1" + } +} diff --git a/tests/dump_input/copp/config_db.json b/tests/dump_input/copp/config_db.json new file mode 100644 index 000000000000..752f35ad8530 --- /dev/null +++ b/tests/dump_input/copp/config_db.json @@ -0,0 +1,17 @@ +{ + "COPP_TRAP|snmp_grp": { + "trap_ids": "snmp,bfd", + "trap_group": "queue4_group2" + }, + "COPP_GROUP|queue2_group1":{ + "cbs": "2000", + "cir": "2000" + }, + "COPP_TRAP|vrrpv6": { + "trap_ids": "vrrpv6", + "trap_group": "queue5_group2" + }, + "COPP_TRAP|bgp":{ + "trap_ids": "ospfv6,ospf" + } +} diff --git a/tests/dump_input/copp/state_db.json b/tests/dump_input/copp/state_db.json new file mode 100644 index 000000000000..5589597c05af --- /dev/null +++ b/tests/dump_input/copp/state_db.json @@ -0,0 +1,23 @@ +{ + "COPP_TRAP_TABLE|snmp_grp": { + "state": "ok" + }, + "COPP_GROUP_TABLE|queue4_group2": { + "state": "ok" + }, + "COPP_TRAP_TABLE|arp": { + "state": "ok" + }, + "COPP_TRAP_TABLE|sflow": { + "state": "ok" + }, + "COPP_GROUP_TABLE|queue2_group1": { + "state": "ok" + }, + "COPP_GROUP_TABLE|queue4_group1": { + "state": "ok" + }, + "COPP_TRAP_TABLE|bgp": { + "state": "ok" + } +} diff --git a/tests/dump_tests/module_tests/copp_test.py b/tests/dump_tests/module_tests/copp_test.py new file mode 100644 index 000000000000..53a4293e2e45 --- /dev/null +++ b/tests/dump_tests/module_tests/copp_test.py @@ -0,0 +1,227 @@ +import json +import os +import sys +import jsonpatch +import unittest +import pytest +from deepdiff import DeepDiff +from mock import patch +from dump.helper import create_template_dict, sort_lists +from dump.plugins.copp import Copp +from dump.match_infra import MatchEngine, ConnectionPool +from swsscommon.swsscommon import SonicV2Connector + +# Location for dedicated db's used for UT +module_tests_path = os.path.dirname(__file__) +dump_tests_path = os.path.join(module_tests_path, "../") +tests_path = os.path.join(dump_tests_path, "../") +dump_test_input = os.path.join(tests_path, "dump_input") +copp_files_path = os.path.join(dump_test_input, "copp") + +# Define the mock files to read from +dedicated_dbs = {} +dedicated_dbs['CONFIG_DB'] = os.path.join(copp_files_path, "config_db.json") +dedicated_dbs['APPL_DB'] = os.path.join(copp_files_path, "appl_db.json") +dedicated_dbs['ASIC_DB'] = os.path.join(copp_files_path, "asic_db.json") +dedicated_dbs['STATE_DB'] = os.path.join(copp_files_path, "state_db.json") + + +def populate_mock(db, db_names): + for db_name in db_names: + db.connect(db_name) + # Delete any default data + db.delete_all_by_pattern(db_name, "*") + with open(dedicated_dbs[db_name]) as f: + mock_json = json.load(f) + for key in mock_json: + for field, value in mock_json[key].items(): + db.set(db_name, key, field, value) + + +@pytest.fixture(scope="class", autouse=True) +def match_engine(): + + print("SETUP") + os.environ["VERBOSE"] = "1" + + # Monkey Patch the SonicV2Connector Object + from ...mock_tables import dbconnector + db = SonicV2Connector() + + # popualate the db with mock data + db_names = list(dedicated_dbs.keys()) + try: + populate_mock(db, db_names) + except Exception as e: + assert False, "Mock initialization failed: " + str(e) + + # Initialize connection pool + conn_pool = ConnectionPool() + DEF_NS = '' # Default Namespace + conn_pool.cache = {DEF_NS: {'conn': db, + 'connected_to': set(db_names)}} + + # Initialize match_engine + match_engine = MatchEngine(conn_pool) + yield match_engine + print("TEARDOWN") + os.environ["VERBOSE"] = "0" + + +@pytest.mark.usefixtures("match_engine") +@patch("dump.plugins.copp.Copp.CONFIG_FILE", os.path.join(dump_test_input, "copp_cfg.json")) +class TestCoppModule: + + def test_usr_cfg_trap_and_copp_cfg_file_grp(self, match_engine): + ''' + Scenario: A custom COPP_TRAP table entry is defined by the user and the relevant Trap Group is configured through the copp_cfg file + ''' + params = {Copp.ARG_NAME: "snmp", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_FILE", "CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB"]) + expect["CONFIG_FILE"]["keys"].append("COPP_GROUP|queue4_group2") + expect["CONFIG_DB"]["keys"].append("COPP_TRAP|snmp_grp") + expect["APPL_DB"]["keys"].append("COPP_TABLE:queue4_group2") + expect["STATE_DB"]["keys"].extend(["COPP_GROUP_TABLE|queue4_group2", "COPP_TRAP_TABLE|snmp_grp"]) + expect["ASIC_DB"]["keys"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004dc", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004da", + "ASIC_STATE:SAI_OBJECT_TYPE_POLICER:oid:0x120000000004db", "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a0"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_copp_cfg_file_trap_and_copp_cfg_file_grp(self, match_engine): + ''' + Scenario: Both the Trap ID and Trap Group are configured through copp_cfg file + ''' + params = {Copp.ARG_NAME: "arp_resp", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_FILE", "CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB"]) + expect["CONFIG_FILE"]["keys"].extend(["COPP_GROUP|queue4_group2", "COPP_TRAP|arp"]) + expect["APPL_DB"]["keys"].append("COPP_TABLE:queue4_group2") + expect["STATE_DB"]["keys"].extend(["COPP_GROUP_TABLE|queue4_group2", "COPP_TRAP_TABLE|arp"]) + expect["ASIC_DB"]["keys"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004dd", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004da", + "ASIC_STATE:SAI_OBJECT_TYPE_POLICER:oid:0x120000000004db", "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a0"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_copp_cfg_file_trap_and_copp_cfg_file_grp_with_diff(self, match_engine): + ''' + Scenario: Both the Trap ID and Trap Group are configured through copp_cfg file. + In addition, User also provided a diff for the COPP_GROUP entry + ''' + params = {Copp.ARG_NAME: "sample_packet", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(json.dumps(returned, indent=2)) + expect = create_template_dict(dbs=["CONFIG_FILE", "CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB"]) + expect["CONFIG_FILE"]["keys"].extend(["COPP_GROUP|queue2_group1", "COPP_TRAP|sflow"]) + expect["CONFIG_DB"]["keys"].append("COPP_GROUP|queue2_group1") + expect["APPL_DB"]["keys"].append("COPP_TABLE:queue2_group1") + expect["STATE_DB"]["keys"].extend(["COPP_GROUP_TABLE|queue2_group1", "COPP_TRAP_TABLE|sflow"]) + expect["ASIC_DB"]["keys"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004de", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004db", + "ASIC_STATE:SAI_OBJECT_TYPE_POLICER:oid:0x120000000004dc", "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a1", + "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd0000000004d6", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TABLE_ENTRY:oid:0x230000000004d8"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_usr_cfg_trap_with_missing_group(self, match_engine): + ''' + Scenario: A custom COPP_TRAP table entry is defined by the user, but the relevant COPP_GROUP entry is missing + ''' + params = {Copp.ARG_NAME: "vrrpv6", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB", "CONFIG_FILE"]) + expect["CONFIG_DB"]["keys"].append("COPP_TRAP|vrrpv6") + expect["CONFIG_DB"]["tables_not_found"].append("COPP_GROUP") + expect["APPL_DB"]["tables_not_found"].append("COPP_TABLE") + expect["STATE_DB"]["tables_not_found"].extend(["COPP_GROUP_TABLE", "COPP_TRAP_TABLE"]) + expect["ASIC_DB"]["tables_not_found"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_copp_cfg_file_group_and_copp_cfg_file_trap_with_diff(self, match_engine): + ''' + Scenario: User has added a trap_id to a COPP_TRAP entry. The COPP_TRAP entry is already present in copp_cfg file (i.e diff) + and the relevant trap group is in copp_cfg file + ''' + params = {Copp.ARG_NAME: "ospfv6", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_FILE", "CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB"]) + expect["CONFIG_FILE"]["keys"].extend(["COPP_GROUP|queue4_group1", "COPP_TRAP|bgp"]) + expect["CONFIG_DB"]["keys"].append("COPP_TRAP|bgp") + expect["APPL_DB"]["keys"].append("COPP_TABLE:queue4_group1") + expect["STATE_DB"]["keys"].extend(["COPP_GROUP_TABLE|queue4_group1", "COPP_TRAP_TABLE|bgp"]) + expect["ASIC_DB"]["keys"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004df", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004db", + "ASIC_STATE:SAI_OBJECT_TYPE_POLICER:oid:0x120000000004dc", "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a1"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_invalid_trap_id(self, match_engine): + params = {Copp.ARG_NAME: "random", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB", "CONFIG_FILE"]) + expect["CONFIG_FILE"]["tables_not_found"].extend(["COPP_GROUP", "COPP_TRAP"]) + expect["CONFIG_DB"]["tables_not_found"].extend(["COPP_GROUP", "COPP_TRAP"]) + expect["APPL_DB"]["tables_not_found"].append("COPP_TABLE") + expect["STATE_DB"]["tables_not_found"].extend(["COPP_GROUP_TABLE", "COPP_TRAP_TABLE"]) + expect["ASIC_DB"]["tables_not_found"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_missing_asic_dump(self, match_engine): + params = {Copp.ARG_NAME: "ospf", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_FILE", "CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB"]) + expect["CONFIG_FILE"]["keys"].extend(["COPP_GROUP|queue4_group1", "COPP_TRAP|bgp"]) + expect["CONFIG_DB"]["keys"].append("COPP_TRAP|bgp") + expect["APPL_DB"]["keys"].append("COPP_TABLE:queue4_group1") + expect["STATE_DB"]["keys"].extend(["COPP_GROUP_TABLE|queue4_group1", "COPP_TRAP_TABLE|bgp"]) + expect["ASIC_DB"]["tables_not_found"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_missing_appl(self, match_engine): + params = {Copp.ARG_NAME: "lldp", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_FILE", "CONFIG_DB", "APPL_DB", "ASIC_DB", "STATE_DB"]) + expect["CONFIG_FILE"]["keys"].extend(["COPP_GROUP|queue4_group3", "COPP_TRAP|lldp"]) + expect["APPL_DB"]["tables_not_found"].append("COPP_TABLE") + expect["STATE_DB"]["tables_not_found"].extend(["COPP_GROUP_TABLE", "COPP_TRAP_TABLE"]) + expect["ASIC_DB"]["tables_not_found"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_missing_state(self, match_engine): + params = {Copp.ARG_NAME: "src_nat_miss", "namespace": ""} + m_copp = Copp(match_engine) + returned = m_copp.execute(params) + print(returned) + expect = create_template_dict(dbs=["CONFIG_FILE", "APPL_DB", "ASIC_DB", "STATE_DB", "CONFIG_DB"]) + expect["CONFIG_FILE"]["keys"].extend(["COPP_GROUP|queue1_group2", "COPP_TRAP|nat"]) + expect["APPL_DB"]["keys"].append("COPP_TABLE:queue1_group2") + expect["STATE_DB"]["tables_not_found"].extend(["COPP_GROUP_TABLE", "COPP_TRAP_TABLE"]) + expect["ASIC_DB"]["keys"].extend(["ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP:oid:0x220000000004e0", "ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF_TRAP_GROUP:oid:0x110000000004e0", + "ASIC_STATE:SAI_OBJECT_TYPE_QUEUE:oid:0x150000000002a1"]) + ddiff = DeepDiff(returned, expect, ignore_order=True) + assert not ddiff, ddiff + + def test_all_args(self, match_engine): + params = {} + m_copp = Copp(match_engine) + returned = m_copp.get_all_args("") + expect = ["bgp", "bgpv6", "lacp", "arp_req", "arp_resp", "neigh_discovery", "lldp", "dhcp", "dhcpv6", "udld", "ip2me", "src_nat_miss", "dest_nat_miss", "sample_packet", "snmp", "bfd", "vrrpv6", "ospf", "ospfv6"] + ddiff = DeepDiff(expect, returned, ignore_order=True) + assert not ddiff, ddiff